1. 本期主题:Python单元测试框架unittest
详解
unittest
是Python内置的单元测试框架,遵循Java JUnit的"测试驱动开发"(TDD)理念,通过继承TestCase
类实现测试用例的模块化组织。本文聚焦于独立测试脚本的编写,暂不涉及数据库集成或参数化测试等高级场景(参数化测试建议使用parameterized
包或pytest
实现)。
涵盖内容
- 基础机制:测试类与测试方法的定义规范
- 执行流程:从脚本运行到结果输出的完整链路
- 结果解读:通过符号标记快速定位测试问题
不涵盖内容
- DOM操作(前端测试建议使用
Selenium
) - 数据库集成测试(需结合
unittest.mock
或pytest-fixture
) - 参数化测试(后续文章将单独讲解)
2. unittest
核心机制与执行流程
2.1 测试脚本结构解析
import unittest
class CalculatorTestCase(unittest.TestCase):
"""加法器测试类"""
def test_add_positive(self):
"""正数加法测试"""
self.assertEqual(10 + 5, 15)
def test_add_negative(self):
"""负数加法测试"""
self.assertEqual(-3 + 7, 4)
if __name__ == "__main__":
unittest.main(verbosity=2) # 增加输出详细度
关键点说明:
- 命名规范:测试类以
Test
结尾,测试方法以test_
开头 - 文档字符串:类和方法建议添加说明性注释
- 执行参数:
verbosity=2
可显示测试名称而非仅点号
2.2 执行结果解读
运行上述脚本将输出:
test_add_negative (__main__.CalculatorTestCase) ... ok
test_add_positive (__main__.CalculatorTestCase) ... ok
----------------------------------------------------------------------
Ran 2 tests in 0.001s
OK
- OK标记:所有测试通过
- FAIL标记:断言失败时会显示具体值(如
self.assertEqual(10, 20)
会输出10 != 20
)
3. 常用断言方法详解
3.1 基础断言
class StringTestCase(unittest.TestCase):
def test_string_operations(self):
# 字符串相等验证
self.assertEqual("tianxin".upper(), "TIANXIN")
# 字符串包含验证
self.assertIn("xin", "tianxin")
# 布尔值验证
self.assertTrue("tianxin".startswith("tian"))
self.assertFalse("tianxin".endswith("xin")) # 实际会失败,此处仅为示例
失败案例演示:
def test_false_positive(self):
self.assertEqual(10, 20) # 输出: AssertionError: 10 != 20
3.2 异常验证
class DivisionTestCase(unittest.TestCase):
def test_zero_division(self):
with self.assertRaises(ZeroDivisionError):
5 / 0
def test_invalid_type(self):
with self.assertRaises(TypeError):
"10" + 5 # 字符串与整数拼接会触发TypeError
应用场景:验证边界条件(如除零、类型错误)
4. 生命周期钩子:setUp
与tearDown
4.1 层级说明与执行顺序
钩子方法 | 执行时机 | 适用场景 |
---|---|---|
setUpModule |
模块首次导入时 | 初始化全局资源(如数据库连接池) |
setUpClass |
测试类首次实例化时 | 类级别资源(如测试文件路径) |
setUp |
每个测试方法执行前 | 测试方法独占资源(如临时文件) |
tearDown |
每个测试方法执行后 | 清理测试残留(如删除临时文件) |
tearDownClass |
测试类所有方法执行完毕后 | 释放类级别资源 |
tearDownModule |
模块所有测试执行完毕后 | 关闭全局资源 |
4.2 完整示例
import os
import unittest
class FileOperationTestCase(unittest.TestCase):
temp_file = "temp_test.txt"
@classmethod
def setUpClass(cls):
print("▶ 准备测试文件...")
with open(cls.temp_file, "w") as f:
f.write("initial content")
def setUp(self):
print(" → 每个测试前重置文件内容")
with open(self.temp_file, "w") as f:
f.write("") # 清空文件
def test_write_content(self):
with open(self.temp_file, "a") as f:
f.write("line1\n")
self.assertTrue(os.path.exists(self.temp_file))
def test_append_content(self):
with open(self.temp_file, "a") as f:
f.write("line2\n")
with open(self.temp_file) as f:
self.assertEqual(f.read(), "line2\n") # 验证清空操作是否生效
@classmethod
def tearDownClass(cls):
print("◀ 删除测试文件")
os.remove(cls.temp_file) if os.path.exists(cls.temp_file) else None
if __name__ == "__main__":
unittest.main()
输出顺序:
▶ 准备测试文件...
→ 每个测试前重置文件内容
test_write_content ... ok
→ 每个测试前重置文件内容
test_append_content ... ok
◀ 删除测试文件
5. 测试跳过机制
5.1 装饰器应用
import unittest
import platform
class PlatformTestCase(unittest.TestCase):
@unittest.skipIf(platform.system() == "Windows", "Windows系统暂不支持")
def test_linux_feature(self):
print("仅在Linux下运行的测试")
@unittest.skipUnless(hasattr(os, "symlink"), "系统不支持符号链接")
def test_symlink(self):
print("符号链接测试")
@unittest.skip("功能重构中,暂不测试")
def test_deprecated_feature(self):
print("已弃用功能测试")
执行结果:
s (skipped) ... skipped 'Windows系统暂不支持'
s (skipped) ... skipped '系统不支持符号链接'
s (skipped) ... skipped '功能重构中,暂不测试'
6. 总结与建议
- 测试设计原则:
- 每个测试方法只验证一个功能点
- 使用有意义的测试名称(如
test_add_positive
而非test1
) - 优先使用
setUp
/tearDown
而非重复代码
- 扩展方向:
- 数据库测试:结合
unittest.mock
模拟数据库连接 - 参数化测试:使用
pytest.mark.parametrize
或parameterized
包 - 集成测试:通过
subTest
实现测试数据驱动
- 工具链建议:
- 简单项目:直接使用
unittest
- 复杂项目:迁移至
pytest
(支持更简洁的语法和插件生态) - 持续集成:结合
tox
实现多Python版本测试
通过以上结构化讲解,读者可系统掌握unittest
的核心用法,并逐步向更复杂的测试场景扩展。