TDD是否真的适用于复杂项目?


53

我问这个问题,关于我在TDD项目期间遇到的问题。在创建单元测试时,我注意到了以下挑战。

  • 生成和维护模拟数据

维护大型模拟数据既困难又不切实际。当数据库结构发生变化时,这甚至变得更加困难。

  • 测试GUI

即使具有MVVM和测试GUI的能力,也需要大量代码来重现GUI场景。

  • 测试业务

我的经验是,如果将TDD限于简单的业务逻辑,则它会很好地工作。但是,由于测试组合(测试空间)的数量非常大,因此很难测试复杂的业务逻辑。

  • 需求矛盾

实际上,很难捕获正在分析和设计中的所有需求。由于项目很复杂,很多情况下一个便笺的要求会导致矛盾。在实施阶段的后期发现了矛盾。TDD要求要求100%正确。在这种情况下,可以期望在创建测试期间会捕获到相互矛盾的需求。但是问题在于,在复杂的场景中情况并非如此。

我已经读过这个问题:TDD为什么起作用?

TDD是否真正适用于复杂的企业项目,或者实际上是对项目类型的限制?


+1阅读完该问题后,我遇到了相同的问题-我在有限的意义上将其与模拟数据的同一问题一起使用。
Michael

20
“ TDD要求要求的要求是100%正确的”,其中“要求”表示“我需要知道这种单一方法必须如何工作”。而且,如果您不知道该方法的工作原理,那么应该如何实现呢?
Frank Shearar 2011年

@FrankShearar:您知道方法应如何在预期的输入上工作。假设strcmp必须使用2个指针,它们中的任何一个都不为nullptr且都有效。当您输入错误的指针时,您不知道会发生什么。也许在某些体系结构上您可以捕获AV并做一些理智的事情,但是您无法想象这种情况是可能的,因此您的测试并未涵盖它。
编码员

7
我会说TDD是唯一适用于大型项目的东西!项目越大,交互越复杂,随机更改的需求就越多-只有TDD才能跟上发展
Martin Beckett 2012年

2
实际上,就需求变更而言,TDD的优点在于,当需求变更时,您可以针对该需求编写新的测试,并确定它不会破坏项目的其余部分。如果尚未编写测试,则还必须编写测试以确保所做的更改不会破坏其他任何内容。此外,我喜欢它的错误修复。即使您没有使用TDD开发所有内容,也可以将其用于错误修复:编写一个可重现该错误的测试,然后修复该错误并再次运行测试。
Jordan Reiter

Answers:


53

维护大型模拟数据既困难又不切实际。当数据库结构发生变化时,这甚至变得更加困难。

假。

单元测试不需要“大型”模拟数据。它需要足够的模拟数据来测试场景,仅此而已。

另外,真正懒惰的程序员要求主题专家为各种测试用例创建简单的电子表格。只是一个简单的电子表格。

然后,懒惰的程序员编写了一个简单的脚本,将电子表格行转换为单元测试用例。确实很简单。

当产品发展时,将更新测试用例的电子表格并生成新的单元测试。一直做。真的行。

即使具有MVVM并具有测试GUI的能力,也需要大量代码来重现GUI场景。

什么?“复制”?

TDD的重点是设计可测试性的东西(测试驱动开发)。如果GUI如此复杂,则必须对其进行重新设计以使其更简单,更可测试。更简单也意味着更快,更可维护且更灵活。但是大多数情况下,更简单意味着可测试性更高。

我的经验是,如果将TDD限于简单的业务逻辑,则它会很好地工作。但是,由于测试(测试空间)的组合数量非常大,因此很难测试复杂的业务逻辑。

可以的。

但是,要求主题专家以简单的形式(例如电子表格)提供核心测试用例确实有帮助。

电子表格可能会变得很大。但这没关系,因为我使用了一个简单的Python脚本将电子表格转换为测试用例。

和。我确实必须手动编写一些测试用例,因为电子表格不完整。

然而。当用户报告“错误”时,我只是问电子表格中哪个测试用例是错误的。

那时,主题专家将更正电子表格,或者添加示例以解释应该发生的情况。在许多情况下,错误报告可以明确定义为测试用例问题。实际上,根据我的经验,将错误定义为坏的测试用例会使讨论变得非常简单。

专家们不必听专家试图解释超复杂的业务流程,而必须提供流程的具体示例。

TDD要求要求100%正确。在这种情况下,可以期望在创建测试期间会捕获到相互矛盾的需求。但是问题在于,在复杂的情况下并非如此。

不使用TDD绝对要求这些要求是100%正确的。一些人声称,TDD可以容忍不完整和不断变化的需求,而非TDD方法不能满足不完整的需求。

如果您不使用TDD,则会在实施阶段的后期发现矛盾。

如果使用TDD,则在代码通过某些测试但未通过其他测试时,就会更早发现矛盾。确实,TDD 在实现过程中早于实现(早于实现)(以及用户接受测试期间的参数)为您提供了矛盾的证明

您的代码通过了一些测试,但未通过其他测试。您查看那些测试,就会发现矛盾。它在实践中确实非常非常有效,因为现在用户必须争论矛盾并产生所需行为的一致且具体的示例。


4
@ S.Lott由于OP最有可能在MVVM上谈论WPF / SL,因此您的GUI测试注释有些偏离基础。即使使用去耦和严格的MVVM方法,从定义上来说,View仍然很难测试。这与任何UI相同。众所周知,测试View非常耗时,麻烦且投资回报率很低。这是关于测试M / VM而不考虑V的MVVM表面的论点可能是最好的方法,但是在View上测试组件(例如控件的放置,着色等)仍然非常耗时且复杂。
亚伦·麦克弗

3
@ S.Lott取决于范围。TDD在测试View方面没有提供实质性价值。但是,TDD在测试Model和ViewModel方面确实提供了实质性的价值。如果您的范围是ViewModel和View,则根据您的范围,TDD的值将大不相同,而如果您的范围是Model和所需的服务。不要误会我的意思,我相信TDD在复杂项目中具有巨大的价值...其价值因范围而异。
亚伦·麦克弗

5
@罗伯特·哈维:不可能是我的发明。我懒得发明任何东西。
S.Lott

4
@Amir Rezaei:对不起,您的最小单元测试数据很复杂。这与TDD无关。您的应用程序很复杂。您仍然需要测试,对吗?您还必须产生测试数据吗?如果您不打算遵循TDD,将如何创建可测试的应用程序?运气?希望?是。这很复杂。没有什么能消除复杂性。TDD确保您将实际测试这种复杂性。
S.Lott

4
@Amir Rezaei:“我们正在为现实而设计”。你要写测试吗?如果是这样,则设计可测性。如果您不打算编写测试,那么您如何知道什么有效?
S.Lott

28

我对TDD的初次接触是为基于Linux的手机开发中间件组件。最终最终导致了数百万行源代码,而这些代码又又为各种开源组件调用了约9 GB的源代码。

期望所有组件作者都提出一个API和一组单元测试,并由同行委员会进行设计审查。没有人期望测试会达到完美,但是所有公开公开的功能都必须至少具有一个测试,并且一旦将组件提交给源代码管理,所有单元测试都必须始终通过(即使因为组件错误地报告也是如此)工作正常)。

毫无疑问,至少部分归因于TDD以及坚持所有单元测试都必须通过的要求,1.0版本的出现早于预算之内,并且具有惊人的稳定性。

在1.0版本之后,由于公司希望能够根据客户需求快速更改范围,因此他们告诉我们停止执行TDD,并删除了通过单元测试的要求。令人惊讶的是质量下降到厕所的速度,然后按照时间表进行。


8
removed the requirement that unit tests pass. It was astonishing how quickly quality went down the toilet, and then the schedule followed it.-就像告诉您的F1赛车手他不允许进站,因为这需要太多时间。
杰西·特尔福德2013年

1
这体现了我一直在说的话:快速前进的唯一方法就是前进
TheCatWhisperer

18

我认为项目越复杂,您从TDD中获得的利益就越大。TDD将迫使您以更小,更独立的代码块编写代码的主要好处是副作用。主要好处是:

a)您得到了很多得多的设计验证,因为由于开始进行的测试,您的反馈循环更加紧密。

b)您可以零零碎碎地查看系统的反应,因为您一直都在构建测试覆盖范围。

c)结果,最终代码会更好。


1
我了解并了解TDD的好处。但是,我认为在此类项目中进行TDD需要多么现实,需要多少资源和负担。
阿米尔·雷扎伊

我不得不赞同你。(在我看来)在复杂的项目中,除了测试之外,没有其他方法可以确保一切正常……如果许多程序员在您的代码库上工作,您将无法确保没有人改变您的工作内容。如果测试持续通过-没问题。如果没有,您就知道要看哪里
mhr

10

TDD是否真的适用于复杂项目?
是。并不是说每个项目都可以与TDD一起很好地工作,但是大多数业务应用程序都很好,我敢打赌,如果以纯TDD方式编写的应用程序工作不佳,则可以采用ATDD方式编写而不会出现重大问题。

生成和维护模拟数据
使其小而只有您所需的内容,这似乎并不是可怕的问题。不要误会我的意思,这很痛苦。但这是值得的。

测试GUI
测试MVVM,并确保可以在没有视图的情况下进行测试。我发现这并不比测试任何其他业务逻辑难。用我不做的代码来测试视图,但是您目前所测试的只是绑定逻辑,希望您在进行快速手动测试时能很快抓住它。

测试业务
找不到问题。很多小型测试。就像我在上面说的,某些情况下(数独难题求解器似乎很流行)显然很难进行TDD。

TDD要求要求是100%正确的
,不是的。您从哪里得到这个想法?所有敏捷实践都接受需求变更。在做之前,您确实需要知道自己在做什么,但这与要求100%的要求不同。TDD是Scrum中的一种常见做法,根据定义,需求(用户故事)并不是100%完成的。


如果您没有准确的要求,您怎么甚至开始进行单元测试?您是否在冲刺过程中在实现和设计之间来回跳跃?
阿米尔·雷扎伊

“单位”小于要求,通常可以在不束缚所有UAC的情况下完成。
MLK

我们对每个单元进行单元测试,也对单元进行单元测试组合,这是必需的。
阿米尔·雷扎伊

9

首先,我认为您的问题更多地是关于单元测试而不是TDD,因为您所说的内容并没有真正涉及TDD(测试优先+红绿色重构周期)。

维护大型模拟数据既困难又不切实际。

模拟数据是什么意思?精确地假设一个模拟几乎不包含任何数据,即测试中不需要一个或两个字段,也没有任何字段,而被测系统除外。设置模拟期望值或返回值可以在一行中完成,所以没有什么不好的。

当数据库结构发生变化时,这甚至变得更加困难。

如果您是说数据库在未对对象模型进行适当修改的情况下进行了更改,那么恰好在这里进行单元测试可以警告您。否则,对模型的更改必须明显地反映在单元测试中,但是带有编译指示,这是一件容易的事。

即使具有MVVM和测试GUI的能力,也需要大量代码来重现GUI场景。

没错,对GUI(视图)进行单元测试并不容易,并且许多人在没有它的情况下都做得很好(此外,测试GUI并不是TDD的一部分)。相比之下,强烈建议对Controller / Presenter / ViewModel /任何中间层进行单元测试,实际上,这是诸如MVC或MVVM之类的模式的主要原因之一。

我的经验是,如果将TDD限于简单的业务逻辑,则它会很好地工作。但是,由于测试组合(测试空间)的数量非常大,因此很难测试复杂的业务逻辑。

如果您的业务逻辑很复杂,那么很难设计单元测试的正常情况。使它们尽可能原子化是您的责任,每次测试仅对被测对象负责。在复杂的环境中,单元测试变得更加必要,因为它们提供了一个安全网,可确保您在更改代码时不会违反业务规则或要求。

TDD要求要求100%正确。

绝对不。成功的软件要求需求是100%正确的;)单元测试仅反映您当前对需求的看法;如果愿景存在缺陷,那么您的代码和软件也是如此,无论是否进行单元测试...这就是单元测试的亮点:有了足够明确的测试标题,您的设计决策和需求解释就变得透明,这使指向变得更容易当您的客户下次说:“此业务规则不完全符合我的意愿时”,您就可以确定。


6

当我听到有人抱怨他们不能使用TDD来测试其应用程序的原因是,因为他们的应用程序太复杂了,我才笑了起来。有什么选择?有测试猴子敲打英亩的键盘吗?让用户成为测试者?还有什么?当然,这既困难又复杂。您认为英特尔在出货之前不会测试他们的芯片吗?那是怎样的“沙尘暴”?


5
拥有熟练,专业的工人,他们编写简单而有效的代码。并使用测试仪。这种方法对许多成功的公司都有效。
编码员

一种选择是回归测试。考虑一下,例如,测试Web浏览器。假设您是Google,并且想测试新版本的Chrome。您可以测试每个单独的CSS元素,每个HTML标签的每个属性以及JavaScript可以执行的每种基本操作。但是,这些功能有多少种可能的组合?我认为没有人可能知道这一点。因此,他们对各种功能中的单个功能进行了各种测试,但最终,他们对已知的网站进行了回归。那就是那里的百万只猴子。
Dan Korn 2015年

现实的选择是交付不起作用的软件。在适当的情况下,这仍然可以盈利。选择您喜欢的示例。
soru

4

我发现TDD(以及一般的单元测试)实际上是不可能的,原因有一个:复杂,新颖和/或模糊的算法。我编写的研究原型中遇到的大多数问题是,除了运行代码之外,我不知道正确的答案是什么。除可笑的琐碎案件外,它无法手工合理地解决。如果算法涉及启发式,近似或非确定性,则尤其如此。我仍然尝试测试此代码所依赖的较低级别的功能,并大量使用断言作为健全性检查。我最后的测试方法是编写两个不同的实现,最好使用两种不同的库以两种不同的语言编写两种实现,然后比较结果。


我遇到了这个问题。您需要“手工”解决简单的案例,并且需要领域专家解决和验证足够复杂的案例。如果没有人可以这样做,那么您将遇到规范问题。当您可以对算法接受函数进行编码时,即使它没有退出正确的形状状态空间,也可以将其用于统计测试(运行该测试10000次并查看答案接受趋势)
Tim Williscroft

“并且由领域专家设计并验证了一个足够复杂的案例”-那么,这是单元测试还是回归测试?
quant_dev

2
@Tim:我领域专家(在我的工作中,通常一个人既是领域专家又是程序员),我无法理智地手工解决这些问题。在另一方面,我几乎总是知道近似的答案应该是什么(例如,机器学习算法应该相当准确的预测,算法喂随机数据应该产生任何“有趣”的结果),但是这是很难实现自动化。此外,对于研究原型,几乎没有正式的规范。
dsimcha 2011年

@quant_dev这是一个单元测试。它在更复杂的测试数据集上测试设备的行为。您可以使用单元测试进行回归测试。您还应该为错误编写回归测试,以防止错误再次发生。(有力的证据表明,错误会聚集)
Tim Williscroft

@dsimcha:单元测试的统计方法可能对您有用,因为您可以估算出近似值。我在武器系统中使用了这种方法来选择和调试移动目标,移动射击者参与代码。手工得出答案很难,但是要预测出预测器的工作效果则相对容易(您实际上发射了一个弹丸,然后观察它实际上击中了什么地方,起泡沫,重复冲洗100000次,您会得到类似“ Algorithm A的工作时间为91%,AlgorithmeB的工作时间为85%。)
Tim Williscroft 2011年

4
> Does TDD really work for complex projects?

根据我的经验:是的,对于单元测试(独立的模块/功能测试),因为这些大多数都没有您提到的问题:(Gui,Mvvm,Business-Modell)。我从来没有超过3个模拟/存根来完成一个单元测试(但是您的域可能需要更多)。

但是,我不确定 TDD是否可以解决您使用BDD样式的测试的集成或端到端测试中提到的问题 。

但是至少可以减少一些问题

> However complex business logic is hard to test since the number 
> of combinations of tests (test space) is very large.

如果要在集成测试或端到端测试的级别上进行全面介绍,则为true。在单元测试级别进行完整覆盖可能会更容易。

示例:检查复杂的用户权限

IsAllowedToEditCusterData()在集成测试级别上测试功能将需要向不同的对象询问有关用户,域,客户,环境...的信息。

模拟这些部分非常困难。如果IsAllowedToEditCusterData()必须知道这些不同的对象,则尤其如此。

在单元测试级别上,您将具有IsAllowedToEditCusterData()例如20个参数的Function ,其中包含该函数需要知道的所有内容。由于 IsAllowedToEditCusterData()不需要知道a user,a domain,a customer,....的哪些字段具有此易于测试。

当我必须实现时IsAllowedToEditCusterData(),它有两个重载:

一种重载,无非就是获取这20个参数,然后用做决策的20个参数调用重载。

(我IsAllowedToEditCusterData()只有5个参数,我需要32种不同的组合来对其进行完整测试)

// method used by businesslogic
// difficuilt to test because you have to construct
// many dependant objects for the test
public boolean IsAllowedToEditCusterData() {
    Employee employee = getCurrentEmployee();
    Department employeeDepartment = employee.getDepartment();
    Customer customer = getCustomer();
    Shop shop = customer.getShop();

    // many more objects where the permittions depend on

    return IsAllowedToEditCusterData(
            employee.getAge(),
            employeeDepartment.getName(),
            shop.getName(),
            ...
        );
}

// method used by junittests
// much more easy to test because only primitives
// and no internal state is needed
public static boolean IsAllowedToEditCusterData(
        int employeeAge,
        String employeeDepartmentName,
        String shopName,
        ... ) 
{
    boolean isAllowed; 
    // logic goes here

    return isAllowed;
}

1
+1非常好的示例“检查复杂的用户权限”,这正是我们的场景之一。
阿米尔·雷扎伊

3

可悲的答案是,对于大型复杂项目,什么都没有真正起作用!

TDD和其他任何东西一样好,并且比大多数更好,但是仅TDD不能保证在大型项目中取得成功。但是,它将增加您成功的机会。特别是与其他项目管理学科(需求验证,用例,需求易处理性矩阵,代码演练等)结合使用时。


1

请记住,单元测试是强制性规范。这在复杂的项目中特别有价值。如果您的旧代码库没有任何可备份的测试,则没有人会敢于更改任何内容,因为他们会害怕破坏任何内容。

“ Wtf。为什么这个代码分支甚至还在那儿?不知道,也许有人需要它,而不是让任何人烦恼最好留在那儿……”随着时间的流逝,复杂的项目变成了一片垃圾。

通过测试,任何人都可以自信地说:“我已经进行了重大更改,但所有测试仍在通过中。” 根据定义,他没有破坏任何东西。这导致可以发展的更敏捷的项目。也许我们仍然需要人们维护COBOL的原因之一是因为从那时起测试并不受欢迎:P


1

我已经看到,当仅使用TDD时,即没有至少在调试器/ IDE中进行设置时,一个大型复杂项目完全失败。模拟数据和/或测试证明不足。Beta客户端的真实数据很敏感,无法复制或记录。因此,开发团队永远无法修复在指向实际数据时出现的致命错误,整个项目被废弃,每个人都被解雇了。

解决此问题的方法是将其在客户端站点的调试器中启动,根据真实数据运行,逐步执行代码,并带有断点,监视变量,监视内存等。但是,这个团队他们认为自己的代码适合装饰最精美的象牙塔,在过去的一年中,他们从来没有启动过他们的应用。那使我震惊。

因此,就像一切一样,平衡是关键。TDD可能不错,但不要完全依赖它。


1
TDD不会阻止白痴。TDD是敏捷的一部分,但另一个重要的方面是关于在每个sprint中交付可执行的可运行代码...
oligofren 2013年

0

我认为是这样,请参阅“ 测试驱动开发”确实有效

2008年,Nachiappan Nagappan,E。Michael Maximilien,Thirumalesh Bhat和Laurie Williams撰写了一篇论文,题为“通过测试驱动的开发实现质量改进:四个工业团队的成果和经验”(PDF链接)。摘要:

测试驱动开发(TDD)是一种软件开发实践,已被零星使用了数十年。通过这种做法,软件工程师会在编写失败的单元测试与编写实现代码以通过这些测试之间的每一分钟之间进行循环。测试驱动的开发最近重新出现,成为敏捷软件开发方法学的关键支持实践。但是,很少有经验证据支持或驳斥这种做法在工业环境中的效用。案例研究由Microsoft的三个开发团队和IBM的一个采用TDD的开发团队进行。案例研究的结果表明,与未使用TDD做法的类似项目相比,四种产品的预发布缺陷密度降低了40%至90%。主观上

2012年,Ruby on Rails开发实践采用了TDD。我个人依靠诸如用于编写测试和模拟的rspec,用于创建对象的factory_girl,用于浏览器自动化的capybara,用于代码覆盖率的simplecov以及用于使这些测试自动化的工具等工具。

由于使用了这种方法论和这些工具,我倾向于在主观上同意Nagappan等人的观点。


0

如果预算,需求和团队技能的组合处于项目空间的象限中,“放弃希望所有人进入这里”,那么根据定义,该项目将很可能失败。

需求可能是复杂和多变的,基础架构不稳定,团队初级且离职率很高,或者架构师是个白痴。

在TDD项目上,这种迫在眉睫的失败的症状是无法按计划编写测试。你试试,却发现“这就是要采取这个长,我们只有 ”。

其他方法失败时将显示不同的症状。最常见的是交付不起作用的系统。政治和合同将决定是否更可取。


-1

TDD听起来可能很痛苦,但是从长远来看,它将是您最好的朋友,请相信我,从长远来看,TDD它将真正使应用程序可维护和安全。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.