You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何测试执行时间(性能)装饰器的平均时间计算正确性?

如何测试带微小差异的执行时间装饰器

问题背景

我实现了一个性能装饰器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

测试时遇到两个核心问题:

  1. 调用sleep(1)这类函数时,每次执行时间有微小差异,无法直接断言固定值;
  2. 测试空函数这类极快的函数时,取整后时间为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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 09:02:05