如何在c#中对性能优化进行单元测试?

本文关键字:优化 单元测试 性能 | 更新日期: 2023-09-27 18:01:49

我正在构建的一些搜索代码中使用Levenshtein算法的优化版本。我有功能单元测试来验证算法是否返回正确的结果,但是在这种情况下,算法的性能也非常重要。

我希望在项目中添加一些测试覆盖,以便如果任何未来的修改影响优化,它们将显示为失败的测试-因为算法是确定性的,并且针对已知的测试数据运行,这可以像计算给定测试输入集执行的指令数量一样详细。换句话说,我不是想用计时器来衡量算法的性能——我感兴趣的是实际测试算法的内部行为,而不仅仅是输出。

我该如何在c#/中实现这个呢?净4 ?

编辑:我不想使用挂钟时间的原因是它会随着CPU负载和测试控制之外的其他因素而变化。例如,这可能导致在构建服务器处于负载状态时测试失败。作为部署系统的一部分进行挂钟监控。

编辑2:这样想……当性能是一个关键需求时,您将如何应用red->green->重构?

如何在c#中对性能优化进行单元测试?

我来回答你问题的第三部分,因为我已经成功地做过几次了。

如何应用红->绿->重构当性能是一个关键的要求吗?

  1. 为您计划更改的内容和其他可能因更改而减慢速度的方法编写固定测试以捕获回归。
  2. 编写一个失败的性能测试。
  3. 进行性能改进,经常运行所有测试。
  4. 更新固定测试以更紧密地固定性能。

写pin测试

创建一个像这样的辅助方法来计时你想要固定的时间。

private TimeSpan Time(Action toTime)
{
    var timer = Stopwatch.StartNew();
    toTime();
    timer.Stop();
    return timer.Elapsed;
}

然后编写一个测试,断言您的方法不需要花费时间:

[Test]
public void FooPerformance_Pin()
{
    Assert.That(Time(()=>fooer.Foo()), Is.LessThanOrEqualTo(TimeSpan.FromSeconds(0));
}

当它失败时(在失败消息中使用实际经过的时间),使用比实际时间稍长的时间更新时间。重新运行,它就会通过。对于其他可能因更改而影响其性能的函数,重复此步骤,最终得到如下所示的结果:

[Test]
public void FooPerformance_Pin()
{
    Assert.That(Time(()=>fooer.Foo()), Is.LessThanOrEqualTo(TimeSpan.FromSeconds(0.8));
}
[Test]
public void BarPerformance_Pin()
{
    Assert.That(Time(()=>fooer.Bar()), Is.LessThanOrEqualTo(TimeSpan.FromSeconds(6));
}

写一个失败的性能测试

我喜欢把这种测试称为"诱饵测试"。这只是固定测试的第一步。

[Test]
public void FooPerformance_Bait()
{
    Assert.That(Time(()=>fooer.Foo()), Is.LessThanOrEqualTo(TimeSpan.FromSeconds(0));
}

现在,努力改进性能。每次试探性改进后,运行所有的测试(固定和诱饵)。如果成功,您将看到诱饵测试的失败输出中的时间下降,并且您的固定测试都不会失败。

当您对改进感到满意时,为您更改的代码更新固定测试,并删除诱饵测试。

你现在怎么处理这些测试?

最不需要担心的事情是用Explicit属性标记这些测试,并在下次想要检查性能时保留它们。

在工作范围的另一边,在CI中为运行这些类型的测试创建一个合理的良好控制的子系统是监视性能回归的真正好方法。根据我的经验,比起真正的失败,我们更担心它们"因为CPU负载而随机失败"。这种努力的成功更多地取决于团队文化,而不是您控制环境的能力。