最近在幫公司開發,使用 python 製作出 Windows 使用的小工具,順便 k 了一下單元測試,把遇到過的技巧做個筆記。
| import os |
| import shutil |
| |
| import requests as requests |
| |
| |
| class Chan: |
| def add(self, number1: int, number2: int) -> int: |
| return number1 + number2 |
| |
| def raise_method(self, result: str) -> str: |
| if result != 'raise': |
| return result |
| |
| raise Exception('exception raised') |
| |
| def a(self) -> str: |
| return self.b() |
| |
| def b(self) -> str: |
| return 'b' |
| |
| def move_file(self, from_path, to_path) -> object: |
| return os.rename(from_path, to_path) |
| |
| def get_url(self, url) -> int: |
| response = requests.get(url) |
| |
| return response.status_code |
| |
| def my_open(self, path) -> str: |
| with open(path) as f: |
| return f.read() |
| |
| def copy_twice(self, source: str, dest_dir: str) -> None: |
| base_path = os.path.dirname(__file__) |
| shutil.copyfile(source, os.path.join(base_path, dest_dir, 'first')) |
| shutil.copyfile(source, os.path.join(base_path, dest_dir, 'second')) |
| |
| def scan_folder(self, path) -> list: |
| files = [] |
| for item in os.listdir(os.path.join(path)): |
| if os.path.isfile(os.path.join(path, item)): |
| files.append(os.path.join(path, item)) |
| |
| return files |
| |
tests/test_chan.py
| import os.path |
| import unittest |
| from unittest.mock import patch, MagicMock, mock_open, call |
| |
| from chan import Chan |
| |
| |
| def mock_response(*args, **kwargs) -> object: |
| class Response: |
| status_code = 500 |
| |
| return Response() |
| |
| |
| class MyTestCase(unittest.TestCase): |
| base_path: str |
| |
| def setUp(self) -> None: |
| self.base_path = os.path.dirname(os.path.dirname(__file__)) |
| |
| def test_add(self) -> None: |
| chan = Chan() |
| actual = chan.add(1, 2) |
| self.assertEqual(3, actual) |
| |
| def test_raise_should_not_happened(self) -> None: |
| chan = Chan() |
| actual = chan.raise_method('test') |
| self.assertEqual('test', actual) |
| |
| def test_raise_should_happened(self) -> None: |
| chan = Chan() |
| self.assertRaises(Exception, chan.raise_method, 'raise') |
| |
| def test_raise_error_message(self) -> None: |
| chan = Chan() |
| with self.assertRaises(Exception) as error: |
| chan.raise_method('raise') |
| |
| self.assertEqual('exception raised', str(error.exception)) |
| |
| @patch.object(Chan, 'b', MagicMock(return_value='c')) |
| def test_mock_method(self) -> None: |
| chan = Chan() |
| actual = chan.a() |
| self.assertEqual('c', actual) |
| |
| @patch.object(Chan, 'b') |
| def test_mock_method_called_once_by_injection(self, mock_b: MagicMock) -> None: |
| mock_b.return_value = 'c' |
| chan = Chan() |
| actual = chan.a() |
| self.assertEqual('c', actual) |
| self.assertTrue(mock_b.called) |
| |
| def test_mock_method_called_once_by_with(self) -> None: |
| with patch.object(Chan, 'b') as check: |
| check.return_value = 'c' |
| chan = Chan() |
| actual = chan.a() |
| self.assertEqual('c', actual) |
| check.assert_called_once() |
| |
| @patch('chan.os.rename', MagicMock(return_value='moved')) |
| def test_os_method(self) -> None: |
| chan = Chan() |
| actual = chan.move_file('1.txt', '2.txt') |
| self.assertEqual('moved', actual) |
| |
| @patch('chan.requests.get', MagicMock(side_effect=mock_response)) |
| def test_requests_get(self) -> None: |
| chan = Chan() |
| actual = chan.get_url('https://www.google.com') |
| self.assertEqual(500, actual) |
| |
| @patch('chan.open', mock_open(read_data='ok')) |
| def test_open(self) -> None: |
| chan = Chan() |
| actual = chan.my_open('path') |
| self.assertEqual('ok', actual) |
| |
| @patch('chan.open', new_callable=mock_open, read_data='ok') |
| def test_open_by_injection(self, m) -> None: |
| chan = Chan() |
| actual = chan.my_open('path') |
| self.assertEqual('ok', actual) |
| self.assertTrue(m.called) |
| |
| @patch('chan.shutil.copyfile') |
| def test_copy_twice(self, mock_copyfile: MagicMock) -> None: |
| chan = Chan() |
| source = 'test.zip' |
| chan.copy_twice(source, 'test_path') |
| |
| expected = [call(source, os.path.join(self.base_path, 'test_path', 'first')), |
| call(source, os.path.join(self.base_path, 'test_path', 'second'))] |
| |
| self.assertEqual(expected, mock_copyfile.call_args_list) |
| self.assertEqual(2, mock_copyfile.call_count) |
| |
| @patch('chan.os.path.isfile') |
| @patch('chan.os.listdir') |
| def test_scan_folder(self, mock_listdir: MagicMock, mock_isfile: MagicMock) -> None: |
| mock_listdir.return_value = ['dir1', 'file1', 'dir2', 'file2'] |
| mock_isfile.side_effect = [False, True, False, True] |
| |
| chan = Chan() |
| test_dir = 'test_dir' |
| actual = chan.scan_folder('test_dir') |
| expected = [os.path.join(test_dir, 'file1'), os.path.join(test_dir, 'file2')] |
| self.assertEqual(expected, actual) |
| |
| |
| if __name__ == '__main__': |
| unittest.main() |
| |