LoginSignup
0
1

More than 5 years have passed since last update.

DIなしに組み込み関数とかをテスト時にモックに入れ替える

Last updated at Posted at 2017-05-08
open_repeatedly.py
import time

def open_repeatedly(path, retries=5, retry_interval=1.0):
    while True:
        try:
            return open(path)
        except OSError:
            if retries <= 0:
                raise
            if retry_interval > 0:
                time.sleep(retry_interval)
            retries -= 1

何に使うかよくわからないけどこういう関数があったとして、これの例外時の挙動をテストしたい。例えば retry_interval が0なら time.sleep() が呼ばれないことなどを一応入念にテストしておきたい。

ただ、open()time.sleep() をDIするのは面倒。インターフェース汚れるし。 **kwargs も避けたい。

で、Twitterで unittest.mock.patch を教えてもらった。

test.py
import os
from tempfile import mkstemp
import unittest
from unittest.mock import call, patch, MagicMock

import open_repeatedly

class TestOpenRepeatedly(unittest.TestCase):
    def test_1(self):
        with patch('open_repeatedly.open') as mock:
            expected_ret = MagicMock()
            mock.return_value = expected_ret
            path = '/path/to/test.txt'
            f = open_repeatedly.open_repeatedly(path)
            self.assertEqual(f, expected_ret)
            mock.assert_called_with(path)

    def test_2(self):
        path = '/path/to/test.txt'
        with patch('open_repeatedly.open') as o_mock:
            o_mock.side_effect = OSError('Test')
            with patch('open_repeatedly.time.sleep') as s_mock:
                calls = [call(1.0)] * 5
                with self.assertRaises(OSError):
                    open_repeatedly.open_repeatedly(path)
                self.assertEqual(5, s_mock.call_count)
                s_mock.assert_has_calls(calls)
            self.assertEqual(6, o_mock.call_count)
            o_mock.assert_called_with(path)

    def test_3(self):
        path = '/path/to/test.txt'
        with patch('open_repeatedly.open') as o_mock:
            o_mock.side_effect = OSError('Test')
            with patch('open_repeatedly.time.sleep') as s_mock:
                with self.assertRaises(OSError):
                    open_repeatedly.open_repeatedly(
                        path, retry_interval=0)
                self.assertEqual(0, s_mock.call_count)
            self.assertEqual(6, o_mock.call_count)
            o_mock.assert_called_with(path)

    @patch('open_repeatedly.time.sleep')
    @patch('open_repeatedly.open')
    def test_4(self, o_mock, s_mock):
        path = '/path/to/test.txt'
        o_mock.side_effect = OSError('Test')
        with self.assertRaises(OSError):
            open_repeatedly.open_repeatedly(
                path, retries=0)
        self.assertEqual(0, s_mock.call_count)
        self.assertEqual(1, o_mock.call_count)
        o_mock.assert_called_with(path)


if __name__ == '__main__':
    unittest.main()

デコレータのほうが楽だな。

$ python -m unittest
....
----------------------------------------------------------------------
Ran 4 tests in 0.005s

OK

実装してみた感じ期待通りの動作の様子。
使い方、合ってるだろうか。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1