我确实在编写 API 和核心功能时编写单元测试。但我想成为吃、睡和呼吸 TDD 和 BDD 的酷迷。以正确的方式开始使用 TDD/BDD 的最佳方式是什么?任何书籍、资源、框架、最佳实践?
我的环境是带有 Grails 前端的 Java 后端,与多个外部 Web 服务和数据库集成。
我确实在编写 API 和核心功能时编写单元测试。但我想成为吃、睡和呼吸 TDD 和 BDD 的酷迷。以正确的方式开始使用 TDD/BDD 的最佳方式是什么?任何书籍、资源、框架、最佳实践?
我的环境是带有 Grails 前端的 Java 后端,与多个外部 Web 服务和数据库集成。
一个好的起点是阅读博客。然后买写博客的人的书。我强烈推荐一些:
“鲍勃叔叔”马丁和 Object Mentor 的人:http: //blog.objectmentor.com/
PS get Bobs book Clean Code:
http://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882
我的朋友 Tim Ottinger(前对象导师老兄) http://agileinaflash.blogspot.com/ http://agileotter.blogspot.com/
Jetbrains 的家伙: http ://www.jbrains.ca/permalink/285
我觉得有必要对此进行扩展,因为其他人似乎只是想给你他们对 TDD 的看法,而不是帮助你成为绝地忍者。TDD 的迈克尔乔丹是肯特贝克。他确实在上面写了本书:
http://www.amazon.com/Test-Driven-Development-Kent-Beck/dp/0321146530
他还写博客:
http://www.threeriversinstitute.org/blog/?p=29
TDD 的其他“著名”支持者包括:
所有人都是值得追随的好人。您还应该考虑参加一些会议,例如 Agile 2010 或 Software Craftsmanship(今年它们在芝加哥同时举行)
我不喜欢人们说“练习 X 从来都不是坏事;如果它不起作用,你就没有做对。” 抱歉,它与任何其他过分热心的宗教教条具有相同的感觉。我不买。
我同意那些说你的时间和金钱能负担得起的最佳解决方案应该是目标的人的观点。
任何反对 TDD 的人都不应被自动指责为无视质量。(“那么你什么时候停止打你的妻子了?”)事实上,软件中存在缺陷,消除所有缺陷的成本必须与收益相权衡。
制造业也是如此。尺寸公差和表面光洁度并不完全相同,因为有时不能保证紧密的公差和镜面光洁度。
是的,我编写单元测试,尽管在编写课程之前并不经常。我已经看到了测试对设计的影响。我测量并观察代码覆盖率。如果我发现我的覆盖率不可接受,我会编写更多测试。我了解用于重构的单元测试安全网的好处。即使我独自工作,我也会遵循这些做法,因为我亲身体验了这些好处。我得到它。
但是对于任何开始困扰我关于“饮食、睡眠和呼吸单元测试和 TDD”的队友,我都会怀疑。
我的经理说,让我升职的唯一方法是让团队进入 TDD/BDD。
有没有想过这可能会让你听起来像个傻瓜?你有没有发现你的唠叨已经疏远了你团队的其他成员?
这种回应可能会让我失去一些声誉点,但必须说。
我认为更好的方法是自己练习并让其他人看到好处。以身作则。这比嘴上说的更有说服力。
天哪,Grails 内置了测试生成。如果您在使用 Grails 的团队工作,还需要多少销售?
最佳实践恕我直言:做实际的事情,而不仅仅是因为它是一个过程。不要忘记编写应用程序的目标是什么,在商业世界中,它不是编写测试。不要误会我的意思,他们有自己的位置,但这不应该是目标。
找一个一直在做 TDD/BDD 的人,和他们结对编程。
恕我直言,度量标准是从这里到那里的最佳方式。跟踪您的代码被覆盖的程度,为每次提交保留代码复杂度的增量,使用测试运行程序来监视您的代码的更改并不断地重新运行相应的测试。永远不要让测试长度超过几行,这样你的所有工具都可以正常工作。我建议每月一次,请一天假通过突变测试器运行您的代码。那天应该专门用于编写测试。如果你还没有做好 TDD,所有这些东西都会给你带来痛苦。从痛苦中吸取教训,你很快就会做对了。
永远不要忽视测试的目的:描述期望的行为。它们是您的可执行规范。(这也是我喜欢Cucumber的原因;现在您可以让您的 PHB 为您编写测试!好吧,也许不是那么好,但它已经接近了!)
“PS:我的经理说,让我升职的唯一方法是让团队达到 TDD/BDD。”
让团队做某事(在此过程中不会杀死您)的唯一现实方法是向他们清楚地证明改变习惯将使他们受益。换句话说,写代码。很多代码。大量的代码。然后,当重要的电子邮件从根本上改变了规范时,向他们展示你可以通过重构轻松更改代码,更糟糕的是,因为你已经为它做好了准备,测试已经到位。酒吧是绿色的,哈克哈克哈克,红色酒吧!!!!,哈克哈克哈克,绿色酒吧,回家。
阅读 Kent Becks 关于测试驱动设计的书。从测试开始,然后编写代码。获取运行测试的构建服务器!您不需要为整个团队拥有它 - 为自己做它并向他们展示它有帮助。
讲道只会惹恼当地人:)
我从事 TDD 已有几年了,但最近我开始更多地研究推动我的设计和开发的 BDD 方式。帮助我开始 BDD 的资源首先是 Dan North 的博客(BDD 的“创始人”)。看看介绍 BDD。在behaviour-driven.org上还有一个“官方”BDD Wiki,其中有一些值得一读的好帖子。
在开始使用 BDD 时,我发现非常困难的一件事(并且仍然觉得有点困难)是如何制定这些场景以使其适合 BDD。Scott Bellware 是一位精通 BDD(或他喜欢称之为 Context-Spesification)的人,他在 Code Magazine 上发表的文章Behavior-Driven Development在理解 BDD 的思维方式和制定用户故事方面帮助了我很多。
我还会推荐 Rob Conery 的 TekPub 截屏视频行为驱动设计和 Specflow。BDD 和非常适合在 C# 中执行 BDD 的工具 (SpecFlow) 的精彩介绍。
至于TDD资源,这里已经有很多不错的推荐了。但我只想指出几本我可以真正推荐的书;
开始进行单元测试,然后阅读如何正确地做,最后教你的团队如何进行 TDD 并让他们参与进来——因为根据我的经验,没有什么比与整个团队一起进行单元测试更重要的了。
您还需要一个适当的构建过程 - 使用构建服务器来构建您的代码并运行您的测试,我建议使用 TeamCity(免费但有限制)。
学习如何纠正好的单元测试是困难的部分——其中一些你会自己学习(只要你保持单元测试),其余的你可以通过搜索互联网来学习。
当不将单元测试作为开发的一部分编写时,您会知道您已经达到了目标,这对您来说是错误的。
请记住,敏捷意味着您不会完全放弃任何特定方法。如果您正在做的事情不值得 TDD 的好处(例如在 Swing 界面上进行试错编辑),那么不要使用 TDD。
我看不出有人真正表示 TDD与测试无关。TDD-ing 是在进行微小的行为改变修改之前表达预期的行为。这极大地改进了设计,并以我从未体验过的方式实现了专注。您可以免费获得保护您未来重构和 90% 覆盖率的测试。
为了学习它,我建议(总结其他人所说的并添加我自己的):
在我开始看到光之前,我自己练习了保龄球 kata(练习)大约 20 次(每次大约 30 分钟)。从分析鲍勃叔叔 在这里的描述开始。在 codingdojo.org 网站上有许多 katas,包括解决方案和讨论。试试看!
引用耐克的一句话:就做吧。
第二条建议——永远不要依赖别人的界面。始终在每个类的级别上写入您希望存在的接口 - 根据需要为实际实现编写适配器。
此外,我发现避免方法返回值以及根据消息传递而不是函数调用来考虑代码很有用。
YMMV。
一年前,我几乎不知道如何进行 TDD(但我真的很想(多么令人沮丧))并且从未听说过 BDD ......现在我都强迫性地这样做了。我一直在 .Net 开发环境中,而不是 Java,但我什至用宏替换了“F5 - 运行”按钮来运行 Cucumber (BDD) 或 MBUnit (TDD),具体取决于它是功能/场景还是规范。如果可能的话,没有调试器。如果您使用调试器(开玩笑(有点)),则 jar 中的 $1。
这个过程非常棒。我们另外使用的框架是由我有幸遇到的 Oracle 提供的,并从中吸收信息,他/我们使用的框架是 MavenThought。
一切从 BDD 开始。我们的 BDD 是铁红宝石上的黄瓜。
特征:
情景:....鉴于我做废话...
当我做其他事情...然后美妙的事情发生...
设想: ...
这不是单元测试本身,而是逐个场景驱动功能,进而驱动单元(测试)规范。所以你从一个场景开始,在场景中你需要完成的每一步,它都会驱动你的 TDD .
我们一直在使用的 TDD 在某种程度上是一种 BDD,因为我们查看 SUT(被测系统)所需的行为,并且每个规范(类“测试”文件)指定一个行为。
例子:
这是一种行为的规范: 创建被测系统时。
当属性更改时,还有一个规范(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 最简单的行为,因为在这种情况下,当它创建时,Diffuser 属性应该与注入的扩散器相同。我不得不使用 Concrete Diffuser 而不是 Mock,因为在这种情况下,Diffuser 是一个核心/域对象,并且没有接口的属性通知。95% 的时间我们引用了我们所有的依赖项,比如 Dep(),而不是注入真实的东西。
通常我们有不止一个 [It] Should_do_xyz(),有时还有一些设置,比如可能多达 10 行存根。这只是一个非常简单的示例,该规范中没有 GivenThat() 或 AndGivenThatAfterCreated()。
对于每个规范的设置,我们通常只需要覆盖规范的几个方法:
GivenThat() ==> 这发生在 SUT 创建之前。
CreatSut() ==> 我们使用 StructureMap 自动模拟创建 sut,90% 的时间不需要覆盖它,但如果你是构造函数注入混凝土,你必须覆盖它。
AndGivenThatAfterCreated() => 这发生在创建 SUT 之后。
WhenIRun() => 除非它是 [ConstructorSpecification],否则我们使用它来运行一行代码,这是我们为 SUT 指定的行为
此外,如果同一 SUT 的两个或多个规范存在共同行为,我们会将其移至基本规范中。
运行规范所需要做的就是突出显示它的名称,例如“When_diffuser_observer_is_created”并按 F5,因为请记住,对我来说,F5 运行 Rake 任务,如果是 Cucumber,则为 test:feature[tag],或 test:class[SUT]。对我来说很有意义,因为每次运行调试器时都会被扔掉,不会创建任何代码(哦,它要花 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 风格的开发方式令人震惊。花了一年时间,但作为一种生活方式,我完全皈依了。我自己不会学到这一点。我从 The Oracle http://orthocoders.com/中获取了所有内容。
红色或蓝色药丸,由您选择。