如何测试执行时间(性能)装饰器的平均时间计算正确性?
如何测试带微小差异的执行时间装饰器
问题背景
我实现了一个性能装饰器benchmark_decorator,用于计算函数的平均执行时间:
from time import perf_counter, sleep def benchmark_decorator(func): def wrapper(*args, **kwargs): results = list() n_repeats = 3 for i in range(n_repeats): time_start = perf_counter() result = func(*args, **kwargs) time_end = perf_counter() time_duration = time_end - time_start results.append(time_duration) print(f'>run {i+1} took {time_duration} seconds') avg_duration = sum(results) / n_repeats print(f'Took {avg_duration} seconds on average') return result return wrapper
测试时遇到两个核心问题:
- 调用
sleep(1)这类函数时,每次执行时间有微小差异,无法直接断言固定值; - 测试空函数这类极快的函数时,取整后时间为0,无法有效验证计算逻辑。
解决方案
1. 用Mock固定时间源,精确验证计算逻辑
通过Mock替换perf_counter的返回值,让每次计时结果完全可控,彻底避免系统调度带来的误差,直接断言平均时间的计算是否正确。
示例测试代码:
from unittest.mock import patch import unittest # 可修改装饰器返回平均时间,方便测试 def benchmark_decorator(func): def wrapper(*args, **kwargs): results = list() n_repeats = 3 for i in range(n_repeats): time_start = perf_counter() result = func(*args, **kwargs) time_end = perf_counter() time_duration = time_end - time_start results.append(time_duration) print(f'>run {i+1} took {time_duration} seconds') avg_duration = sum(results) / n_repeats print(f'Took {avg_duration} seconds on average') # 新增返回平均时间,便于测试断言 return result, avg_duration return wrapper class TestBenchmark(unittest.TestCase): def test_average_calculation(self): # 预设perf_counter的返回序列:每次计时开始/结束时间依次为0→1,2→3,4→5 # 三次运行耗时分别为1、1、1,平均应为1.0 with patch('time.perf_counter') as mock_perf: mock_perf.side_effect = [0, 1, 2, 3, 4, 5] @benchmark_decorator def test_func(): pass _, avg_time = test_func() self.assertEqual(avg_time, 1.0)
2. 允许合理误差范围,断言真实耗时函数
对于sleep这类有固定预期耗时的函数,不用断言精确值,而是用assertAlmostEqual指定允许的误差范围(比如±0.01秒),覆盖系统调度的微小波动。
示例:
def test_sleep_accuracy(self): @benchmark_decorator def test_func(seconds): sleep(seconds) _, avg_time = test_func(1) # 断言平均时间在0.99~1.01之间 self.assertAlmostEqual(avg_time, 1.0, delta=0.01)
3. 针对极快函数:断言阈值+验证执行次数
对于空函数这类几乎不耗时的函数,不用纠结具体数值,而是:
- 断言平均时间小于一个极小的阈值(比如1e-5秒);
- 验证装饰器确实重复执行了指定次数(比如3次)。
示例:
def test_fast_function(self): @benchmark_decorator def test_func(): pass _, avg_time = test_func() # 断言平均时间足够小 self.assertLess(avg_time, 1e-5) # 验证函数被调用3次 with patch('__main__.test_func') as mock_func: benchmark_decorator(mock_func)() self.assertEqual(mock_func.call_count, 3)
补充:如果不想修改原装饰器
如果不能修改原装饰器的返回值,可以捕获标准输出(stdout),解析打印的平均时间字符串后再断言,但这种方法不如让装饰器直接返回平均时间可靠。
内容的提问来源于stack exchange,提问作者zaelcovsky
相关产品推荐
相关产品推荐

