unittest.TestCase
是所有测试类的基类,它为测试提供了丰富的断言方法和测试工具。通过继承 unittest.TestCase
,可以创建自己的测试类,并定义测试方法来验证代码的行为。
-
断言方法:
assertEqual(a, b)
:检查a
和b
是否相等。assertTrue(x)
:检查x
是否为True
。assertFalse(x)
:检查x
是否为False
。assertRaises(Exception, func, *args, **kwargs)
:检查是否抛出指定的异常。
-
测试方法的命名:
- 在
TestCase
类中,以test_
开头的方法将被自动识别为测试方法,并在运行测试时自动执行。
- 在
-
设置和清理:
setUp()
:在每个测试方法运行之前执行,用于初始化测试环境。tearDown()
:在每个测试方法运行之后执行,用于清理测试环境。
import unittest
class MyTestCase(unittest.TestCase):
def setUp(self):
# 初始化代码
pass
def test_example(self):
self.assertEqual(1 + 1, 2)
def tearDown(self):
# 清理代码
pass
if __name__ == '__main__':
unittest.main()
@patch
装饰器是 unittest.mock
模块中的一个功能强大的工具,用于在单元测试中替换模块或类的属性,使其指向一个模拟对象。通过使用 @patch
,可以在测试过程中替换特定的函数或对象,以控制其行为,并避免依赖外部资源(如文件系统、数据库、网络请求等)。
target
:指定要替换的对象。通常是一个字符串,表示模块路径(如'builtins.open'
)。new
:提供一个新的对象来替换目标对象。可以是任何对象,通常是一个模拟对象(如mock_open
)。new_callable
:指定一个可以调用的对象,当目标对象被替换时,将返回这个对象的实例。常用于创建模拟对象(如mock_open
)。
在单元测试中,@patch
主要用于:
- 模拟外部依赖:例如,模拟文件读取和写入、网络请求、数据库操作等。
- 控制测试环境:通过替换特定对象,可以精确控制测试中的行为,使得测试更加可靠和可控。
- 验证调用:可以检查被替换对象的调用情况,如是否被调用、调用次数、传入的参数等。
@patch('builtins.open', new_callable=mock_open, read_data=json.dumps(["DjangoPeng/openai-quickstart", "some/repo"]))
def test_save_subscriptions(self, mock_file):
# 测试代码...
代码解释:
-
@patch('builtins.open', new_callable=mock_open, read_data=json.dumps(["DjangoPeng/openai-quickstart", "some/repo"]))
:'builtins.open'
:表示我们要替换 Python 内置的open
函数,因为在SubscriptionManager
中会使用open
来读写文件。new_callable=mock_open
:指示patch
使用mock_open
来替换open
。mock_open
是一个专门用于模拟文件操作的工具,它能够模拟文件的打开、读取、写入等行为。read_data=json.dumps(["DjangoPeng/openai-quickstart", "some/repo"])
:指定当文件被读取时,mock_open
将返回的模拟文件内容。在这个例子中,文件内容是一个 JSON 字符串,表示一个包含订阅数据的列表。
-
模拟文件操作:
- 在测试
save_subscriptions
和load_subscriptions
方法时,@patch
替换了真实的文件操作,使得测试环境完全受控,不依赖外部的文件系统。 - 使用
mock_open
替换open
后,所有针对文件的操作都变成了对模拟对象的操作,这样可以捕获和检查这些操作的细节(如写入内容、调用次数等)。
- 在测试
-
mock_file
参数:mock_file
是mock_open
返回的模拟对象,它被传递到测试函数中,允许测试代码对其进行检查和验证。例如,mock_file.assert_called_with
用于验证open
是否以特定的参数被调用。
-
@patch.object
:用于替换特定对象的属性。@patch.object(SomeClass, 'some_method') def test_some_method(self, mock_method): # 测试代码...
-
@patch.multiple
:用于一次性替换多个对象的属性。@patch.multiple(SomeClass, method1=DEFAULT, method2=DEFAULT) def test_multiple_methods(self, method1, method2): # 测试代码...
@patch
是单元测试中替换和模拟依赖的强大工具,能够使测试更加可靠和独立。- 在
test_subscription_manager.py
中,我们使用@patch
模拟了文件操作,从而避免了对实际文件系统的依赖,同时能够检查和验证文件操作的正确性。
MagicMock
是 unittest.mock
模块中的一个强大的模拟工具。它是 Mock
类的子类,继承了 Mock
的所有功能,并扩展了一些额外的功能,使其更强大和灵活。在单元测试中,MagicMock
通常用于替代或模拟某些对象的行为,从而控制测试环境,避免依赖外部资源或复杂的逻辑。
-
模拟对象的方法和属性:
MagicMock
可以模拟任何对象的属性和方法。在测试中,您可以随意定义这些属性和方法的返回值、调用次数、传入的参数等。
-
自动处理魔术方法:
MagicMock
可以自动处理 Python 中的魔术方法(如__str__
、__call__
、__iter__
等)。这使得它在模拟类或复杂对象时更加灵活。
-
行为定义:
- 您可以通过设置
MagicMock
的返回值或副作用(side effect)来定义其行为。例如,可以指定某个方法在调用时返回特定的值,或引发特定的异常。
- 您可以通过设置
-
调用检查:
MagicMock
记录所有的调用信息,您可以在测试中检查这些信息,以验证某些方法是否被调用过,调用了几次,传入了哪些参数等。
在 test_report_generator.py
中,MagicMock
被用来模拟 LLM
(大语言模型)的行为。这是因为在实际的测试中,调用真正的 LLM 可能会消耗大量资源或依赖外部服务,而我们只关心 ReportGenerator
是否正确调用了 LLM 并处理其返回结果。因此,我们使用 MagicMock
来替代真实的 LLM。
self.mock_llm = MagicMock()
-
MagicMock
作为 LLM 的模拟对象:这里的MagicMock
对象self.mock_llm
被传递给ReportGenerator
。在测试中,self.mock_llm
的generate_daily_report
方法被模拟,返回一个我们指定的报告字符串mock_report
。 -
模拟方法的返回值:
self.mock_llm.generate_daily_report.return_value = mock_report
这行代码设置了
generate_daily_report
方法的返回值为mock_report
,这样在测试中调用这个方法时,总是返回我们预期的报告内容。 -
验证调用:
self.mock_llm.generate_daily_report.assert_called_once_with(self.markdown_content)
通过
assert_called_once_with
,我们验证generate_daily_report
方法是否被调用了一次,并且传入的参数与预期一致。
MagicMock
是一个非常灵活和强大的工具,允许您在测试中替代复杂对象或外部依赖,模拟其行为,并验证其调用情况。它在单元测试中被广泛使用,尤其适合模拟依赖注入、API 调用、数据库操作等场景,使得测试更加独立、可控和高效。