[PYTHON] Ersetzen Sie die eingebauten Funktionen zur Testzeit ohne DI durch Mock

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

Ich bin nicht sicher, wofür ich es verwenden soll, aber wenn Sie eine Funktion wie diese haben, möchte ich ihr außergewöhnliches Verhalten testen. Wenn beispielsweise "retry_interval" 0 ist, wird "time.sleep ()" nicht aufgerufen.

Es ist jedoch problematisch, DI open () und time.sleep (). Die Schnittstelle wird schmutzig. Ich möchte auch ** kwargs vermeiden.

Also wurde mir auf Twitter "unittest.mock.patch" gesagt.

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()

Dekorateure sind einfacher.

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

OK

Ich habe das Gefühl, dass ich es implementiert habe. Es sieht so aus, als würde es wie erwartet funktionieren. Ist es richtig, wie man es benutzt?

Recommended Posts

Ersetzen Sie die eingebauten Funktionen zur Testzeit ohne DI durch Mock
Ersetzen Sie alles auf einmal durch sed