据我所知,TDD和BDD周期类似于:
问题是如何在有代码之前编写测试?我应该创建某种类骨架或接口吗?或者我误解了什么?
答案 0 :(得分:6)
你有它的本质,但我会改变你描述的一部分。在编写代码之前,您没有编写测试 - 在编写代码之前编写测试。然后 - 在编写任何更多测试之前 - 编写足够的代码以使测试通过。当它通过时,你会寻找改进代码的机会,并在保持测试通过的同时进行改进 - 然后你编写第二个测试。关键是,您在任何给定时间都专注于一小部分功能。你希望你的程序接下来要做什么?为此写一个测试,仅此而已。让测试通过。清理代码。你想要它做的下一件事是什么?迭代直到你开心。
问题是,如果您在编写代码之前编写测试,那么您就没有那么专注。它一次只进行一次测试。
答案 1 :(得分:1)
是的,这是正确的。如果你查看Michael Hartl关于Ruby on Rails的书(可以免费查看HTML),你会看到他是如何做到这一点的。因此,为了增加lared所说的内容,让我们说你的第一份工作是在网页上添加一个新按钮。您的流程如下所示:
当您不小心对您的代码执行某些破坏旧测试的操作时,TDD将保存您的培根。例如,您意外地将按钮更改为链接。测试将失败并提醒您该问题。
答案 2 :(得分:1)
如果你使用的是真正的编程语言,(你知道,使用编译器和所有),那么是的,当然你必须编写类骨架或接口,否则你的测试甚至都不会编译。
如果您使用的是脚本语言,那么您甚至不必编写骨架或接口,因为您的测试脚本将很乐意开始运行,并且会遇到它遇到的第一个不存在的类或方法。
答案 3 :(得分:1)
问题是如何在有代码之前编写测试?我应该创建某种类骨架或接口吗?或者我误解了什么?
扩大他评论中提出的观点:
然后你编写测试,因为类/什么不存在而失败,然后你编写最少量的代码使它们通过
TDD要记住的一件事是您正在编写的测试是代码的第一个客户端。因此,我不担心没有定义类或接口 - 因为正如他所指出的那样,仅仅通过编写引用不存在的类的代码,您将获得第一个" Red&#34 ;在循环中 - 即你的代码不会编译!这是一次非常有效的测试。
一旦你接受了这个想法,你就会发现编写测试首先不那么简单"这个代码是否正确"更多的是#34;这段代码是对"指南,因此您会发现您实际上最终生成的代码不仅正确,而且结构良好。
现在一个显示这个过程的视频将是超级的,但我没有一个,但我会做一个例子。请注意,这是一个超级简单示例,忽略了业务中的前期铅笔和纸张规划/现实世界要求,这通常是设计过程背后的驱动力。
无论如何,假设我们想创建一个简单的Person对象,可以存储一个人的姓名和年龄。我们希望通过TDD这样做,所以我们知道它是正确的。
所以我们考虑一下,写下我们的第一个测试(注意:使用伪C#/伪测试框架的例子)
public void GivenANewPerson_TheirNameAndAgeShouldBeAsExpected()
{
var sut = new Person();
Assert.Empty(sut.Name);
Assert.Zero(sut.Age);
}
直接我们有一个失败的测试,这不会编译因为Person类不存在。因此,您可以使用IDE为您自动创建类:
public class Person
{
public int Age {get;set;}
public string Name {get;set;}
}
好的,现在你有第一次通过考试。但现在,当你看到那个课程时,你会发现没有什么可以确保一个人的年龄总是积极的(> 0)。让我们断言是这种情况:
public void GivenANegativeAgeValue_PersonWillRejectIt()
{
var sut = new Person();
Assert.CausesException(sut.Age = -100);
}
嗯,那个测试失败了,所以让我们修完课程:
public class Person
{
protected int age;
public int Age
{
get{return age;}
set{
if(value<=0)
{
throw new InvalidOperationException("Age must be a positive number");
}
age=value;
}
}
public string Name {get;set;}
}
但是现在你可能会对自己说 - 好吧,因为我知道一个人的年龄永远不会是<= 0,为什么我甚至打扰创造一个可写的财产 - 做我总是希望写两个语句,一个用于创建Person
,另一个用于设置Age
?如果我忘记在代码的一部分中执行该操作会怎么样?如果我在代码的一部分中创建了Person
,然后稍后又尝试在另一个模块中为Age
分配了一个负数,该怎么办?当然,Age
必须是Person
的不变量,所以让我们解决这个问题:
public class Person
{
public Person(int age){
if (age<=0){
throw new InvalidOperationException("Age must be a positive number");
}
this.Age = age;
}
public int Age {get;protected set;}
public string Name {get;set;}
}
当然,你必须修复你的测试,因为他们已经不再编译了 - 如果事实上现在你意识到第二次测试是多余的并且可以被删除!
public void GivenANewPerson_TheirNameAndAgeShouldBeAsExpected() { var sut = new Person(42); Assert.Empty(sut.Name); Assert.42(sut.Age); }
然后您可能会使用Name进行类似的过程,依此类推。 现在我知道这似乎是一种非常冗长的创建类的方法,但是考虑到你基本上已经从头开始设计这个类,内置防御无效状态 - 例如你永远不会必须调试这样的代码:
//A Person instance, 6,000 lines and 3 modules away from where it was instantiated
john.Age = x; //Crash because x is -42
或
//A Person instance, reserialised from a message queue in another process
var someValue = 2015/john.Age; //DivideByZeroException because we forgot to assign john's age
对我来说,这是TDD的主要优点之一,它不仅可以作为测试工具,还可以作为一种设计工具,让您考虑正在实施的生产代码,并强制执行您要考虑您创建的类如何最终处于无效的应用程序查杀状态,以及如何防范这种情况,并帮助您编写易于使用且不要求其消费者了解其工作方式的对象,而是他们做了什么。
由于任何现代IDE值得它的盐将为您提供通过几次击键或鼠标点击创建缺失的类/接口的机会,我相信它非常值得尝试这种方法。
答案 4 :(得分:0)
(
您通过考虑您要解决的问题编写测试,并通过假装您有一个可以测试的理想解决方案来充实细节。您编写测试以使用理想的解决方案。这样做可以做各种各样的事情:
BDD和TDD之间的区别在于BDD更关注于什么&#39;以及为什么&#39;而不是&#39;如何&#39;。 BDD非常关注使用语言来描述事物。 BDD从更高的抽象层开始。当你到达细节压倒语言的区域时,TDD被用作实现细节的工具。
你可以选择在不同的抽象层次上思考和写下它们的这个想法是关键。
你写的测试&#39;你需要选择: