您是如何“强迫”自己做TDD而不是TAD?

时间:2010-04-28 16:06:34

标签: tdd

我一直试图加入TDD的潮流已经有一段时间了,除了一件至关重要的事情之外它一直很顺利,通常我最终做的就是开发后测试。
我需要精神上的转变,我想知道你是如何强迫自己先写测试的?

12 个答案:

答案 0 :(得分:26)

我的心理转变是认识到TDD是关于设计,而不是测试。 TDD允许您批判性地推断您正在构建的事物的API。首先编写测试,并且通常非常清楚API最方便和最合适。然后编写您的实现。

当然你也应该编写测试(回归测试,集成测试等)。 TDD通常会产生良好的设计,但不一定是良好的测试代码。

答案 1 :(得分:21)

当我读到一个特定的引用(我记不清哪里)时,一个重要的时刻来到TDD我认为测试的胜利时刻是测试从红色变为绿色的时刻

这可能是你之前读过的所有内容,但除非我从失败的测试开始,并且成为通过测试,否则我将获得巨大的心理益处。从红色变为绿色感觉很好。如果你坚持将这一时刻交给自己,它会让人上瘾,然后更容易让自己做。

但对我而言,诀窍就是孤立那一刻并陶醉其中。

答案 2 :(得分:8)

一旦我开始利用依赖注入,我的类变得更小,更专业,这使我能够编写简单的单元测试来确认它们的工作原理。鉴于我知道我的班级必须通过工作的测试数量有限,我的TDD工作的目标变得更加明确。由于依赖于外部资源,以及将模拟/存根/伪对象注入SUT以缩小测试重点所需的单元测试,也更容易识别哪些类需要集成测试。

答案 3 :(得分:5)

  1. 如果你有一个通用的测试框架,它会有所帮助。

    拥有适用于您运行的各种测试的通用函数库。然后重新使用它们作为构建块来为您正在进行的项目构建测试。

    要实现这一目标,请注意您在之后编写的测试中所做的常见事情。将它们逐一抽象为广义库。

    这样做可以让您轻松快速地完成许多更简单的测试,无需重新执行枯燥且耗时的测试驱动程序代码,而是专注于实际的测试用例。

  2. “以文档测试”方法。不要添加/更改未通过适当测试备份的文档中的任何措辞。

    这样可以节省时间 - 您不必为了以后建立测试而重新解析文档/要求的另一个问题 - 并且有助于您提出的心理转变。

  3. 逐步逐步加入 - 在新功能/更改开始工作时添加测试。

    没有人喜欢改变冷火鸡的方式 - 人性。让好习惯溜进去,最终成为第二天性。

  4. 立即预算在项目开发计划开始时编写测试的时间

    这将迫使你养成适当的习惯(假设你倾向于遵循你的项目计划)并保护你免于因为建立测试所花费的“额外”时间而过期。

    当然,TDD的“额外”时间最终会延长净节省时间,但在项目的最初阶段并不总能实现,这给TDD实践带来了负面压力(“原型屏幕截图在哪里? ??你是什么意思,你还在写测试?“)。

  5. 此外,请尝试遵循小型一用途类和功能的常规推荐做法。这 - 在所有其他好处中 - 允许更容易的单元测试编写。结合#2(通过编写单元测试作为API文档的一部分,在设计API时),并且您的API设计“神奇地”改进,因为您基于它们编写测试时立即开始注意弱点。正如其他人所指出的那样,使用某种依赖注入模式/框架有助于简化测试的构建。

答案 4 :(得分:5)

结对编程

我意识到这可能不是每个人的选择,并且许多开发者不喜欢这个想法。但是我发现,如果我将程序与同时也致力于TDD的人配对,我们倾向于“保持彼此诚实”并且远远超过TDD,而不仅仅是纯粹的编程。

答案 5 :(得分:4)

作为一名独立开发者,帮助我转向TDD的一件事就是为自己设置代码覆盖率阈值。

在我的构建脚本中,我使用代码覆盖率工具(NCover)来确定测试所涵盖的代码百分比,并且最初将阈值设置为80%。如果我首先停止编写我的测试,覆盖百分比将低于80%的阈值,我的构建脚本将导致失败。然后我会立刻拍打自己的手腕并写下缺失的测试。

我逐渐增加了代码覆盖率阈值,最终成为完整的TDD转换。

答案 6 :(得分:3)

帮助我灌输习惯性纪律的是什么,在做出任何改变之前,对自己说,“我写什么测试来证明改变有效?”。虽然不是严格的TDD(因为焦点非常小),但只要系统发生变化,它就会将测试带到我的思想的最前沿。

从小开始,进入门槛低,每天练习,习惯成为第二天性。一段时间后,您考虑测试的范围自然会扩大到包括设计,以及集成和系统测试。

我发现“start small”在遗留项目上运作良好,这些项目几乎没有进行单元测试,并且将其带到临时的intertia太大,以至于没有人打扰。即使整个项目的测试环境非常贫瘠,小变化,错误修正等通常也可以轻松进行单元测试。

答案 7 :(得分:3)

对我而言,完全是为了实现这些好处。事实上修复错误比从未编写错误要困难得多。

最简单的启动方式是imo,它是从一个新组件开始的。 TDD和一般的有效单元测试要求您以允许无依赖性测试的方式构建代码(意味着您需要具有模拟对象实现的接口等)。在任何复杂的软件中,这都会对代码的结构产生实际影响。

答案 8 :(得分:2)

我们的TDD从名称开始推动开发。最好从已经极端/有纪律的人那里学习。如果你的速度影响工作项目立即应用TDD,那么是什么阻止你在侧面项目的工作之外培养你的TDD肌肉呢?

这是我如何成为BDD / TDD转换的转贴:

一年前,我不知道如何做TDD(但真的很想(多么令人沮丧))并且从未听说过BDD ...现在我强制要求。我一直在.Net开发环境中,而不是Java,但我甚至用宏替换了“F5 - Run”按钮来运行Cucumber(BDD)或MBUnit(TDD),具体取决于它是功能/场景还是规范。如果可能的话,没有调试器。如果您使用调试器(JOKING(排序)),jar中$ 1。

这个过程非常棒。我们另外使用的框架是Oracle,我很幸运能够接触到这些框架,从中吸收信息,他/我们使用的框架是MavenThought。

一切都从BDD开始。我们的BDD是铁红宝石上直立的黄瓜。

特点:

情景:....    鉴于我做了... ...    当我做别的事情......    然后奇妙的事情发生......

场景:......

这不是单元测试本身,但它驱动功能,逐个场景,反过来单元(测试)规范..所以你开始一个场景,并在每个步骤,你需要在场景中完成它推动你的TDD。

我们一直使用的TDD在某种程度上是一种BDD,因为我们查看SUT(系统测试)所需的行为,并且每个规范指定一种行为(类“test”文件)。

示例:

以下是一种行为的规范:创建受测试系统时。

当属性发生更改时,还有一个规范(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属性应该与注入的漫射器相同。我不得不使用混凝土扩散器而不是Mock,因为在这种情况下,扩散器是Core / Domain对象,并且没有接口的属性通知。 95%的时间我们引用所有依赖项,如Dep(),而不是注入真实的东西。

通常我们有多个[It] Should_do_xyz(),有时甚至可能有多达10行的存根。这只是一个非常简单的例子,在该规范中没有GivenThat()或AndGivenThatAfterCreated()。

对于每个规范的设置,我们通常只需要覆盖规范的几个方法:

GivenThat()==&gt;这是在创建SUT之前发生的。

CreatSut()==&gt;我们使用StructureMap自动模拟sut的创建,90%的时间永远不需要覆盖它,但如果你是构造函数注入一个Concrete,你必须覆盖它。

AndGivenThatAfterCreated()=&gt;这是在创建SUT之后发生的。

WhenIRun()=&gt;除非它是[ConstructorSpecification],否则我们使用它来运行一行代码,这是我们为SUT指定的行为

此外,如果同一SUT的两个或多个规范存在共同行为,我们将其移至基本规范中。

所有我要做的就是运行规范是突出显示它的名称,例如“When_diffuser_observer_is_created”并按F5,因为记住,对我来说F5运行一个Rake任务要么测试:feature [tag] if Cucumber,或者test:class [SUT ]。对我来说很有意义,因为每次运行调试器都会丢掉它,没有代码被创建(哦,花费1美元(开玩笑))。

这是一种非常非常干净的方式,用TDD指定行为,并且具有非常简单的SUT和简单的规范。如果你试着成为牛仔编码器,并写出SUT蹩脚的硬依赖等等,你会感到尝试做TDD并厌倦/放弃或咬紧牙关并做正确的痛苦。

这是实际的SUT。我们有点花哨,并使用PostSharp在扩散器上添加属性通知更改,因此Post.Cast&lt;&gt;。再次,这就是我注入混凝土而不是模拟的原因。无论如何,正如您所看到的那样,另一个规范中定义的缺失行为是在扩散器上发生任何变化时。

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/中获取了所有内容。

红色或蓝色药丸,选择权在您手中。

答案 9 :(得分:2)

阅读测试代码!

阻止我首先测试的是在尝试重新创建模块需要在测试工具内运行的环境时缺乏洞察力。

要克服这些困难,您必须阅读其他程序员的测试代码并将该代码应用于您的需求。与学习如何使用新语言或新库进行编程时的方法相同。

答案 10 :(得分:1)

虽然我们从来没有能够实现完整的TDD,但是报告缺陷并创建一个phpunit(在我们的php商店中)测试用例失败但在错误被解决时通过的概念被证明对所有人来说都是可取的由于缺陷规范的明确性和变更代码的验证,各方(dev和qa)。我们还将这些单元测试合并到回归套件中,以防我们在发布的代码中错过了这些“缺陷修复”分支。

答案 11 :(得分:0)

TDD越多越好,您拥有的体验就越多。但是达到收支平衡经验水平是很困难的,这使得更容易做tdd而不是tad。

所以相反的问题:&#34;什么阻止我做tdd&#34; 可能有助于获得一个良好的起点:

  • 时间压力,如果我没有经验tdd
  • 错误修正或改进未开发testdriven或尚未进行单元测试的应用程序(棕色现场应用程序)。
  • 快速应用程序开发环境,我改变了一些代码,我可以(几乎)立即看到它是否使用最终用户gui。

如果这些好处激怒了我,那么就会有动力。

目前我正在java / tomcat / web商店中开发零件,在这里编译所有并启动服务器/商店需要大约10分钟,这是快速应用程序开发的对立面。

编译和运行businesslogic以及单元测试只需不到10秒。

因此只要编写单元测试很容易,tdd就会比tad快得多。

相同的rad适用于我当前的android项目,我开发了android独立的java lib部分,可以很容易地进行单元测试。无需在设备上部署代码以查看代码是否正在运行。

在我看来,通过tdd获得更多经验是一个很好的起点:等待一个新的绿色领域应用程序,在开始时你没有时间压力。