我在编写API和核心功能时会编写单元测试。但我想成为一个粉丝,热爱TDD和BDD。那么正确开始学习TDD/BDD的最佳方法是什么?有没有书籍、资源、框架和最佳实践?
我的环境是Java后端与Grails前端集成,同时集成了多个外部Web服务和数据库。
我在编写API和核心功能时会编写单元测试。但我想成为一个粉丝,热爱TDD和BDD。那么正确开始学习TDD/BDD的最佳方法是什么?有没有书籍、资源、框架和最佳实践?
我的环境是Java后端与Grails前端集成,同时集成了多个外部Web服务和数据库。
这些都是值得关注的伟大人物。你也应该考虑参加一些会议,比如Agile 2010或者软件工艺大会(今年它们在芝加哥同时举行)。
我的最佳实践建议是:做实际可行的事情,而不仅仅是因为它是一个流程。不要忘记编写应用程序的目标,在商业世界中,它并不是编写测试。不要误解我,测试有其重要性,但这不应该成为目标。
找一个有 TDD/BDD 经验的人,与他们一起进行配对编程。
PS:我的经理说,唯一能让我升职的方法是让团队实行TDD/BDD。
让一个团队改变他们的习惯(而你不会被同时搞崩)的唯一现实途径是向他们清晰地展示改变将对他们有益。也就是说,编写代码。写很多代码。大量的代码。当接收到重要的电子邮件改变了规格后,展示给他们你如何通过重构轻松修改代码,更糟糕的是因为你预先准备好了测试,所以你能够应对。绿色的条纹,hack hack hack,红色的条纹!!!,hack hack hack,绿色的条纹,然后回家。
阅读Kent Beck关于测试驱动设计的书籍。从测试开始,然后再去写代码。启动一个运行测试的构建服务器!你不需要让整个团队使用它-只需为自己操作并向他们展示它的有效性。
只讲道理只会惹恼本地人 :)
首先进行单元测试,然后了解如何正确地执行单元测试,最后教授你的团队TDD,并让他们加入其中-因为根据我的经验,没有什么比与整个团队一起进行单元测试更重要的事情了。
您还需要一个适当的构建过程-使用构建服务器来构建代码并运行测试,我建议使用TeamCity(带有限制的免费版本)。
学习如何编写良好的单元测试是困难的部分-你可以从互联网上搜索到其中的一些内容,但只要您坚持进行单元测试,您也会自己学到更多。
当不编写单元测试作为开发的一部分看起来很奇怪时,您就知道已经达到了目标。
记住,敏捷开发意味着你并不完全信奉任何特定的方法。如果你正在处理一些 TDD 的好处不值得使用的东西(比如在 Swing 界面上进行试错式编辑),那就不要使用 TDD。
我们一直在使用的TDD在某种程度上类似于BDD,因为我们关注SUT(被测试系统)需要的行为,并且每个规范(类“test”文件)指定一个行为。
例如:
这是一个行为的规范:当创建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属性应该与注入的扩散器相同。我不得不使用具体的扩散器而不是模拟,因为在这种情况下,扩散器是一个核心/域对象,并且没有接口的属性通知。95%的时间我们像Dep()一样引用所有依赖项,而不是注入真正的东西。
通常我们有多个[It] Should_do_xyz(),有时会有相当多的设置,比如最多10行存根。这只是一个非常简单的例子,在该规范中没有GivenThat()或AndGivenThatAfterCreated()。
对于每个规范的设置,我们通常只需要覆盖规范的几个方法:
GivenThat() ==> 在创建SUT之前发生。
CreatSut() ==> 我们使用StructureMap自动模拟sut的创建,90%的时间不需要覆盖此方法,但如果您正在构造函数注入Concrete,则必须覆盖此方法。
AndGivenThatAfterCreated() => 在创建SUT之后发生。
WhenIRun() => 除非它是[ConstructorSpecification],否则我们使用它来运行一个代码行,这是我们为SUT指定的行为
此外,如果同一SUT的两个或多个规范具有共同的行为,则将其移动到基本规范中。
要运行规范,我只需突出显示其名称,例如"When_diffuser_observer_is_created",然后按F5,因为请记住,对于我来说,F5会运行Rake任务,无论是test:feature[tag](如果是Cucumber)还是test:class[SUT]。对我来说很有意义,因为每次运行调试器时都是一次性的,不会创建任何代码(哦,而且它花费1美元(开玩笑))。
这是使用TDD指定行为并拥有非常简单的SUT和简单规范的非常干净的方法。如果您尝试成为牛仔编码人员并编写具有硬依赖关系等糟糕的SUT,则会感受到尝试进行TDD并感到沮丧/放弃或咬紧牙关并做正确的事情的痛苦。
以下是实际的SUT。我们使用了PostSharp来在Diffuser上添加属性通知更改,因此使用了Post.Cast<>。这就是为什么我注入了一个Concrete而不是Mock的原因。无论如何,您可以看到另一个规范中定义的缺失行为是当Diffuser上的任何内容发生更改时。
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);
}
}
}