你如何unit testingunit testing?

我正在观看Rob Connerys在MVCStoreFront应用程序上的networking广播,我注意到他是unit testing,甚至是最平凡的事情,例如:

public Decimal DiscountPrice { get { return this.Price - this.Discount; } } 

会有一个像这样的testing:

 [TestMethod] public void Test_DiscountPrice { Product p = new Product(); p.Price = 100; p.Discount = 20; Assert.IsEqual(p.DiscountPrice,80); } 

虽然我都是unit testing,但我有时会怀疑这种testing方式是否真正有益,例如,在真实的过程中,您的代码上面有3-4层(业务请求,需求文档,架构文档) ,实际定义的业务规则(折扣价格是价格 – 折扣)可能会被错误定义。

如果是这种情况,你的unit testing对你来说没有任何意义。

另外,你的unit testing是另一个失败点:

 [TestMethod] public void Test_DiscountPrice { Product p = new Product(); p.Price = 100; p.Discount = 20; Assert.IsEqual(p.DiscountPrice,90); } 

现在testing是有缺陷的。 很明显,在一个简单的testing中,没有什么大不了的,但是我们正在testing一个复杂的业务规则。 我们在这里获得什么?

在维护开发人员维护它的过程中,快速进入应用程序的两年。 现在业务改变了规则,testing再次中断,一些菜鸟开发人员不正确地修复testing…我们现在还有另一个失败点。

我所看到的只是更多可能的失败点,没有真正的有利回报,如果折扣价格错误,testing团队仍然会发现问题,unit testing如何保存任何工作?

我在这里错过了什么? 请教我爱TDD,因为我目前很难接受它。 我也想要,因为我想保持进步,但是对我来说没有任何意义。

编辑:一些人一直提到testing有助于强制规范。 根据我的经验,这个规范一直是错误的,往往不是这样,但也许我注定要在规范由不应该写规范的人写的规范的组织中工作。

首先,testing就像安全 – 你永远不可能100%确定你已经掌握了它,但是每一层都增加了更多的信心和框架,可以更容易地解决问题。

其次,你可以把testing分解成子程序,然后它们就可以被testing了。 当你有20个类似的testing,做一个(testing)的子程序意味着你的主要testing是子程序的20个简单的调用,这是更可能是正确的。

第三,有人会认为TDD解决了这个问题。 也就是说,如果你只写了20个testing,他们通过,你不完全有信心,他们实际上正在testing任何东西。 但是,如果您最初编写的每个testing都失败了 ,然后又修复了这个问题,那么您就更有信心它正在testing您的代码。 恕我直言,这反复需要更多的时间,而不是它的价值,但它是一个尝试解决您的关注的过程。

一个错误的testing不太可能破坏你的产品代码。 至less,没有比根本没有考试更糟的了。 所以这不是一个“失败点”:为了使产品实际工作,testing不一定是正确的。 在签名之前,他们可能必须是正确的工作,但修复任何破解testing的过程不会危及您的实现代码。

你可以考虑testing,甚至像这些简单的testing,作为第二个意见代码是应该做的。 一个意见是testing,另一个是实施。 如果他们不同意,那么你知道你有一个问题,你看得更近。

如果将来有人希望从头开始实现相同的接口,这也很有用。 他们不需要阅读第一个实现,以便知道折扣是什么意思,并且testing可以作为您可能具有的任何书面描述的明确备份。

这就是说,你正在交易时间。 如果还有其他的testing,你可以用跳过这些简单的testing的时间来写作,也许它们会更有价值。 这取决于你的testing设置和应用程序的性质,真的。 如果折扣对于应用程序来说很重要,那么无论如何你都要在functiontesting中捕捉这个方法中的任何错误。 所有的unit testing都可以让你在testing这个单元的时候捕获它们,当错误的位置立即变得明显的时候,而不是等到应用程序被集成在一起,错误的位置可能不太明显。

顺便说一下,我个人不会使用100作为testing案例的价格(或者说,如果我这样做,那么我会用另一个价格再增加一个testing)。 原因是未来有人会认为折扣应该是一个百分比。 像这样微不足道的testing的一个目的是为了确保在阅读规范中的错误得到纠正。

[关于编辑:我认为不正确的规范是不可避免的。 如果你不知道应用程序应该做什么,那么很可能不会这样做。 但是写testing来反映规范并不能放大这个问题,它只是不能解决问题。 所以,你不会添加新的失败点,你只是代performance有的错误代码而不是华夫饼文件。

我所看到的只是更多可能的失败点,没有真正的有利回报,如果折扣价格错误,testing团队仍然会发现问题,unit testing如何保存任何工作?

unit testing不是真的应该保存工作,它应该帮助你find并防止错误。 这是更多的工作,但这是正确的工作。 它以最低的粒度级别思考你的代码,并编写testing用例,certificate它在预期的条件下工作,对于给定的一组input。 它是隔离variables,所以你可以通过在错误确实出现的时候在正确的位置查找来节省时间 。 这是保存一套testing,以便您可以一次又一次地使用它们,当你必须在路上做出改变。

我个人认为,大多数方法学并不是从货物崇拜软件工程中删除很多步骤,包括TDD,但是您不必遵循严格的TDD来获得unit testing的好处。 保留好的零件,扔掉那些收益不大的零件。

最后,对于你的题目问题“ 你怎么unit testingunit testing? ”的答案是,你不应该这样做。 每个unit testing都应该是简单的脑死亡。 调用具有特定input的方法,并将其与预期的输出进行比较。 如果一个方法的规范发生了变化,那么你可以期望该方法的一些unit testing也需要改变。 这就是你在这么低的粒度级别进行unit testing的原因之一,所以只有部分unit testing必须改变。 如果你发现许多不同方法的testing正在改变一个需求的变化,那么你可能不是在一个足够精细的粒度水平上进行testing。

unit testing是在那里,让你的单位(方法)做你期望的。 写testing首先迫使你编写代码之前思考你的期望。 做之前的思考总是一个好主意。

unit testing应该反映业务规则。 当然,代码中可能存在错误,但是首先编写testing可以让您在编写代码之前从业务规则的angular度编写代码。 之后编写testing,我认为,更可能导致你描述的错误,因为你知道代码如何实现它,并试图确保实现是正确的 – 而不是意图是正确的。

此外,unit testing只是一种forms – 而且是你应该写的最低的testing。 集成testing和验收testing也应该被编写,如果可能的话,由客户编写后者,以确保系统以预期的方式运行。 如果在testing过程中发现错误,请回过头来编写unit testing(失败)来testingfunction更改以使其正常工作,然后更改代码以使testing通过。 现在你有回归testing,捕捉你的错误修复。

[编辑]

我在做TDD时发现的另一件事。 它几乎强制devise默认。 这是因为高度耦合的devise几乎不可能单独进行unit testing。 使用TDD并不需要很长的时间才能发现使用接口,控制反转和dependency injection – 所有可以改进devise和减less耦合的模式对于可testing代码来说都非常重要。

如何testing一个testing ? 突变检测是我亲自使用的一个有价值的技术,效果出人意料。 阅读链接文章的更多细节,并链接到更多的学术参考,但一般来说,它通过修改你的源代码(例如,改变“x + = 1”到“x – = 1”)来“testing你的testing”,然后重新运行testing,确保至less有一个testing失败。 任何不会导致testing失败的突变都会被标记出来供以后调查。

您会惊讶于如何通过一系列看起来全面的testing来实现100%的线路和分支机构的覆盖率,然而,如果没有任何testing抱怨,您可以从根本上改变,甚至可以在源代码中注释掉一行。 通常情况下,这不是用正确的input来检测所有的边界情况,有时候更加微妙,但是在所有的情况下,我对它的输出有多less留下了深刻的印象。

在应用testing驱动开发(TDD)时,一开始就是一个失败的testing。 这一步似乎是不必要的,实际上是在这里validationunit testing正在testing的东西。 事实上,如果考试永远不会失败,那就没有价值,更糟糕的是,会导致错误的信心,因为你会依赖一个没有任何证据的积极结果。

严格遵循这个过程时,所有“单位”都受到单位testing所制定的安全网的保护,甚至是最普通的。

 Assert.IsEqual(p.DiscountPrice,90); 

testing没有理由朝着这个方向发展 – 或者我在你的推理中丢失了一些东西。 当价格是100和折扣20,折扣价格是80.这就像一个不变的。

现在假设您的软件需要支持另一种基于百分比的折扣,可能取决于购买量,您的Product :: DiscountPrice()方法可能会变得更加复杂。 而引入这些变化可能会破坏我们最初的简单折扣规则。 然后你会看到这个testing的价值,这将立即检测回归。


红 – 绿 – 重构 – 这是要记住TDD过程的本质。

testing失败时, 红色表示JUnit红色条。

绿色是所有testing通过时JUnit进度条的颜色。

在绿色条件下重构 :消除任何重复,提高可读性。


现在为了解决“代码之上3-4层”的问题,在传统的(类似瀑布式的)过程中,这不是事实,而是开发过程是敏捷的。 敏捷是TDD来自的世界; TDD是eXtreme Programming的基石。

敏捷是关于直接交stream,而不是抛弃式的需求文档。

虽然,我都是unit testing,我有时会怀疑这种testing方式是否真正有益于…

像这样小的,微不足道的testing可能是你的代码库中的“煤矿中的金丝雀”,在太迟之前提醒危险。 微不足道的testing是有用的,因为它们帮助你获得正确的交互。

例如,考虑一个简单的testing来探究如何使用你不熟悉的API。 如果testing与使用API​​的代码“真实”的代码有什么关系,那么保持这个testing是有用的。 当API发布一个新的版本,你需要升级。 现在,您已经假设您期望API以可执行格式进行logging,您可以使用它来logging回归。

…在真实stream程中,您的代码(业务请求,需求文档,架构文档)上面有3-4层,实际定义的业务规则(折扣价格为价格 – 折扣)可能会被错误定义。 如果是这种情况,你的unit testing对你来说没有任何意义。

如果你已经写了几年而没有写testing,那么你可能不会立即明白它有什么价值。 但是如果你的思维方式是最好的工作方式是“提前发布,经常发布”或“敏捷”,那么你希望能够快速/持续部署,那么你的testing肯定意味着什么。 要做到这一点的唯一方法就是通过对testing代码进行的每一项更改进行合法化。 无论testing多么小,一旦你有一个绿色的testing套件,你理论上可以部署。 另请参阅“连续生产”和“永久testing版”。

你不必一定要先做“testing”,但这通常是达到目标的最有效的方法。 当你进行TDD时,你将自己locking在两到三分钟的红色绿色重构周期。 在任何时候,你都不能停下来离开,并且完全弄乱你的手,需要花费一个小时的时间来debugging和整理。

另外,你的unit testing是另一个失败点

一个成功的testing是certificate系统失败的一个testing。 失败的testing会提醒您testing逻辑或系统逻辑中的错误。 你的testing的目标是破坏你的代码或者certificate一个场景有效。

如果你在代码之后编写testing,那么你就冒着写一个“不好”的testing的风险,因为为了看到你的testing真正起作用,你需要看看它是否被破坏和工作。 当你在代码之后编写testing时,这意味着你必须“跳出陷阱”,并在代码中引入一个错误,以便testing失败。 大多数开发者不仅对此感到不安,而且会认为这是浪费时间。

我们在这里获得什么?

这样做肯定有好处。 Michael Feathers将“遗留代码”定义为“未经testing的代码”。 当你采取这种方法时,你将对你的代码库所做的每一个改变合法化。 它比不使用testing更严格,但是当涉及到维护一个大的代码库时,它自己付出代价。

说到羽毛,有两个很好的资源你应该检查一下:

  • 有效地使用遗留代码
  • .NET中的Brownfield应用程序开发

这两个解释如何将这些types的实践和学科工作到不是“绿地”的项目。 他们提供了围绕紧密耦合的组件编写testing的技术,硬连线的依赖关系以及您不一定能够控制的事情。 这一切都是为了寻找“接缝”,并围绕这些进行testing。

如果折扣价格不对,testing团队仍然会发现问题,unit testing如何保存任何工作?

这样的习惯就像投资一样。 退货不是直接的; 他们随着时间的推移而增长 没有testing的替代scheme实质上是承担了无法回避的债务,无需担心集成错误而引入代码,或者推动devise决策。 美是让你的代码库引入的每一个变化合法化。

我在这里错过了什么? 请教我爱TDD,因为我目前很难接受它。 我也想要,因为我想保持进步,但是对我来说没有任何意义。

我把它看作是一个职业责任。 这是一个奋斗的理想。 但是很难跟踪和繁琐。 如果你关心它,并觉得你不应该产生未经testing的代码,你将能够find意志力来学习良好的testing习惯。 我现在做的一件事情(就像其他人一样)是一个自己的时间,一个小时就可以编写代码,而没有任何testing,然后有纪律就可以把它扔掉。 这可能看起来很浪费,但并不是真的。 这不是这样做的成本公司的物理材料。 这有助于我理解这个问题,以及如何编写代码,使其具有更高的质量和可testing性。

我的build议最终会是,如果你真的不想做好这件事,那就不要这样做。 不好的testing没有得到维护,performance不佳等等可能比没有任何testing更糟。 你自己很难学,而且你可能不会喜欢它,但如果你没有这个愿望,或者没有足够的价值去学习,那将是不可能的。保证时间投入。

一些人一直提到testing有助于强制规范。 这是我的经验,规范也是错误的,往往不是。

开发人员的键盘是橡胶与路面相遇的地方。 如果规范是错误的,而且你不提高标志,那很有可能你会被指责。 或者至less你的代码会。 参与testing的纪律和严谨是很难坚持的。 这并不容易。 这需要练习,很多的学习和很多的错误。 但最终它确实还清。 在快节奏,快速变化的项目中,无论晚上睡觉,这都是唯一的办法。

另一个需要思考的问题是,与testing基本相同的技术已经被certificate在过去有效:“无尘室”和“合同devise”都倾向于生成相同types的“元”码结构testing在不同的地方执行。 这些技术都不是银弹,严格来说,最终将在您可以提供的function范围内花费您的时间。 但是这不是这个意思。 这是关于能够维持你所做的事情。 这对大多数项目来说非常重要。

unit testing与复式记账非常相似。 您可以用两种完全不同的方式(如生产代码中的程序规则,以及testing中简单,有代表性的示例)陈述相同的事情(业务规则)。 你们两个都犯了同样的错误是不太可能的,所以如果他们双方都认同,那么你们错了就不太可能了。

testing如何是值得的努力? 根据我的经验,至less有四种方法,至less在进行testing驱动开发时:

  • 它可以帮助你拿出一个很好的解耦devise。 你只能unit testing很好解耦的代码;
  • 它可以帮助您确定何时完成。 必须在testing中指定所需的行为有助于不构build实际上不需要的function,并确定function何时完成;
  • 它为您提供了一个重构的安全networking,这使得代码更易于修改; 和
  • 它为您节省了大量的debugging时间,这非常昂贵(我听说估计传统上,开发人员花费高达80%的时间进行debugging)。

大多数unit testing,testing假设。 在这种情况下,折扣价格应该是价格减去折扣。 如果你的假设是错误的,我敢打赌你的代码也是错的。 如果你犯了一个愚蠢的错误,testing将失败,你会纠正它。

如果规则改变了,testing将会失败,这是一件好事。 所以在这种情况下你也必须改变testing。

一般来说,如果一个testing立即失败(而且你不使用testing第一个devise),那么testing或者代码都是错误的(或者如果你的日子不好的话)。 您使用常识(并通过规格)来纠正有问题的代码并重新运行testing。

像Jason说的那样,testing就是安全。 是的,有时他们由于错误的testing而引入额外的工作。 但是大部分时间他们都是非常节省时间的。 (你有完美的机会惩罚那个打破testing的人(我们在说橡皮鸡))。

testing你所能做的一切。 即使是微不足道的错误,例如忘记将仪表转换为脚,也会产生非常昂贵的副作用。 写一个testing,写它的代码检查,让它通过,继续前进。 谁知道在未来某个时候,有人可能会改变折扣码。 一个testing可以检测到这个问题。

我看到unit testing和生产代码有共生关系。 简单地说:一个testing另一个。 并且都testing开发者。

请记住,修复缺陷的成本会随着开发周期中的缺陷而增加(呈指数级增长)。 是的,testing团队可能会抓住这个缺陷,但是如果unit testing失败,它通常会(通常)需要更多的工作来隔离和修复缺陷,并且如果您在修复它的时候更容易引入其他缺陷没有unit testing运行。

通常情况下,通过一个简单的例子就可以更容易地看到这些东西了…并且用简单的例子来说明,如果你以某种方式弄乱了unit testing,那么审阅它的人将会捕获testing中的错误或代码中的错误,都。 (他们正在接受审查,对吧?)正如tvanfosson所指出的 ,unit testing只是SQA计划的一部分。

从某种意义上讲,unit testing就是保险。 他们不能保证你能捕捉到每一个缺陷,而且有时你似乎花费了大量的资源,但是当它们发现了可以修复的缺陷时,你将花费很less比如果你根本没有testing,并且必须修复下游的所有缺陷。

我明白你的观点,但显然是夸大了。

你的论点基本上是:testing引入失败。 因此,testing是不好的/浪费时间。

虽然这在某些情况下可能是真的,但它并不是多数。

TDD假设:更多的testing=更less的失败。

testing更容易发现失败点而不是介绍它们。

更多的自动化可以在这里帮助! 是的,编写unit testing可以做很多工作,所以使用一些工具来帮助你。 如果您使用的是.Net,请查看来自Microsoft的Pex的信息。它将通过检查您的代码自动为您创buildunit testing套件。 它会提供覆盖率很高的testing,试图覆盖通过代码的所有path。

当然,只要看看你的代码,它就不知道你实际上正在做什么,所以它不知道它是否正确。 但是,它会为您生成有趣的testing案例,然后您可以检查它们,看看它是否按照您的预期行事。

如果你进一步去写参数化的unit testing(你可以把它们当作契约来考虑),它会从中产生特定的testing用例,这次它可以知道是否有错误,因为你的testing中的断言将会失败。

我曾经想过一个很好的方法来回答这个问题,并且希望对科学方法进行一个平行分析。 国际海事组织,你可以改变这个问题,“你如何实验一个实验?”

实验validation关于物理宇宙的经验假设(假设)。 unit testing将testing关于他们调用的代码的状态或行为的假设。 我们可以谈论一个实验的有效性,但那是因为我们通过许多其他的实验知道一些不适合的东西。 它不具有收敛效度经validation据 。 我们不devise一个新的实验来testing或validation实验的有效性,但我们可能会devise一个全新的实验

所以就像实验一样 ,我们也没有根据unit testing是否通过unit testing来描述unit testing的有效性。 与其他unit testing一起,它描述了我们对正在testing的系统所作的假设。 而且,就像实验一样,我们试图从我们正在testing的东西中去除尽可能多的复杂性。 “尽可能简单,但不简单。”

与实验不同的是 ,我们有一个技巧来validation我们的testing是否合法,而不仅仅是聚合的有效性。 我们可以巧妙地引入一个我们知道应该被testing抓住的bug,看看testing是否确实失败了。 (如果只有我们可以在现实世界中做到这一点,那么我们将更less地依赖于这个趋同的有效性的东西!)一个更有效的方法是在实现之前注意你的testing失败( 红色,绿色,重构中的红色步骤)。

编写testing时你需要使用正确的范例。

  1. 首先写你的testing。
  2. 确保他们没有开始。
  3. 让他们通过。
  4. 检查代码之前进行代码审查(确保testing已经过审查。)

你不能总是确定,但他们改善整体testing。

即使你不testing你的代码,它肯定会在你的用户的生产testing。 用户非常有创意,试图破坏软件,甚至发现非严重错误。

解决生产中的错误要比在开发阶段解决问题要昂贵得多。 作为一个副作用,你会因为客户的stream失而失去收入。 一个愤怒的顾客可以指望11个丢失或未获得的顾客。