1. Python单元测试(unittest)实战指南
在软件开发中,单元测试是保证代码质量的第一道防线。Python内置的unittest模块提供了一套完整的测试框架,能够帮助我们编写、组织和运行测试用例。作为一名有多年Python开发经验的工程师,我发现很多项目虽然引入了单元测试,但往往停留在表面,没有充分发挥其价值。本文将分享如何在实际项目中高效运用unittest框架,从基础用法到高级技巧,带你掌握单元测试的实战精髓。
2. unittest框架核心概念
2.1 测试用例(TestCase)
unittest的核心是TestCase类,它代表一个独立的测试单元。每个测试方法都应该是一个以"test_"开头的方法,这样unittest才能自动识别并执行它们。
python复制import unittest
class TestStringMethods(unittest.TestCase):
def test_upper(self):
self.assertEqual('foo'.upper(), 'FOO')
def test_isupper(self):
self.assertTrue('FOO'.isupper())
self.assertFalse('Foo'.isupper())
在实际项目中,我习惯将测试类与被测代码保持相同的包结构,这样既方便管理,又能快速定位问题。例如,如果有一个utils.py模块,我会在同级目录下创建test_utils.py文件。
2.2 断言方法
unittest提供了丰富的断言方法,这是编写测试的核心工具。最常用的包括:
assertEqual(a, b):验证a == bassertTrue(x):验证x是TrueassertFalse(x):验证x是FalseassertRaises(exc, fun, *args, **kwargs):验证函数抛出指定异常
我特别推荐使用assertRaises来测试异常情况,这是很多开发者容易忽略的部分。一个健壮的测试套件应该同时覆盖正常路径和异常路径。
2.3 测试固件(Fixture)
测试固件指的是测试前后的准备和清理工作。unittest提供了setUp()和tearDown()方法:
python复制class TestDatabase(unittest.TestCase):
def setUp(self):
self.conn = create_test_connection()
self.cursor = self.conn.cursor()
def tearDown(self):
self.cursor.close()
self.conn.close()
def test_query(self):
self.cursor.execute("SELECT 1")
result = self.cursor.fetchone()
self.assertEqual(result, (1,))
在实际项目中,我经常使用setUp()来创建测试数据,用tearDown()来清理测试产生的临时文件或数据库记录。这样可以保证每个测试方法都在干净的环境中运行。
3. 测试组织与执行
3.1 测试套件(TestSuite)
对于大型项目,我们需要将多个测试用例组织成测试套件:
python复制def suite():
suite = unittest.TestSuite()
suite.addTest(TestStringMethods('test_upper'))
suite.addTest(TestDatabase('test_query'))
return suite
if __name__ == '__main__':
runner = unittest.TextTestRunner()
runner.run(suite())
在实际工作中,我更推荐使用unittest的自动发现功能:
bash复制python -m unittest discover -s tests -p "test_*.py"
这个命令会自动发现tests目录下所有以test_开头的.py文件,并运行其中的测试用例。
3.2 测试覆盖率
测试覆盖率是衡量测试质量的重要指标。可以使用coverage.py工具来测量:
bash复制pip install coverage
coverage run -m unittest discover
coverage report -m
我建议将覆盖率目标设为80%以上,关键模块要达到100%。但要注意,高覆盖率不等于高质量测试,还要看测试用例是否真正验证了各种边界条件。
4. 高级测试技巧
4.1 Mock对象
单元测试应该独立运行,不依赖外部服务。unittest.mock模块提供了Mock类,可以模拟外部依赖:
python复制from unittest.mock import Mock, patch
class TestPayment(unittest.TestCase):
def test_payment_success(self):
payment_gateway = Mock()
payment_gateway.process.return_value = True
result = process_payment(payment_gateway, 100)
self.assertTrue(result)
payment_gateway.process.assert_called_once_with(100)
在实际项目中,我经常用@patch装饰器来临时替换模块中的对象:
python复制@patch('module.ClassName')
def test_mock_class(MockClass):
instance = MockClass.return_value
instance.method.return_value = 'mocked'
assert module.ClassName() is instance
assert module.ClassName().method() == 'mocked'
4.2 参数化测试
unittest本身不支持参数化测试,但可以通过子类化或第三方库实现:
python复制def parameterized_test(params):
def decorator(func):
def wrapper(self):
for param in params:
with self.subTest(**param):
func(self, **param)
return wrapper
return decorator
class TestMath(unittest.TestCase):
@parameterized_test([
{'a': 1, 'b': 2, 'expected': 3},
{'a': 5, 'b': -3, 'expected': 2},
])
def test_add(self, a, b, expected):
self.assertEqual(a + b, expected)
对于复杂的参数化需求,我推荐使用pytest框架,它内置了更强大的参数化功能。
5. 测试最佳实践
5.1 测试命名规范
好的测试名称应该清晰表达测试意图。我遵循以下命名约定:
- 测试类:Test[被测类名]
- 测试方法:test_[场景]_[预期结果]
例如:
python复制class TestUserRepository(unittest.TestCase):
def test_find_by_id_with_invalid_id_raises_exception(self):
with self.assertRaises(ValueError):
UserRepository().find_by_id(None)
5.2 测试隔离性
每个测试应该独立运行,不依赖其他测试的状态。我遇到过因为测试顺序导致的问题,后来通过以下方式解决:
- 使用setUp()创建全新测试环境
- 避免使用共享的类属性存储状态
- 对于数据库测试,每个测试使用独立的事务
5.3 测试性能优化
随着测试套件增长,执行时间可能成为问题。我的优化经验:
- 将慢测试标记为@unittest.skip("需要优化")
- 使用内存数据库替代真实数据库
- 并行运行独立测试(需要第三方工具如pytest-xdist)
6. 常见问题与解决方案
6.1 测试依赖外部服务
解决方案:
- 使用Mock对象替代真实服务
- 建立测试专用的沙箱环境
- 对于不可避免的外部依赖,添加@unittest.skipIf标记
6.2 测试随机失败
如果测试有时通过有时失败,通常是因为:
- 测试依赖未清理的状态 - 确保tearDown()彻底清理
- 竞态条件 - 添加适当的同步机制
- 依赖系统时间 - 使用Mock替换datetime.now()等调用
6.3 测试维护成本高
高维护成本通常意味着测试设计有问题:
- 避免过度Mock - 这会使得测试脆弱
- 测试行为而非实现 - 这样重构时测试不需要频繁修改
- 将复杂测试分解为多个简单测试
7. 测试驱动开发(TDD)实践
虽然unittest可以用于TDD,但我发现结合pytest能获得更好的体验。不过使用unittest进行TDD的基本流程是:
- 编写一个失败的测试
- 实现最简单的代码使测试通过
- 重构代码,确保测试仍然通过
例如,开发一个计算器:
python复制# test_calculator.py
class TestCalculator(unittest.TestCase):
def test_add(self):
self.assertEqual(Calculator().add(2, 3), 5)
# calculator.py
class Calculator:
def add(self, a, b):
return a + b
TDD的关键是保持小步快跑,每个测试只验证一个小功能点。
8. 集成测试与单元测试
虽然本文聚焦单元测试,但需要明确它与集成测试的区别:
- 单元测试:验证单个函数/类的行为,通常使用Mock隔离依赖
- 集成测试:验证多个组件的交互,使用真实依赖
在实际项目中,我通常这样组织测试目录:
code复制tests/
unit/
test_*.py
integration/
test_*.py
使用不同的运行命令来执行不同类型的测试:
bash复制# 只运行单元测试
python -m unittest discover -s tests/unit
# 只运行集成测试
python -m unittest discover -s tests/integration
9. 持续集成中的测试
将单元测试纳入CI/CD流水线是保证代码质量的关键。一个典型的.travis.yml配置示例:
yaml复制language: python
python:
- "3.7"
- "3.8"
- "3.9"
install:
- pip install -r requirements.txt
- pip install coverage
script:
- coverage run -m unittest discover
- coverage report
在团队中,我们设置CI流水线在测试覆盖率低于阈值时失败,这样可以防止代码质量下滑。
10. 测试代码的质量
测试代码本身也需要保持高质量。我遵循以下原则:
- 测试代码应该比生产代码更简单
- 避免测试中的重复代码 - 使用setUp()或工具函数
- 测试应该快速失败 - 尽早发现问题
- 定期审查测试代码 - 就像审查生产代码一样
一个常见的反模式是过度复杂的测试装置。如果发现setUp()方法变得很长,可能需要重新考虑测试设计。
11. 测试与日志
良好的日志能帮助调试测试失败。我通常在setUp()中配置测试专用的日志:
python复制class TestWithLogging(unittest.TestCase):
def setUp(self):
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
self.logger = logging.getLogger(self.__class__.__name__)
def test_with_logging(self):
self.logger.debug("This is a debug message")
# 测试逻辑...
对于失败的测试,unittest会自动打印相关日志,但自定义日志可以提供更多上下文信息。
12. 测试与性能分析
有时我们需要测试性能而不仅仅是功能。虽然unittest不是性能测试框架,但可以结合timeit进行简单测量:
python复制import timeit
class TestPerformance(unittest.TestCase):
def test_algorithm_performance(self):
elapsed = timeit.timeit(
lambda: expensive_operation(),
number=1000
)
self.assertLess(elapsed, 1.0) # 确保1000次操作在1秒内完成
对于复杂的性能测试,我建议使用专门的性能测试工具,如pytest-benchmark。
13. 测试与数据库
测试数据库交互时需要特别注意:
- 使用内存数据库(SQLite)加速测试
- 每个测试使用独立的事务,并在tearDown()中回滚
- 考虑使用工厂模式生成测试数据
一个使用SQLite的示例:
python复制class TestDatabase(unittest.TestCase):
def setUp(self):
self.engine = create_engine('sqlite:///:memory:')
Base.metadata.create_all(self.engine)
self.Session = sessionmaker(bind=self.engine)
self.session = self.Session()
def tearDown(self):
self.session.rollback()
self.session.close()
def test_create_user(self):
user = User(name="test")
self.session.add(user)
self.session.commit()
fetched = self.session.query(User).first()
self.assertEqual(fetched.name, "test")
14. 测试与多线程
测试多线程代码特别具有挑战性。一些建议:
- 尽可能将线程逻辑提取到单独函数中,同步测试该函数
- 使用unittest.mock替换真实的线程操作
- 添加适当的超时机制,防止测试挂起
python复制class TestThreading(unittest.TestCase):
def test_thread_safety(self):
counter = 0
lock = threading.Lock()
def increment():
nonlocal counter
with lock:
counter += 1
threads = [threading.Thread(target=increment) for _ in range(100)]
for t in threads:
t.start()
for t in threads:
t.join()
self.assertEqual(counter, 100)
15. 测试与异步代码
对于asyncio代码,可以使用unittest.IsolatedAsyncioTestCase:
python复制from unittest import IsolatedAsyncioTestCase
class TestAsync(IsolatedAsyncioTestCase):
async def test_async_function(self):
result = await async_function()
self.assertEqual(result, "expected")
在大型异步项目中,我建议使用pytest-asyncio,它提供了更完善的异步测试支持。
16. 测试与第三方API
测试涉及第三方API的代码时:
- 使用Mock替代真实API调用
- 录制真实的API响应用于测试(VCR.py工具)
- 为不可避免的真实调用添加@unittest.skipUnless标记
python复制@patch('requests.get')
def test_api_call(mock_get):
mock_get.return_value.json.return_value = {"key": "value"}
result = call_api()
self.assertEqual(result, "value")
mock_get.assert_called_once_with("https://api.example.com")
17. 测试与用户界面
对于GUI或Web界面测试,unittest可能不够用。可以考虑:
- 将核心逻辑与界面分离,单独测试核心逻辑
- 使用专门的UI测试工具如Selenium
- 使用Page Object模式组织UI测试
虽然这不是unittest的强项,但可以结合使用:
python复制class TestWebUI(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Chrome()
def tearDown(self):
self.driver.quit()
def test_login(self):
self.driver.get("http://example.com/login")
self.driver.find_element(By.ID, "username").send_keys("test")
self.driver.find_element(By.ID, "password").send_keys("pass")
self.driver.find_element(By.ID, "submit").click()
welcome = self.driver.find_element(By.ID, "welcome").text
self.assertIn("Welcome test", welcome)
18. 测试与配置管理
测试可能需要不同的配置。我的做法是:
- 使用环境变量控制测试行为
- 为测试创建专用的配置文件或配置类
- 使用unittest.mock.patch.dict临时修改环境变量
python复制class TestConfig(unittest.TestCase):
@patch.dict('os.environ', {'APP_ENV': 'test'})
def test_config(self):
config = load_config()
self.assertEqual(config.env, "test")
19. 测试与安全
安全测试通常需要特殊考虑:
- 测试敏感数据是否被正确清理
- 验证权限控制是否有效
- 检查日志是否包含敏感信息
python复制class TestSecurity(unittest.TestCase):
def test_password_hashing(self):
password = "secret"
hashed = hash_password(password)
self.assertNotEqual(password, hashed)
self.assertTrue(verify_password(password, hashed))
def test_logs_no_password(self):
with self.assertLogs() as cm:
login("user", "password")
self.assertNotIn("password", "\n".join(cm.output))
20. 测试与遗留代码
处理遗留代码时的测试策略:
- 先为重要功能添加测试,再逐步覆盖其他部分
- 使用依赖注入解耦代码,使其更易测试
- 从集成测试开始,逐步添加单元测试
一个常见技巧是使用"接缝" - 在不改变行为的前提下修改代码使其可测试:
python复制# 原始代码
def process_data():
db = get_db_connection() # 直接获取数据库连接
# 处理逻辑...
# 可测试版本
def process_data(db=None):
db = db or get_db_connection() # 允许注入测试用的db
# 处理逻辑...
# 测试代码
class TestProcessData(unittest.TestCase):
def test_process_data(self):
mock_db = Mock()
process_data(db=mock_db)
mock_db.query.assert_called_once()
21. 测试与性能优化
测试性能优化代码的挑战:
- 确保优化前后功能一致
- 测量优化效果
- 验证边界条件
python复制class TestOptimization(unittest.TestCase):
def test_optimized_equivalent(self):
data = generate_test_data()
original = original_algorithm(data)
optimized = optimized_algorithm(data)
self.assertEqual(original, optimized)
def test_optimization_improvement(self):
data = generate_large_data()
original_time = timeit.timeit(lambda: original_algorithm(data), number=10)
optimized_time = timeit.timeit(lambda: optimized_algorithm(data), number=10)
self.assertLess(optimized_time, original_time)
22. 测试与机器学习
测试机器学习代码的特殊考虑:
- 验证输入数据的预处理
- 检查模型输出的合理范围
- 确保训练过程可复现
python复制class TestML(unittest.TestCase):
def setUp(self):
np.random.seed(42) # 固定随机种子
self.model = train_model()
def test_predict_range(self):
test_data = generate_test_data()
predictions = self.model.predict(test_data)
self.assertTrue(np.all(predictions >= 0))
self.assertTrue(np.all(predictions <= 1))
def test_reproducibility(self):
model1 = train_model()
model2 = train_model()
test_data = generate_test_data()
pred1 = model1.predict(test_data)
pred2 = model2.predict(test_data)
np.testing.assert_array_equal(pred1, pred2)
23. 测试与日期时间
处理日期时间测试的技巧:
- 使用freezegun固定测试时间
- 验证时间计算逻辑
- 检查时区处理
python复制from freezegun import freeze_time
class TestDateTime(unittest.TestCase):
@freeze_time("2023-01-01")
def test_new_years(self):
self.assertEqual(get_current_holiday(), "New Year's Day")
def test_date_math(self):
start = datetime(2023, 1, 1)
end = datetime(2023, 1, 8)
self.assertEqual(calculate_days(start, end), 7)
def test_timezone(self):
utc_time = datetime(2023, 1, 1, tzinfo=timezone.utc)
local_time = convert_to_local(utc_time)
self.assertEqual(local_time.tzinfo.zone, "America/New_York")
24. 测试与文件系统
测试文件操作的策略:
- 使用临时文件和目录
- 使用unittest.mock模拟文件操作
- 验证文件内容和权限
python复制import tempfile
class TestFileOperations(unittest.TestCase):
def setUp(self):
self.temp_dir = tempfile.mkdtemp()
def tearDown(self):
shutil.rmtree(self.temp_dir)
def test_file_creation(self):
test_file = os.path.join(self.temp_dir, "test.txt")
create_file(test_file, "content")
self.assertTrue(os.path.exists(test_file))
with open(test_file) as f:
self.assertEqual(f.read(), "content")
@patch('builtins.open', new_callable=mock_open)
def test_file_write(self, mock_file):
write_to_file("/path/to/file", "data")
mock_file.assert_called_once_with("/path/to/file", "w")
mock_file().write.assert_called_once_with("data")
25. 测试与网络操作
测试网络相关代码的方法:
- 使用Mock替代真实网络请求
- 测试重试和超时逻辑
- 验证错误处理
python复制class TestNetwork(unittest.TestCase):
@patch('requests.get')
def test_retry_logic(self, mock_get):
mock_get.side_effect = [
requests.exceptions.Timeout(),
requests.exceptions.Timeout(),
Mock(status_code=200, json=lambda: {"success": True})
]
result = fetch_with_retries("http://example.com", max_retries=3)
self.assertEqual(result, {"success": True})
self.assertEqual(mock_get.call_count, 3)
def test_timeout(self):
with patch('socket.create_connection') as mock_connect:
mock_connect.side_effect = socket.timeout()
with self.assertRaises(NetworkError):
check_connection("example.com", 80)
26. 测试与随机性
处理随机性的测试方法:
- 固定随机种子确保可重复性
- 测试统计属性而非具体值
- 验证随机分布
python复制class TestRandomness(unittest.TestCase):
def setUp(self):
random.seed(42)
def test_random_consistency(self):
first_run = [random.random() for _ in range(10)]
random.seed(42)
second_run = [random.random() for _ in range(10)]
self.assertEqual(first_run, second_run)
def test_random_distribution(self):
samples = [random.choice([1, 2, 3]) for _ in range(1000)]
counts = {1: 0, 2: 0, 3: 0}
for s in samples:
counts[s] += 1
for count in counts.values():
self.assertAlmostEqual(count / 1000, 1/3, delta=0.05)
27. 测试与并发控制
测试并发代码的要点:
- 验证锁和同步机制
- 测试竞态条件
- 检查死锁可能性
python复制class TestConcurrency(unittest.TestCase):
def test_lock_contention(self):
lock = threading.Lock()
shared = 0
def increment():
nonlocal shared
for _ in range(1000):
with lock:
shared += 1
threads = [threading.Thread(target=increment) for _ in range(10)]
for t in threads:
t.start()
for t in threads:
t.join()
self.assertEqual(shared, 10000)
def test_deadlock(self):
lock1 = threading.Lock()
lock2 = threading.Lock()
def thread1():
with lock1:
time.sleep(0.1)
with lock2:
pass
def thread2():
with lock2:
time.sleep(0.1)
with lock1:
pass
t1 = threading.Thread(target=thread1)
t2 = threading.Thread(target=thread2)
t1.start()
t2.start()
t1.join(timeout=1.0)
t2.join(timeout=1.0)
self.assertFalse(t1.is_alive())
self.assertFalse(t2.is_alive())
28. 测试与缓存
测试缓存行为的策略:
- 验证缓存命中/未命中
- 测试缓存失效逻辑
- 检查缓存一致性
python复制class TestCache(unittest.TestCase):
def setUp(self):
self.cache = create_cache()
def test_cache_hit(self):
self.cache.set("key", "value")
value, hit = self.cache.get("key")
self.assertEqual(value, "value")
self.assertTrue(hit)
def test_cache_miss(self):
value, hit = self.cache.get("nonexistent")
self.assertIsNone(value)
self.assertFalse(hit)
def test_cache_expiration(self):
self.cache.set("key", "value", ttl=0.1)
time.sleep(0.2)
value, hit = self.cache.get("key")
self.assertIsNone(value)
self.assertFalse(hit)
29. 测试与状态机
测试状态机的要点:
- 验证所有状态转换
- 检查非法转换处理
- 测试边界条件
python复制class TestStateMachine(unittest.TestCase):
def test_valid_transitions(self):
sm = StateMachine("start")
sm.transition("middle")
self.assertEqual(sm.state, "middle")
sm.transition("end")
self.assertEqual(sm.state, "end")
def test_invalid_transition(self):
sm = StateMachine("start")
with self.assertRaises(InvalidTransition):
sm.transition("end")
def test_edge_cases(self):
sm = StateMachine("start")
sm.transition("start") # 自循环
self.assertEqual(sm.state, "start")
30. 测试与设计模式
测试设计模式实现的方法:
- 验证模式的核心行为
- 检查扩展点是否按预期工作
- 测试模式组合
python复制class TestObserverPattern(unittest.TestCase):
def test_observer_notification(self):
subject = Subject()
observer = Mock()
subject.attach(observer)
subject.notify("event")
observer.update.assert_called_once_with("event")
def test_multiple_observers(self):
subject = Subject()
obs1 = Mock()
obs2 = Mock()
subject.attach(obs1)
subject.attach(obs2)
subject.notify("event")
obs1.update.assert_called_once_with("event")
obs2.update.assert_called_once_with("event")
class TestFactoryPattern(unittest.TestCase):
def test_product_creation(self):
factory = ConcreteFactory()
product = factory.create_product()
self.assertIsInstance(product, ConcreteProduct)
def test_product_interface(self):
product = ConcreteProduct()
self.assertTrue(hasattr(product, "operation"))
self.assertTrue(callable(product.operation))
31. 测试与算法
测试算法的要点:
- 验证正确性
- 测试边界条件
- 检查性能特征
python复制class TestSorting(unittest.TestCase):
def test_sort_empty(self):
self.assertEqual(quick_sort([]), [])
def test_sort_single(self):
self.assertEqual(quick_sort([1]), [1])
def test_sort_sorted(self):
self.assertEqual(quick_sort([1, 2, 3]), [1, 2, 3])
def test_sort_reversed(self):
self.assertEqual(quick_sort([3, 2, 1]), [1, 2, 3])
def test_sort_random(self):
input = [3, 1, 4, 1, 5, 9, 2, 6]
expected = sorted(input)
self.assertEqual(quick_sort(input), expected)
def test_sort_stability(self):
# 对于稳定排序算法
items = [(1, 'a'), (2, 'b'), (1, 'c')]
sorted_items = stable_sort(items, key=lambda x: x[0])
self.assertEqual(sorted_items[0][1], 'a')
self.assertEqual(sorted_items[1][1], 'c')
32. 测试与数据结构
测试数据结构的策略:
- 验证基本操作
- 测试边界条件
- 检查性能保证
python复制class TestLinkedList(unittest.TestCase):
def setUp(self):
self.list = LinkedList()
def test_empty_list(self):
self.assertEqual(len(self.list), 0)
with self.assertRaises(IndexError):
self.list[0]
def test_append(self):
self.list.append(1)
self.assertEqual(len(self.list), 1)
self.assertEqual(self.list[0], 1)
def test_insert(self):
self.list.append(1)
self.list.append(3)
self.list.insert(1, 2)
self.assertEqual(list(self.list), [1, 2, 3])
def test_delete(self):
self.list.append(1)
self.list.append(2)
self.list.append(3)
del self.list[1]
self.assertEqual(list(self.list), [1, 3])
def test_iteration(self):
items = [1, 2, 3]
for item in items:
self.list.append(item)
self.assertEqual(list(self.list), items)
33. 测试与序列化
测试序列化/反序列化的要点:
- 验证往返一致性
- 测试版本兼容性
- 检查特殊值处理
python复制class TestSerialization(unittest.TestCase):
def test_roundtrip(self):
original = {"a": 1, "b": [2, 3]}
serialized = serialize(original)
deserialized = deserialize(serialized)
self.assertEqual(original, deserialized)
def test_version_compatibility(self):
# 旧版本数据
old_data = b'...'
obj = deserialize(old_data)
self.assertEqual(obj.version, 1)
def test_special_values(self):
special = {
'none': None,
'inf': float('inf'),
'nan': float('nan')
}
serialized = serialize(special)
deserialized = deserialize(serialized)
self.assertIsNone(deserialized['none'])
self.assertEqual(deserialized['inf'], float('inf'))
self.assertTrue(math.isnan(deserialized['nan']))
34. 测试与加密
测试加密代码的注意事项:
- 验证加密/解密往返
- 测试不同密钥和IV
- 检查错误处理
python复制class TestEncryption(unittest.TestCase):
def test_encrypt_decrypt(self):
plaintext = b"sensitive data"
key = os.urandom(32)
iv = os.urandom(16)
ciphertext = encrypt(plaintext, key, iv)
decrypted = decrypt(ciphertext, key, iv)
self.assertEqual(plaintext, decrypted)
def test_wrong_key(self):
plaintext = b"data"
key1 = os.urandom(32)
key2 = os.urandom(32)
iv = os.urandom(16)
ciphertext = encrypt(plaintext, key1, iv)
with self.assertRaises(DecryptionError):
decrypt(ciphertext, key2, iv)
def test_tampered_data(self):
plaintext = b"data"
key = os.urandom(32)
iv = os.urandom(16)
ciphertext = encrypt(plaintext, key, iv)
tampered = ciphertext[:-1] + bytes([ciphertext[-1] ^ 0x01])
with self.assertRaises(DecryptionError):
decrypt(tampered, key, iv)
35. 测试与国际化
测试国际化代码的策略:
- 验证多语言支持
- 测试日期/数字格式化
- 检查文本方向处理
python复制class TestI18N(unittest.TestCase):
def test_translation(self):
translator = Translator('fr')
self.assertEqual(translator.translate("hello"), "bonjour")
def test_date_formatting(self):
dt = datetime(2023, 1, 1)
formatter = DateFormatter('ja')
self.assertEqual(formatter.format(dt), "2023年1月1日")
def test_number_formatting(self):
formatter = NumberFormatter('de')
self.assertEqual(formatter.format(1234.56), "1.234,56")
def test_rtl_support(self):
text = "نص عربي"
detector = TextDirectionDetector()
self.assertEqual(detector.detect(text), "rtl")
36. 测试与可访问性
测试可访问性的要点:
- 验证ARIA属性
- 测试键盘导航
- 检查颜色对比度
python复制class TestAccessibility(unittest.TestCase):
def test_aria_attributes(self):
html = """
<button aria-label="Close">X</button>
"""
parser = AccessibilityParser(html)
button = parser.find_element("button")
self.assertEqual(button.aria_label, "Close")
def test_keyboard_navigation(self):
app = WebApplication()
app.focus_first_element()
app.press_key("Tab")
self.assertEqual(app.current_focus, "second_element")
def test_color_contrast(self):
checker = ColorContrastChecker()
self.assertTrue(checker.verify("#000000", "#ffffff")) # 高对比度
self.assertFalse(checker.verify("#888888", "#999999")) # 低对比度
37. 测试与错误处理
测试错误处理的策略:
- 验证错误检测
- 测试恢复机制
- 检查错误报告
python复制class TestErrorHandling(unittest.TestCase):
def test_error_detection(self):
with self.assertRaises(ValueError):
parse_input("invalid")
def test_graceful_degradation(self):
result = fallible_operation(fallback=True)
self.assertEqual(result, "fallback")
def test_error_logging(self):
with self.assertLogs(level='ERROR') as cm:
handle_error("test error")
self.assertIn("test error", "\n".join(cm.output))
def test_retry_mechanism(self):
attempts = 0
def failing_operation():
nonlocal attempts
attempts += 1
if attempts < 3:
raise RuntimeError("Temporary failure")
return "success"
result = with_retry(failing_operation, max_retries=3)
self.assertEqual(result, "success")
self.assertEqual(attempts, 3)
38. 测试与性能监控
测试性能监控代码的方法:
- 验证指标收集
- 测试警报触发
- 检查数据聚合
python复制class TestMonitoring(unittest.TestCase):
def test_metric_collection(self):
collector = MetricCollector()
collector.record("latency", 100)
collector.record("latency", 200)
stats = collector.get_stats("latency")
self.assertEqual(stats["count"], 2)
self.assertEqual(stats["avg"], 150)
def test_alert_trigger(self):
monitor = PerformanceMonitor(threshold=100)
monitor.check(90) # 低于阈值
self.assertFalse(monitor.triggered)
monitor.check(110) # 超过阈值
self.assertTrue(monitor.triggered)
def test_data_aggregation(self):
aggregator = DataAggregator(window_size=3)
aggregator.add(1)
aggregator.add(2)
aggregator.add(3)
self.assertEqual(aggregator.average(), 2)
aggregator.add(4) # 滑动窗口,移除第一个值
self.assertEqual(aggregator.average(), 3)
39. 测试与配置验证
测试配置验证的逻辑:
- 验证有效配置
- 测试无效配置检测
- 检查默认值应用
python复制class TestConfigValidation(unittest.TestCase):
def test_valid_config(self):
config = {
"host": "example.com",
"port": 8080,
"timeout": 30
}
validator = ConfigValidator()
self.assertTrue(validator.validate(config))
def test_invalid_config(self):
config = {
"host": "example.com",
"port": 99999, # 无效端口
"