当生产function可以有数百万个测试用例时,TDD如何工作?

在TDD中,您选择一个测试用例并实现该测试用例然后您编写足够的生产代码以便测试通过,重构代码并再次选择一个新的测试用例并继续循环。

我在这个过程中遇到的问题是TDD说你只编写了足够的代码来传递刚才写的测试。 我所指的确切地说,如果一个方法可以有100万个测试用例,你能做什么?! 显然没有写100万个测试用例?!

让我通过下面的例子更清楚地解释一下我的意思:

internal static List GetPrimeFactors(ulong number) { var result = new List(); while (number % 2 == 0) { result.Add(2); number = number / 2; } var divisor = 3; while (divisor <= number) { if (number % divisor == 0) { result.Add(divisor); number = number / divisor; } else { divisor += 2; } } return result; } 

上面的代码返回给定数字的所有素数因子。 ulong有64位,这意味着它可以接受0到18,446,744,073,709,551,615之间的值!

那么,当生产function可以有数百万个测试用例时,TDD如何工作?!

我的意思是有多少测试用例可以编写,所以我可以说我使用TDD来实现这个生产代码?

TDD中的这个概念说你应该只编写足够的代码来通过测试,这对我来说似乎是错误的,如上面的例子所示?

什么时候足够了?

我自己的想法是,我只挑选一些测试用例,例如高频段,低频段和更多例如5个测试用例,但这不是TDD,是吗?

非常感谢您对此示例的TDD想法。

这是一个有趣的问题,与认识论中的可证伪性概念有关。 通过unit testing,您并不是真的想certificate系统有效; 你正在构建实验,如果它们失败,将certificate系统不能以符合你的期望/信念的方式工作。 如果您的测试通过,您不知道您的系统是否正常工作,因为您可能忘记了一些未经测试的边缘情况; 你知道的是,截至目前,你没有理由相信你的系统有问题。

科学史上的经典例子是“所有天鹅都是白色的”这个问题。 无论你发现多少只不同的白天鹅,你都不能说假设“所有天鹅都是白色的”是正确的。 另一方面,带给我一只黑天鹅,我知道这个假设是不正确的。

沿着这些方向进行了良好的TDDunit testing; 如果它通过,它不会告诉你一切都是正确的,但如果它失败了,它会告诉你你的假设不正确。 在那个框架中,对每个数字的测试都没有那么有价值:一个案例就足够了,因为如果它不适用于那个案例,你就会知道出了什么问题。

虽然这个问题很有意思,但对于天鹅而言,你不能真正枚举世界上每一只天鹅,以及他们未来的所有孩子和他们的父母,你可以枚举每一个整数,这是一个有限的集合,并validation每种可能的情况。 此外,一个程序在很多方面更接近数学而不是物理,在某些情况下,你也可以真正validation一个陈述是否正确 – 但在我看来,这种类型的validation不是TDD所追求的。 TDD正在进行良好的实验,旨在捕捉可能的失败案例,而不是certificate某些事情是真的。

你忘记了第三步:

  1. 红色
  2. 绿色
  3. 重构

编写测试用例会让你变红。

编写足够的代码来使这些测试用例通过可以让你变成绿色。

将代码推广到不仅仅是你编写的测试用例,而不是破坏它们中的任何一个,都是重构。

您似乎将TDD视为黑盒测试 。 不是。 如果是黑盒测试,只有完整的(数百万个测试用例)测试集会满足你,因为任何给定的情况都可能未经测试,因此黑盒子中的恶魔将能够逃脱作弊。

但是你的代码中的黑盒子里不是恶魔。 这是你,在一个白色的盒子里。 你知道你是否在作弊。 Fake It Til You Make的做法与TDD密切相关,有时与它混淆。 是的,你编写虚假的实现来满足早期的测试用例 – 但你知道你在假装它。 而且你也知道你何时停止伪装它。 你知道什么时候你有一个真正的实现,并且你已经通过渐进式迭代和测试驱动来实现。

所以你的问题确实是错误的。 对于TDD,您需要编写足够的测试用例来推动解决方案的完成和正确性; 您不需要为每个可能的输入集合设置测试用例。

从我的POV开始, 重构步骤似乎没有发生在这段代码上……

在我的书中,TDD并不意味着为每个可能的输入/输出参数的每个可能的排列编写测试用例……

但是要编写所有必需的测试用例,以确保它完成指定的操作,即对于这样的方法,所有边界情况加上一个测试,该测试从包含已知正确结果的数字的列表中随机选取一个数字。 如果需要,您可以随时扩展此列表以使测试更加彻底……

如果你不把常识抛到窗外,TDD只适用于现实世界……

至于

只写足够的代码来通过测试

在TDD中,这指的是“非作弊程序员”……如果你有一个或多个“作弊程序员”,例如只是将测试用例的“正确结果”硬编码到方法中,我怀疑你的问题就更大了手比TDD ……

BTW“Testcase构建”是你练习得越多越好的东西 – 没有书籍/指南可以告诉你哪些测试用户最适合任何给定的情况……在构建测试用例时体验会有很大的回报。 ..

如果您愿意,TDD确实允许您使用常识。 没有必要定义你的TDD版本是愚蠢的,只是为了你可以说“我们没有做TDD,我们做的事情不那么愚蠢”。

您可以编写一个测试用例,多次调用被测函数,传入不同的参数。 这可以防止“将代码分解为1”,“将代码分解为2”,“将代码分解为3代码”作为单独的开发任务。

要测试多少个不同的值实际上取决于您运行测试所需的时间。 你想测试任何可能是一个极端情况的东西(所以在分解的情况下至少有0,1,2,3, LONG_MAX+1因为它有最多的因素,无论哪个值有最独特的因素,Carmichael数,和一些具有不同数量的素数因子的完美正方形)加上尽可能大的一系列值,希望覆盖一些你没有意识到的东西是一个极端情况,但是。 这可能意味着编写测试,然后编写函数,然后根据其观察到的性能调整范围的大小。

您还可以阅读函数规范,并实现该函数,就好像测试的值多于实际值。 这并不违反“仅实施经过测试的内容”,它只是承认在发货日期之前没有足够的时间来运行所有2 ^ 64个可能的输入,因此实际测试是“逻辑”测试的代表性样本如果你有时间,你会跑。 您仍然可以编码您想要测试的内容,而不是您实际有时间测试的内容。

您甚至可以测试随机选择的输入(通常作为安全分析师的“模糊测试”的一部分),如果您发现您的程序员(即您自己)被确定为反常,并继续编写解决测试输入的代码,并且不其他。 显然,随机测试的可重复性存在问题,因此使用PRNG并记录种子。 为了防止作弊,你会看到与竞赛节目,在线评判程序等类似的事情。 程序员并不确切知道将测试哪些输入,因此必须尝试编写解决所有可能输入的代码。 由于你无法保守自己的秘密,随机输入可以完成同样的工作。 在现实生活中,使用TDD的程序员不会故意作弊,但可能会因为同一个人编写测试和代码而意外作弊。 有趣的是,测试然后错过了代码所做的同样困难的角落案例。

对于采用字符串输入的函数,问题更加明显,有超过2^64可能的测试值。 选择最好的,也就是说程序员最容易出错,最多只是一门不精确的科学。

你也可以让测试人员作弊,超越TDD。 首先编写测试,然后编写代码以通过测试,然后返回并编写更多的白盒测试,(a)包含看起来像实际编写的实现中的边缘情况的值; (b)包含足够的值以获得100%的代码覆盖率,无论您有什么代码覆盖率指标,您都有时间和意愿。 该过程的TDD部分仍然有用,它有助于编写代码,但随后您进行迭代。 如果这些新测试中的任何一个失败,你可以称之为“添加新的要求”,在这种情况下,我认为你所做的仍然是纯粹的TDD。 但这只是一个你所谓的问题,实际上你并没有增加新的要求,而是比编写代码之前更彻底地测试原始需求。

当你编写测试时,你应该采取有意义的案例,而不是每个案例。 有意义的案例包括一般案件,案件……

你只是不能为每一个案例编写一个测试(否则你只需将这些值放在一个表上并回答它们,这样你就可以100%确定你的程序能够正常工作:P)。

希望有所帮助。

这是你为任何测试得到的第一个问题。 TDD在这里并不重要。

是的,有很多很多案件; 此外,如果您开始构建系统,则有案例的组合和组合。 它确实会引导你进行组合爆炸。

怎么办这个问题很好。 通常,您选择等效类 ,您的算法可能会相同 – 并为每个类测试一个值。

下一步是测试边界条件(记住,CS中两个最常见的错误是一个错误)。

接下来……好吧,出于各种实际原因,可以在这里停下来。 不过,请看一下这些讲义: http : //www.scs.stanford.edu/11au-cs240h/notes/testing.html

PS。 顺便说一句,使用TDD“按书”来解决数学问题并不是一个好主意。 Kent Beck在他的TDD书中certificate,实现计算Fibonacci数的函数的最差可能实现。 如果您知道一个封闭的表格 – 或者有一篇文章描述了经过validation的算法,那么只需按照上述说明进行健全性检查,并且不要在整个重构周期中进行TDD – 这样可以节省您的时间。

PPS。 实际上,有一篇很好的文章 (惊喜!)提到了Fibonacci问题以及你对TDD的问题。

没有数百万的测试用例。 只有一点。 您可能想尝试使用PEX ,它可以让您找到算法中不同的实际测试用例。 当然,你只需要测试那些。

我从未做过任何TDD,但你所询问的不是关于TDD:它是关于如何编写一个好的测试套件。

我喜欢设计每个代码可以处于的所有状态的模型(在纸上或在我的脑中)。我认为每一行都好像它是状态机的一部分。 对于每一行,我确定可以进行的所有转换(执行下一行,分支或不分支,抛出exception,溢出表达式中的任何子计算等)。

从那里我有一个测试用例的基本矩阵。 然后我确定每个状态转换的每个边界条件,以及每个边界之间的任何有趣的中点。 然后我得到了我的测试用例的变化。

从这里开始,我尝试提出有趣且不同的流程或逻辑组合 – “这个if语句,加上那个 – 列表中有多个项目”,等等。

由于代码是一个流,你通常不能在中间中断它,除非为一个不相关的类插入一个模拟是有意义的。 在那些情况下,我经常减少我的矩阵,因为有些条件你无法击中,或者因为被另一条逻辑掩盖而变化变得不那么有趣。

在那之后,我已经厌倦了这一天,并回家:)我可能有大约10-20个测试用例,每个良好的因素和相当短的方法,或每个算法/类50-100。 不是10,000,000。

我可能想出了太多无趣的测试用例,但至少我通常会超越而不是进行测试。 我通过尝试很好地考虑我的测试用例以避免代码重复来缓解这种情况。

关键部分:

  • 至少在脑海中为您的算法/对象/代码建模。 您的代码更像是树而不是脚本
  • 彻底确定该模型中的所有状态转换(可以独立执行的每个操作,以及每个要评估的表达式的每个部分)
  • 利用边界测试,您不必提出无限的变化
  • 你可以嘲笑

不,你不必写FSM图纸,除非你做这种事情很有趣。 我不 :)

你通常做什么,它测试“测试边界条件”,以及一些随机条件。

例如:ulong.min,ulong.max和一些值。 你为什么要制作一个GetPrimeFactors? 你喜欢一般地计算它们,还是你想做那些特定的事情? 测试你为什么要这样做。

你也可以做什么断言result.Count,而不是所有单独的项目。 如果你知道你想要获得多少项,以及一些特定情况,你仍然可以重构你的代码,如果这些情况和总数相同,则假设该function仍然有效。

如果你真的想测试那么多,你也可以看看白盒测试。 例如Pex和Moles非常好。

TDD不是检查函数/程序是否可以在每个输入排列上正确工作的方法。 我对此的看法是,我编写特定测试用例的概率与我在这种情况下我的代码是否正确的不确定性成正比。

这基本上意味着我在两种情况下编写测试:1)我编写的一些代码是复杂的或复杂的和/或有太多的假设和2)生产中发生的错误。

一旦了解了导致错误的原因,通常很容易在测试用例中进行编码。 从长远来看,这样做会产生一个强大的测试套件。