你如何“强迫”自己做TDD而不是TAD?
一直以来我一直试图跳上TDD的潮stream,除了一件重要的事情之外,它一直在顺利进行,通常我最终做的就是testing后的开发。
我需要精神转变,想知道你是如何强迫自己先写testing的?
我意识到TDD是关于devise而不是testing。 TDD允许你批判性地推理你正在构build的东西的API。 先写testing,通常很清楚API是最方便和最合适的。 然后编写你的实现。
当然你也应该在写testing之后(回归testing,集成testing等)。 TDD经常产生良好的devise,但不一定是好的testing代码。
当我读到一个特定的报价(我不记得在哪里)时,TDD给了我一个重要的时刻,那就是testing的胜利时刻是testing从红色变成绿色的时刻 。
这可能是你之前读过的所有东西,但是除非我从一个失败的testing开始,并且通过testing,那么当我获得巨大的心理好处的时候。 从红色变成绿色感觉很好。 如果你一直把自己的这一刻传递给自己,那就变得容易上瘾,然后让自己更容易做到。
但是对我来说,诀窍在于隔离那个时刻并且陶醉其中。
一旦我开始利用dependency injection,我的类变得更小,更专业化,使我可以编写简单的unit testing,以确认他们的工作。 鉴于我知道我class必须通过的testing数量有限,我的TDD工作的目标变得更加清晰。 确定哪些类需要集成testing是因为依赖于外部资源,哪些类需要将模拟/存根/伪造对象注入到SUT中的unit testing,以缩小testing的重点。
-
这有助于如果你有一个通用的testing框架。
有一个通用函数库适用于你运行的各种testing。 然后将这些作为构build块重新用于为您正在进行的项目构buildtesting。
为了达到这个目的,请注意你在testing中所做的一些常见的事情。 把它们逐一抽象成泛化库。
这样做可以使您无需重新执行乏味且耗时的testing驱动程序代码就可以非常快速地进行许多简单的testing,而只需重点关注实际的testing案例。
-
做“文件testing”的方法。 请勿在适当的testing备份的文档中添加/更改任何措辞。
这样可以节省时间 – 您不必重新parsing文档/需求,只需稍后构buildtesting,也可以帮助解决问题。
-
在开始工作时逐步添加新function/更改的testing。
没有人喜欢改变他们的方式冷火鸡 – 人性。 让好习惯溜走,最终成为第二天性。
-
在项目的开发计划开始时,马上预算编写testing的时间
这会迫使你进入正确的习惯(假设你倾向于遵循你的项目计划),并且保护你免于因为“额外”的时间花在构buildtesting上而过期。
当然,TDD的“额外”时间最终节省了networking时间,但在项目开始阶段并不总能实现,这给TDD实践带来了负面的压力(“原型截图在哪里?你的意思是你还在写testing?“)。
-
另外,尽量遵循通常推荐的小型一类用途和function。 这个 – 除了所有其他的好处之外 – 允许更容易的unit testing写作。 再加上#2(通过将unit testing编写为API文档的一部分,在deviseAPI时)以及您的APIdevise“神奇地”改进,因为您基于这些testing编写testing,因此立即注意到了不足之处。 正如其他人所指出的那样,使用某种dependency injection模式/框架有助于简化构buildtesting。
配对编程
我意识到这可能不是每个人的select,许多开发人员不喜欢这个想法。 但是我发现,如果我和一个同样致力于TDD的人结成对子,那么我们就倾向于“相互保持诚实”,并且坚持使用TDD,远远超过我可以单凭纯粹的意志编程。
作为一名独立开发人员,帮助我转移到TDD的一件事是为自己设定一个代码覆盖率的门槛。
在我的构build脚本中,我使用代码覆盖工具(NCover)来确定testing覆盖的代码百分比,并将阈值初始设置为80%。 如果我不再写我的testing,覆盖百分比将低于80%的阈值,我的构build脚本将导致失败。 然后,我立即将自己放在手腕上,写下缺less的testing。
我逐渐增加了代码覆盖阈值,最终成为了完整的TDD转换。
对我来说,所有关于实现好处。 在事实之后修复错误比从未写在第一位更难。
开始,最简单的方法是,从一个新的组件开始。 TDD和一般的有效的unit testing,要求你以一种允许在没有依赖关系的情况下进行testing的方式构build代码(这意味着你需要为模拟对象实现提供接口等)。 在任何复杂的软件中,这对代码的结构都有实际的影响。
什么帮助我灌输习惯性的纪律,在做出任何改变之前,对自己说:“我写什么样的testing来certificate这个改变是有效的?”。 虽然不是严格的TDD(因为焦点相当小),但只要系统发生变化,它就把testing带到了我的思维的最前沿。
从小起步,进入门槛低,每天练习,习惯成为第二天性。 过了一段时间,思考testing的范围自然扩大到包括devise,以及集成和系统testing。
我发现,“从小开始”在那些没有进行unit testing的遗留项目上工作得很好,而且把它从头开始的惯例太大了,以至于没有人打扰。 即使在整个项目的testing环境非常糟糕的情况下,通常也可以很容易地进行小的更改,错误修正等。
我们的TDD从名称驱动开发。 最好从已经是极端/有纪律的人那里学到的。 如果你的速度影响了在工作项目中立即应用TDD,什么阻止了你在一个侧面项目之外的TDD肌肉增长?
以下是我如何成为BDD / TDD转换器的转贴:
一年前,我不知道如何做TDD(但真的很想(很沮丧)),从来没有听说过BDD …现在我都是强制性的。 我一直在.Net开发环境中,而不是Java,但是我甚至用macros替代了“F5-Run”button,以运行Cucumber(BDD)或MBUnit(TDD),具体取决于它是function/scheme还是规范。 没有debugging器,如果可能的话。 如果你使用debugging器(JOKING(有点)),那么在jar中$ 1。
这个过程非常棒。 我们另外使用的框架是由甲骨文,我已经被祝福碰到,并从中吸收信息,他/我们使用的框架是MavenThought。
一切从BDD开始。 我们的BDD直上了ruby的黄铜ontop。
特征:
情景:….鉴于我做了咩…
当我做别的事时……那么奇妙的事情发生了…
场景:…
这不是unit testing本身,但它驱动的function,场景的情况,并依次单位(testing)规格..所以,你开始一个场景,并且每个步骤,你需要完成的情况下,它驱动你的TDD 。
我们一直使用的TDD是一种BDD,因为我们考察SUT(被测系统)需要的行为,并根据规范(类“testing”文件)指定一个行为。
例:
以下是一个行为的规范:创build被测系统时。
还有一个规范(C#When_blah_happens类文件)的另一个行为,当一个属性改变,但是被分离出来的另一个文件。
using MavenThought.Commons.Testing; using SharpTestsEx; namespace Price.Displacement.Module.Designer.Tests.Model.Observers { /// <summary> /// Specification when diffuser observer is created /// </summary> [ConstructorSpecification] public class When_diffuser_observer_is_created : DiffuserObserverSpecification { /// <summary> /// Checks the diffuser injection /// </summary> [It] public void Should_return_the_injected_diffuser() { Sut.Diffuser.Should().Be.SameInstanceAs(this.ConcreteDiffuser); } } }
这可能是SUT最简单的行为,因为在这种情况下,当它被创build时,Diffuser属性应该与注入的diffuser相同。 我必须使用混凝土扩散器而不是模拟器,因为在这种情况下,扩散器是核心/域对象,并且没有接口的属性通知。 95%的时间是指我们所有的依赖像Dep(),而不是注入真实的东西。
通常我们有不止一个(It)Should_do_xyz(),有时候可能会有一些设置,比如可能高达10行的残片。 这只是一个非常简单的例子,在该规范中没有GivenThat()或AndGivenThatAfterCreated()。
对于每个规范的设置,我们通常只需要覆盖规范的几个方法:
GivenThat()==>这发生在创buildSUT之前。
CreatSut()==>我们自动模拟使用StructureMap和90%的时间创buildsut,但是如果你是注入一个Concrete的构造函数,你必须覆盖它。
AndGivenThatAfterCreated()=>这发生在创buildSUT之后。
当()=>除非它是一个[ConstructorSpecification],否则我们使用它来运行一行代码,这是我们为SUT指定的行为
另外,如果有相同SUT的两个或多个规范的共同行为,我们将其移到基本规范中。
所有我要做的运行规范是突出显示它的名称,例如“When_diffuser_observer_is_created”,并按F5,因为记住,对我来说F5运行一个Rake任务testing:function[标签]如果黄瓜,或testing:类[SUT]。 对我来说很有意义,因为每次运行debugging器都是一箭双雕,没有代码被创build(哦,它花费1美元(开玩笑))。
这是一个用TDD指定行为的非常非常干净的方式,它具有真正简单的SUT和简单的规范。 如果你尝试着成为牛仔编码器,并且把SUT编写成困难的依赖关系等等,你将会感到尝试执行TDD并且厌倦/放弃或者咬紧牙关的痛苦。
这是实际的SUT。 我们有一点花哨,并使用PostSharp添加属性通知改变了扩散器,所以Post.Cast <>。 再次,这就是为什么我注入一个混凝土而不是模拟。 无论如何,你可以看到在另一个规范中定义的缺失行为是当扩散器发生任何变化时。
using System.ComponentModel; using MavenThought.Commons.Events; using PostSharp; using Price.Displacement.Core.Products; using Price.Displacement.Domain; namespace Price.Displacement.Desktop.Module.Designer.Model.Observers { /// <summary> /// Implementation of current observer for the selected product /// </summary> public class DiffuserObserver : AbstractNotifyPropertyChanged, IDiffuserObserver { /// <summary> /// gets the diffuser /// </summary> public IDiffuser Diffuser { get; private set; } /// <summary> /// Initialize with a diffuser /// </summary> /// <param name="diffuser">The diffuser to observe</param> public void Initialize(IDiffuser diffuser) { this.Diffuser = diffuser; this.NotifyInterface().PropertyChanged += (x, e) => this.OnPropertyChanged(e.PropertyName); } /// <summary> /// Gets the notify interface to use /// </summary> /// <returns>The instance of notify property changed interface</returns> protected INotifyPropertyChanged NotifyInterface() { return Post.Cast<Diffuser, INotifyPropertyChanged>((Diffuser)Diffuser); } } }
总而言之,这种BDD / TDD风格的开发方式非常有用。 花了一年的时间,但我完全转化为一种生活方式。 我不会自己学习这个。 我从Oracle http://orthocoders.com/拿起了所有的东西。;
红色或蓝色的药丸,select是你的。
阅读testing代码!
阻止我首先进行testing的是在尝试重新创build模块需要在testing工具中运行的环境时缺乏洞察力。
为了克服这些困难,你必须阅读其他程序员的testing代码,并将这些代码应用到你的需求。 当你正在学习如何使用新的语言或新的图书馆进行编程时,你也会这样做。
尽pipe我们无法实现完整的TDD,但报告缺陷并创build失败的phpunit(在我们的php商店)testing用例的概念,但在缺陷得到解决时会通过,这一点对于所有各方来说都是可取的(dev和qa)由于缺陷说明的明确性和对改变的代码的validation。 我们还将这些unit testing合并到回归套件中,以防我们在发布的代码中错过了这些“缺陷修复”分支。
TDD越多越好,越有经验。 但是,达到盈亏平衡的体验水平是很困难的,这使得tdd而不是tad更容易做到。
所以相反的问题: “什么阻止我做tdd”可能有助于获得一个好的起点:
- 如果我没有经历TDD的时间压力
- 修正或改进那些没有开发testing驱动或没有unit testing的应用程序(棕色领域的应用程序)。
- 快速的应用程序开发环境,我改变了一些代码,我可以(几乎)立即看到这是否使用最终用户gui。
如果好处让人痛苦,动机就来了。
目前我正在开发一个java / tomcat /networking商店的部分,编译所有和启动服务器/商店需要大约10分钟,这是快速应用程序开发的对立面。
编译和运行businesslogic和unittests需要不到10秒。
所以,只要易于编写unit testing,tdd比tad快得多。
同样的弧适用于我当前的Android项目,我开发的Android独立的Java库部分,可以很容易地testing。 不需要在设备上部署代码来查看代码是否正在运行。
在我看来,有一个很好的起点,可以获得更多关于tdd的经验:等待一个新的绿地应用程序,在这个应用程序中,你一开始没有时间压力。