执行时间(性能)装饰器的单元测试

Unit tests for execution time (perfomance) decorator

提问人:zaelcovsky 提问时间:10/4/2023 更新时间:10/4/2023 访问量:38

问:

我有一个执行时间(性能)装饰器,它计算给定函数的平均执行时间并使用值打印结果:float

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

如何测试此装饰器是否正确计算平均时间? 例如,让我们检查一下这个简单的函数:

@benchmark_decorator
def func(seconds):
    sleep(seconds)

func(1)

我们有这样的输出:

>run 1 took 1.0005083000287414 seconds
>run 2 took 1.0000609999988228 seconds
>run 3 took 1.000284199952148 seconds
Took 1.0002844999932374 seconds on average

所以我每次得到的结果都略有不同,不能只用一个数字来断言这个结果。 此外,我不能只用 example,因为函数可以非常快,我们将得到非常低的时间值:.3f

@benchmark_decorator
def func():
    pass

func()

给出以下输出:

>run 1 took 4.400033503770828e-06 seconds
>run 2 took 1.500011421740055e-06 seconds
>run 3 took 5.00003807246685e-07 seconds
Took 2.133349577585856e-06 seconds on average

如果我们用这个值四舍五入,我们将得到零。 那么如何正确地测试这个略有不同的时间呢?.3f

python 单元测试 日期时间 时间 浮点

评论


答:

1赞 Tzane 10/4/2023 #1

如果您使用的是 ,则可以使用 capsys 夹具捕获 stdout,从中解析平均结果,并在测试中执行舍入,如下所示pytest

import pytest

@benchmark_decorator
def wrap_sleep(seconds):
    sleep(seconds)

def test_benchmark_decorator(capsys):
    wrap_sleep(1)
    captured = capsys.readouterr()
    average = float(captured.out.rsplit(" ", 4)[1])
    # Rounding
    assert average == pytest.approx(1, abs=1e-3)
    # Relative 2% error
    assert average == pytest.approx(1, rel=0.02)

评论

0赞 zaelcovsky 10/4/2023
感谢您的回复,但就像我上面提到的 - 四舍五入不是好的做法,因为我们可以得到非常不同的时间值(取决于函数),无论是非常小的还是非常大的,我们不能在任何地方使用。abs=1e-3
1赞 Tzane 10/4/2023
如果你想检查你用这个装饰器测试的每个函数的性能,我认为你需要为每个测试单独参数化预期的性能。如果舍入不适用于您的用例,您也可以使用相对近似值。pytest.approx