基于PRNG的多输入测试:O(n)与O(nlog(n))算法验证疑问
这种用“可信慢算法”校验“优化快算法”的随机测试思路非常棒,我在实际工作中也经常用这种方式验证高性能实现的正确性。针对你的问题,我来分享一下具体的实践方案:
核心结论
当实验失败时,不需要提前手动为每个失败案例创建单元测试,但必须建立自动捕获、记录并固化失败案例的机制——这既能帮你快速定位问题,又能防止后续代码修改时出现回归。
具体操作方案
1. 先保证失败案例可复现
随机测试最大的痛点是“复现难”,所以当断言失败时,你要第一时间把导致失败的关键信息记录下来:
- 当前的随机种子(虽然你用了固定种子,但实验运行到第几次失败也很关键,或者直接保存生成的数组本身)
- 失败的输入数组内容
- 慢算法给出的预期结果和快算法的实际输出
修改你的测试代码,加入异常捕获来输出这些信息:
public static int MySeed = 1000000007; public static Random random; [TestMethod()] public void TestSeveralInputSizes() { random = new Random(MySeed); int numberOfExperiments = (int)1e4; int maxArraySize = (int)1e2; while (numberOfExperiments-- > 0) { var array = GenerateRandomArray(random.Next(maxArraySize)); long expectedAnswer = SlowSolution(array); long myAnswer = FastSolution(array); try { Assert.AreEqual(expectedAnswer, myAnswer); } catch (AssertFailedException ex) { // 打印所有关键信息,方便你复现问题 Console.WriteLine($"❌ Failed experiment!"); Console.WriteLine($"Seed used: {MySeed}"); Console.WriteLine($"Experiment number (remaining): {numberOfExperiments + 1}"); Console.WriteLine($"Input array: [{string.Join(", ", array)}]"); Console.WriteLine($"Expected result: {expectedAnswer} | Actual result: {myAnswer}"); // 可选:直接生成可复用的单元测试代码片段 Console.WriteLine("\n📋 Copy this test case to your test class:"); string testId = Guid.NewGuid().ToString().Substring(0, 8); Console.WriteLine($"[TestMethod()]"); Console.WriteLine($"public void Test_Failure_{testId}() {{"); Console.WriteLine($" var testArray = new long[] {{ {string.Join(", ", array)} }};"); Console.WriteLine($" long expected = {expectedAnswer};"); Console.WriteLine($" Assert.AreEqual(expected, FastSolution(testArray));"); Console.WriteLine($"}}"); // 不要吞掉异常,让测试框架标记这个测试为失败 throw; } } }
2. 自动固化失败案例为永久单元测试
一旦捕获到失败案例,你可以选择:
- 手动复制上面生成的代码片段,创建一个新的单元测试方法(最简单直接)
- 进阶方案:写一个小工具类,在捕获异常时自动将测试用例写入项目的测试文件中(比如用文件IO操作,把生成的代码追加到测试类的.cs文件末尾)
这么做的好处是:
- 后续每次运行测试时,这个失败案例都会被自动执行,确保你修复问题后不会再出现回归
- 其他开发者也能直接看到这个边界案例,理解算法需要处理的特殊情况
3. 保留原有的随机测试作为“模糊测试”
不要因为有了单独的失败测试就删掉原来的随机循环测试——它的作用是持续发现新的边界案例。你可以把它标记为[TestCategory("FuzzTest")],方便在日常构建中选择性运行(比如每天跑一次全量模糊测试,而每次提交只跑固定的失败案例测试)。
补充建议
- 如果你担心随机测试运行时间太长,可以分阶段调整:开发阶段用较小的
numberOfExperiments(比如100次),CI构建时用全量的1e4次 - 可以给
GenerateRandomArray加入更多参数,比如生成包含负数、重复值、空数组、单元素数组等特殊场景的输入,覆盖更多边界情况
内容的提问来源于stack exchange,提问作者Raudel Ravelo
相关产品推荐
相关产品推荐

