目录
- 核心概念:理解
unittest的四大核心组件。 - 第一个测试脚本:从零开始,创建一个简单的测试。
- 断言方法:介绍常用的断言,用于验证代码行为。
- 测试夹具:
setUp和tearDown,用于管理测试前后的环境。 - 测试套件:如何组织和运行多个测试文件。
- 高级特性:跳过测试、预期异常等。
- 最佳实践:编写可维护测试的建议。
核心概念
unittest 框架基于四个核心概念:

- Test Case (测试用例):最小的测试单元,它检查你的代码对特定输入是否产生预期的输出,在
unittest中,我们通过继承unittest.TestCase类来创建测试用例。 - Test Fixture (测试夹具):执行一个或多个测试前需要准备的环境,以及在测试完成后需要清理的环境,这通常通过
setUp()和tearDown()方法来实现。 - Test Suite (测试套件):一组测试用例、测试套件或它们的组合,你可以将多个测试用例打包在一起,然后一次性运行它们。
- Test Runner (测试运行器):执行测试套件,并将结果报告给用户,可以是文本、图形界面等,最常用的是
unittest.TextTestRunner。
第一个测试脚本
让我们从一个简单的例子开始,假设我们有一个简单的计算器模块 calculator.py,我们要对它进行测试。
步骤 1: 创建被测试的代码 (calculator.py)
# calculator.py
def add(a, b):
"""返回两个数的和"""
return a + b
def subtract(a, b):
"""返回两个数的差"""
return a - b
def multiply(a, b):
"""返回两个数的积"""
return a * b
def divide(a, b):
"""返回两个数的商"""
if b == 0:
raise ValueError("Cannot divide by zero")
return a / b
步骤 2: 创建测试脚本 (test_calculator.py)
测试脚本需要遵循一定的结构:
- 导入
unittest模块和你的被测试代码。 - 创建一个类,继承自
unittest.TestCase。 - 在类中编写以
test_开头的方法,这些方法就是你的测试用例。 - 在测试方法中,使用
self.assertEqual(),self.assertTrue()等断言方法来验证结果。
# test_calculator.py
import unittest
from calculator import add, subtract, multiply, divide
class TestCalculator(unittest.TestCase):
"""测试 Calculator 模块中的所有函数"""
def test_add(self):
"""测试加法函数"""
self.assertEqual(add(1, 2), 3)
self.assertEqual(add(-1, 5), 4)
self.assertEqual(add(0, 0), 0)
def test_subtract(self):
"""测试减法函数"""
self.assertEqual(subtract(5, 3), 2)
self.assertEqual(subtract(2, 5), -3)
def test_multiply(self):
"""测试乘法函数"""
self.assertEqual(multiply(3, 4), 12)
self.assertEqual(multiply(-2, 5), -10)
def test_divide(self):
"""测试除法函数"""
self.assertEqual(divide(10, 2), 5)
self.assertEqual(divide(9, 2), 4.5)
# 测试除以零的情况
with self.assertRaises(ValueError):
divide(5, 0)
if __name__ == '__main__':
unittest.main()
代码解释:
import unittest: 导入测试框架。from calculator import ...: 导入我们要测试的函数。class TestCalculator(unittest.TestCase):: 定义一个测试类,它必须继承unittest.TestCase。def test_add(self):: 这是一个测试用例,所有测试方法都必须以test_开头,这样测试运行器才能自动发现它们。self.assertEqual(actual, expected): 这是核心断言方法,它会比较actual(实际结果)和expected(期望结果),如果两者不相等,测试就会失败。with self.assertRaises(ValueError):: 这是一个特殊的断言,用于验证代码是否按预期抛出了特定的异常。divide(5, 0)没有抛出ValueError,测试就会失败。if __name__ == '__main__':: 这行代码确保只有当脚本直接运行时(而不是被其他模块导入时),才会执行测试。unittest.main(): 启动测试运行器,它会自动查找当前文件中以test_开头的方法并执行它们。
步骤 3: 运行测试
在终端中,导航到 test_calculator.py 所在的目录,然后运行脚本:

python test_calculator.py
预期输出:
....
----------------------------------------------------------------------
Ran 4 tests in 0.001s
OK
- 代表 4 个测试点都通过了。
OK表示所有测试都成功。
如果某个测试失败,你会看到类似这样的详细输出,指明是哪个测试用例、哪个断言失败了。
常用断言方法
unittest.TestCase 类提供了许多断言方法,用于验证不同条件:
| 断言方法 | 描述 |
|---|---|
assertEqual(a, b) |
a == b |
assertNotEqual(a, b) |
a != b |
assertTrue(x) |
bool(x) is True |
assertFalse(x) |
bool(x) is False |
assertIs(a, b) |
a is b (对象身份比较) |
assertIsNot(a, b) |
a is not b |
assertIsNone(x) |
x is None |
assertIsNotNone(x) |
x is not None |
assertIn(a, b) |
a in b |
assertNotIn(a, b) |
a not in b |
assertIsInstance(a, b) |
isinstance(a, b) |
assertNotIsInstance(a, b) |
not isinstance(a, b) |
assertRaises(exception, callable, *args, **kwargs) |
验证 callable 调用是否抛出 exception |
assertAlmostEqual(a, b, places=7) |
round(a-b, places) == 0 (用于浮点数比较) |
assertNotAlmostEqual(a, b, places=7) |
round(a-b, places) != 0 |
测试夹具
如果你的多个测试用例需要共享一些准备和清理工作,可以使用 setUp() 和 tearDown()。

setUp(): 在每个测试用例运行之前被调用,用于创建测试所需的对象、建立数据库连接等。tearDown(): 在每个测试用例运行之后被调用,用于关闭文件、断开数据库连接、删除临时文件等。
示例
# test_example_with_fixture.py
import unittest
class TestStringMethods(unittest.TestCase):
def setUp(self):
"""在每个测试前运行"""
print("\n[+] Setting up for a new test...")
self.sample_list = [1, 2, 3]
def tearDown(self):
"""在每个测试后运行"""
print("[-] Tearing down after the test.")
# 通常在这里清理资源
def test_append(self):
"""测试列表的 append 方法"""
self.sample_list.append(4)
self.assertEqual(self.sample_list, [1, 2, 3, 4])
def test_pop(self):
"""测试列表的 pop 方法"""
self.sample_list.pop()
self.assertEqual(self.sample_list, [1, 2])
if __name__ == '__main__':
unittest.main()
运行这个脚本,你会看到 setUp 和 tearDown 的打印信息在每次测试前后被调用。
测试套件
当你的项目变大,会有很多测试文件。unittest.TestSuite 可以让你将它们组合起来一起运行。
假设我们有两个测试文件:test_calculator.py 和 test_string_methods.py。
创建一个主测试运行器 (run_all_tests.py)
# run_all_tests.py
import unittest
from test_calculator import TestCalculator
from test_string_methods import TestStringMethods # 假设这个文件存在
if __name__ == '__main__':
# 1. 创建一个测试套件
suite = unittest.TestSuite()
# 2. 将测试用例添加到套件中
# 方法一:添加一个测试类中的所有测试
suite.addTest(unittest.makeSuite(TestCalculator))
suite.addTest(unittest.makeSuite(TestStringMethods))
# 方法二:添加特定的测试方法
# suite.addTest(TestCalculator('test_add'))
# suite.addTest(TestStringMethods('test_append'))
# 3. 创建一个测试运行器
runner = unittest.TextTestRunner(verbosity=2) # verbosity=2 提供更详细的输出
# 4. 运行测试套件
print("Running all tests...")
runner.run(suite)
你只需要运行 python run_all_tests.py 就能执行所有测试。
高级特性
跳过测试
有时你可能想跳过某个测试,因为它还不稳定、依赖于外部环境或者与当前版本不相关。
import unittest
import sys
class MyTests(unittest.TestCase):
@unittest.skip("暂时跳过这个测试")
def test_skipped(self):
self.fail("这个测试不应该被执行")
@unittest.skipIf(sys.version_info < (3, 6), "需要 Python 3.6 或更高版本")
def test_skipped_on_old_python(self):
self.assertEqual("hello".format(), "hello")
@unittest.skipUnless(sys.platform == "win32", "只在 Windows 上运行")
def test_windows_only(self):
self.assertTrue(True)
if __name__ == '__main__':
unittest.main()
预期失败
你知道一个测试会失败,但你希望它被标记为“预期失败”,而不是“错误”。
import unittest
class MyTests(unittest.TestCase):
@unittest.expectedFailure
def test_expected_to_fail(self):
self.assertEqual(1 + 1, 3) # 这个测试肯定会失败
最佳实践
-
命名清晰:
- 测试文件名:
test_开头,如test_user_model.py。 - 测试类名:
Test开头,描述被测试的类或模块,如TestUserModel。 - 测试方法名:
test_开头,清晰描述测试场景,如test_user_can_be_created或test_user_login_with_invalid_password。
- 测试文件名:
-
一个测试,一个断言:理想情况下,一个测试方法只验证一件事,这使得定位失败原因更容易。
-
测试独立的:测试之间不应该相互依赖,每个测试都应该能独立运行,并产生可预测的结果,这就是
setUp和tearDown的意义所在。 -
测试边界条件和异常:不仅要测试“正常情况”,还要测试空值、零、最大/最小值、非法输入等边界条件。
-
使用 Mock:当你的代码依赖于外部系统(如数据库、网络API、文件系统)时,使用
unittest.mock模块来创建这些依赖的“模拟”对象,这能让你的测试运行得更快、更可靠,并且不依赖外部环境,这是一个更高级的话题,但对于大型项目至关重要。
unittest 是 Python 测试的基石,掌握了它,你就为编写健壮、可靠的 Python 代码打下了坚实的基础,随着项目复杂度的增加,你还可以探索 pytest 等更现代、更灵活的测试框架,但 unittest 的原理和许多概念是相通的。
