我们有几种不同的优化算法,可以为每次运行产生不同的结果。例如,优化的目标可以是找到函数的最小值,其中0是全局最小值。优化运行返回如下数据:
[0.1, 0.1321, 0.0921, 0.012, 0.4]
这与全球最小值非常接近,所以这没关系。我们的第一种方法是选择一个阈值,如果结果发生得太高,让单元测试失败。不幸的是,这根本不起作用:结果似乎有一个高斯分布,因此,虽然不太可能,但即使算法仍然很好而且我们运气不好,测试也会不时发生。
那么,我该如何正确测试呢?我想这里需要相当多的统计数据。同样重要的是测试仍然很快,只需让测试运行几百次,然后取平均值就会太慢。
以下是一些进一步的澄清:
例如,我有一个算法可以将Circle拟合成一组点。它非常快,但并不总能产生相同的结果。我想写一个单元测试,以保证在大多数情况下它足够好。
不幸的是我无法为随机数生成器选择固定种子,因为我不想测试算法是否产生与以前完全相同的结果,但我想测试类似“有90%确定性我获得0.1或更高的结果“。
答案 0 :(得分:15)
听起来你的优化器需要两种测试:
由于算法涉及随机化,(1)难以进行单元测试。任何随机过程的测试都会在某些时间内失败。您需要了解一些统计信息才能了解它应该失败的频率。有很多方法可以在测试的严格程度和失败的频率之间进行权衡。
但是有一些方法可以为(2)编写单元测试。例如,您可以在运行单元测试之前将种子重置为特定值。然后输出是确定性的。这不会让你评估算法的平均有效性,但那是(1)。这样的测试可以作为一个旅程线:如果有人在维护期间将错误引入代码中,确定性单元测试可能会捕获该错误。
可能还有其他东西可以进行单元测试。例如,无论随机部分发生什么,也许您的算法可以保证在一定范围内返回值。也许某些价值应该总是积极的等等。
更新:我在“美丽测试”一书中写了一篇关于这个问题的章节。见第10章:Testing a Random Number Generator。
答案 1 :(得分:7)
单元测试不应该具有未知的通过/未通过状态。如果你的算法在多次使用相同的输入运行时返回不同的值,你可能在你的算法中做了一些棘手的事情。
我会采用5种优化算法中的每一种并测试它们以确保给定一组输入x,每次都会得到y的优化值。
编辑:要处理系统的随机组件,您可以引入传递种子以供使用的随机数生成器,也可以使用模拟库(ala RhinoMocks)当RNG被要求提供随机数时,强制它使用特定的数字。
答案 2 :(得分:7)
您的算法可能有一个随机组件。把它控制住。
你可以
第二个选项可能是最好的,因为这样可以更容易地推断出算法的正确结果。
当单元测试算法时,您要验证的是您已正确实施该算法。不是算法是否完成它应该做的事情。单元测试不应将待测代码视为黑盒子。
您可能希望有一个单独的“性能”测试来比较不同算法的执行方式(以及它们是否真正有效),但您的单元测试实际上是用于测试算法的实现
例如,在实现Foo-Bar-Baz优化算法(TM)时,您可能不小心写了x:= x / 2而不是x:= x / 3。这可能意味着算法工作得更慢,但仍然找到相同的算法。您需要进行白盒测试才能找到这样的错误。
修改强>
不幸的是我不能为随机数生成器选择固定种子,因为我不想测试算法是否产生与以前完全相同的结果,但我想测试类似“有90%确定性我得到结果” 0.1或更好的“。
我看不出任何方法可以进行自动验证和随机验证。特别是如果你想有机会将真实误差与统计噪声区分开来。
如果你想测试“有90%的确定性,我会得到0.1或更高的结果”,我建议如下:
double expectedResult = ...;
double resultMargin = 0.1;
int successes = 0;
for(int i=0;i<100;i++){
int randomSeed = i;
double result = optimizer.Optimize(randomSeed);
if(Math.Abs(result, expectedResult)<resultMargin)
successes++;
}
Assert.GreaterThan(90, successes);
(请注意,此测试具有确定性。)
答案 3 :(得分:5)
让测试运行,如果其中任何一个失败,重新运行这些测试 50次并查看它们失败的时间比例。 (当然是以自动方式。)
答案 4 :(得分:1)
我建议,不要让你的测试针对产生高斯分布的代码运行,而是创建一个蒙特卡洛类型算法,该算法多次运行该方法,然后测试结果的总体分布使用适当的分布模型。例如,如果它是平均值,则 将能够针对确定的阈值进行测试。如果它更复杂,您需要创建对适当分布进行建模的代码(例如,值&lt; x构成我结果的y%)。
请记住,您没有测试数字生成器,您正在测试生成值的单位!
答案 5 :(得分:1)
感谢所有答案,我现在正在这样做:
这样一旦测试看起来会失败,就会经常重新计算,直到确定它确实失败为止。
这似乎有效,但我不太满意,因为我只测试中位数结果。
答案 6 :(得分:0)
jUnit和NUnit都可以使用容差/ delta值来断言浮点数据类型。即你测试输出是否正确值给出或取一些小数。在您的情况下,您要检查的正确值为0,如果您希望给定输出中的所有值都通过(或0.20,公差为+/- 0.20),则公差为0.5。
由于结果的随机性,您可能需要对算法的各个部分进行单元测试,以确保它确实能够实现预期的效果。