第11章:测试代码(Testing Code)
一、导读
1.1 本章学习目标
第11章介绍Python的单元测试。本章核心目标:
- 掌握unittest模块:创建测试用例
- 学会编写测试方法:test_开头的方法
- 掌握断言方法:assertEqual、assertTrue等
- 学会使用setUp():测试初始化
- 理解测试驱动开发(TDD)
1.2 为什么本章如此重要
编写测试是专业程序员的重要技能。测试能提高代码质量和可维护性,防止回归bug。Python的unittest框架提供了完整的测试基础设施。
1.3 核心问题引导
单元测试和集成测试有什么区别?
什么是测试覆盖率?为什么100%覆盖率不等于没有bug?
如何测试抛出异常的代码?
mock对象在测试中如何使用?
二、核心概念详解
11.1 unittest基础
python
import unittest
def format_name(first, last, middle=""):
"""返回格式化后的全名"""
if middle:
return f"{first.title()} {middle.title()} {last.title()}"
return f"{first.title()} {last.title()}"
class NameTestCase(unittest.TestCase):
"""测试format_name函数"""
def test_first_last_name(self):
"""测试只有名和姓的情况"""
formatted = format_name("janis", "joplin")
self.assertEqual(formatted, "Janis Joplin")
def test_with_middle_name(self):
"""测试包含中间名的情况"""
formatted = format_name("john", "hooker", "lee")
self.assertEqual(formatted, "John Lee Hooker")
def test_empty_middle_name(self):
"""测试空中间名"""
formatted = format_name("bob", "dylan", "")
self.assertEqual(formatted, "Bob Dylan")
if __name__ == "__main__":
unittest.main()11.2 测试类
python
import unittest
class Survey:
def __init__(self, question):
self.question = question
self.responses = []
def store_response(self, response):
self.responses.append(response)
def show_results(self):
for response in self.responses:
print(f"- {response}")
class TestSurvey(unittest.TestCase):
def setUp(self):
"""创建测试对象"""
self.survey = Survey("What language did you first learn?")
def test_store_single_response(self):
"""测试存储单个响应"""
self.survey.store_response("English")
self.assertIn("English", self.survey.responses)
def test_store_multiple_responses(self):
"""测试存储多个响应"""
responses = ["English", "Spanish", "Mandarin"]
for response in responses:
self.survey.store_response(response)
for response in responses:
self.assertIn(response, self.survey.responses)
def test_empty_survey(self):
"""测试空调查"""
self.assertEqual(len(self.survey.responses), 0)
if __name__ == "__main__":
unittest.main()11.3 常用断言方法
python
class TestAssertions(unittest.TestCase):
def test_assertions(self):
# 相等性
self.assertEqual(1 + 1, 2)
self.assertNotEqual(1 + 1, 3)
# 布尔值
self.assertTrue(True)
self.assertFalse(False)
# 包含
self.assertIn(1, [1, 2, 3])
self.assertNotIn(4, [1, 2, 3])
# 类型
self.assertIsInstance("hello", str)
self.assertNotIsInstance(42, str)
# 异常
with self.assertRaises(ValueError):
int("not a number")
# 近似相等
self.assertAlmostEqual(0.1 + 0.2, 0.3, places=10)
# 正则匹配
self.assertRegex("hello world", r"hello \w+")11.4 pytest简介
python
# test_example.py (pytest风格)
import pytest
def add(a, b):
return a + b
def divide(a, b):
if b == 0:
raise ValueError("Cannot divide by zero")
return a / b
def test_add():
assert add(1, 2) == 3
assert add(-1, 1) == 0
def test_divide():
assert divide(10, 2) == 5
with pytest.raises(ValueError):
divide(10, 0)
@pytest.fixture
def sample_data():
return [1, 2, 3, 4, 5]
def test_sum(sample_data):
assert sum(sample_data) == 15
# 参数化测试
@pytest.mark.parametrize("a, b, expected", [
(1, 2, 3),
(0, 0, 0),
(-1, -1, -2),
])
def test_add_parametrized(a, b, expected):
assert add(a, b) == expected三、实战案例
3.1 测试一个完整的模块
python
# calculator.py
class Calculator:
def __init__(self):
self.history = []
def add(self, a, b):
result = a + b
self.history.append(f"{a} + {b} = {result}")
return result
def divide(self, a, b):
if b == 0:
raise ValueError("Division by zero")
result = a / b
self.history.append(f"{a} / {b} = {result}")
return result
def get_history(self):
return self.history
# test_calculator.py
import unittest
from calculator import Calculator
class TestCalculator(unittest.TestCase):
def setUp(self):
self.calc = Calculator()
def test_add(self):
self.assertEqual(self.calc.add(2, 3), 5)
self.assertEqual(self.calc.add(-1, 1), 0)
def test_divide(self):
self.assertEqual(self.calc.divide(10, 2), 5)
self.assertAlmostEqual(self.calc.divide(1, 3), 0.333, places=2)
def test_divide_by_zero(self):
with self.assertRaises(ValueError):
self.calc.divide(10, 0)
def test_history(self):
self.calc.add(1, 2)
self.calc.divide(10, 5)
history = self.calc.get_history()
self.assertEqual(len(history), 2)
self.assertIn("1 + 2 = 3", history)
if __name__ == "__main__":
unittest.main()四、本章小结
第11章介绍了Python的测试:unittest框架、测试用例、断言方法、setUp()、pytest。编写测试是保证代码质量的重要手段。
五、思考与练习
思考题
单元测试和端到端测试有什么区别?
什么是mock?什么时候需要mock?
测试覆盖率100%能保证没有bug吗?
TDD的流程是什么?
编程练习
为之前的函数和类编写完整的单元测试。
使用pytest重写unittest测试。
实现一个测试覆盖率报告工具。