8

我正在使用 VS 2012,但这并不重要。

重要的是我试图通过先编写所有测试然后创建代码来做一些 TDD。

但是,该应用程序将无法编译,因为我的任何对象或方法都不存在。

现在,在我看来,我应该能够创建我的所有测试但仍然运行我的应用程序,以便我可以调试等。测试不应该因为缺少对象和方法而阻止编译。

我认为它的全部意义在于,当您开发测试时,您可以开始看到重复等,以便您可以在编写单行代码之前进行重构。

所以问题是,有没有办法做到这一点,还是我做错了?

编辑 我正在使用 VS2012 和 C#

4

7 回答 7

6

我看到一个小问题

首先编写我所有的测试,然后创建代码。

你不需要先写所有的测试,你只需要一个,让它失败,让它通过并重复。这意味着理想情况下,您应该在理想情况下进行一次失败的测试。

从这个意义上说,编译失败算作失败的测试。所以下一步是让它通过 - 即添加存根或返回默认值以使其编译。然后测试将变为红色..然后努力将其变为绿色。

于 2012-11-23T05:06:37.917 回答
5

测试驱动开发是关于非常小的迭代。您没有预先定义所有测试。您根据一项要求的一小部分创建一项测试。然后您实现代码以通过该测试。一旦通过,您就可以处理需求的另一部分。

这个想法是,尝试预先完成所有设计(无论是创建详细的类图还是创建一堆测试)意味着你会发现改变设计中的弱点太昂贵了,所以你不会改进你的代码。

这是一个例子。假设您决定使用继承来关联两个对象,但是当您开始实现这些对象时,您发现测试它们变得很困难。您会发现单独测试每个对象并通过包含将它们关联起来会容易得多。正在发生的事情是测试正在推动您的设计朝着更松散耦合的方向发展。这是 TDD 的一个非常好的结果——您正在使用测试来改进设计。

如果你提前编写了所有的测试,假设你的设计决定继承是一个不错的选择,你要么丢掉很多工作,要么你会说“现在做这样的改变太难了,所以我会只需要接受这种次优设计即可。”

您当然可以提前创建与业务规则相关的验收测试。这些被称为行为测试(行为驱动开发的一部分,或 BDD),它们非常适合从用户的角度测试软件的功能。但那些不是单元测试。单元测试用于从开发人员的角度测试代码。提前创建单元测试违背了 TDD 的目的,因为它会使测试变得更难,它会阻止你改进你的代码,并且经常会导致反抗和实践的失败。这就是为什么正确地做这件事很重要。

于 2012-11-23T04:55:23.617 回答
4

重要的是我试图通过先编写所有测试然后创建代码来做一些 TDD。

问题是“首先编写我所有的测试”最强调不是“做一些 TDD”。测试驱动开发包含大量“红-绿-重构”循环的小重复:

  • 将单元测试添加到测试套件,运行它并观察它失败(红色)
  • 将足够的代码添加到被测系统以使所有测试通过(绿色)
  • 改进被测系统的设计(通常通过删除重复),同时保持所有测试通过(重构)

如果您预先编写了整个庞大的测试套件,您将永远花费大量时间尝试进入“绿色”(所有测试通过)状态。

但是,该应用程序将无法编译,因为我的任何对象或方法都不存在。

这是任何编译语言的典型特征。这本身不是 TDD 问题。这意味着,为了观察新的测试失败,您可能必须为您当前正在处理的任何功能编写一个最小的存根,以使编译器满意。

例如,我可能会编写这个测试(使用 NUnit):

[Test]
public void DefaultGreetingIsHelloWorld()
{
    WorldGreeter target = new WorldGreeter();
    string expected = "Hello, world!";
    string actual = target.Greet();
    Assert.AreEqual(expected, actual);
}

然后我必须编写这么多代码来编译应用程序并且测试失败:

public class WorldGreeter
{
    public string Greet()
    {
        return String.Empty;
    }
}

一旦我获得了构建解决方案并且我看到了一个失败的测试,我可以添加代码以使第一个测试通过:

    public string Greet()
    {
        return "Hello, world!";
    }

一旦所有测试都通过,我就可以查看被测系统,看看可以做些什么来改进设计。但是,对于 TDD 规则来说,在进行重构之前必须经历“红色”和“绿色”这两个步骤。

我认为它的全部意义在于,当您开发测试时,您可以开始看到重复等,以便您可以在编写单行代码之前进行重构。

Martin Fowler 将重构定义为“一种用于重构现有代码体的规范技术,在不改变其外部行为的情况下改变其内部结构”(强调)。如果你没有写过一行代码,就没有什么可重构的了。

所以问题是,有没有办法做到这一点,还是我做错了?

如果你想做 TDD,那么,是的,我担心你做错了。您很可能能够交付出色的代码来做您正在做的事情,但这不是 TDD;这是否是一个问题由您自己决定。

于 2012-11-24T03:43:51.773 回答
2

您应该能够使用存根函数创建空类,不是吗?

class Whatever {
    char *foo( const char *name ) {}

    int can_wibble( Bongo *myBongo ) {}
}

然后就可以编译了。

于 2012-11-23T04:12:56.340 回答
2

不,只是编码足以验证所需用例的实现

您可以尽早定义测试用例,但是要对测试用例进行编码,您需要反复编写测试,否则会失败。然后编写一些代码,确保代码通过。

然后冲洗并重复,直到覆盖所有测试用例,

编辑地址评论。

当您构建代码时,这就是您的编程设计和故障被识别的地方。极限编程使您能够不小心更改代码,因为测试库可以保护您的需求。您的意图是好的,但现实情况是,当您通过构建代码和测试库发现设计问题和缺陷时,您将重构/重新设计测试用例。

但是恕我直言,在一般情况下,无法编译的测试实际上是失败的元测试,需要在继续之前进行纠正。它告诉你写一些代码!

于 2012-11-23T04:10:27.900 回答
0

我发现这对尚不存在的对象使用动态: https ://coderwall.com/p/0uzmdw

于 2013-09-24T16:50:55.123 回答
0

使用来自维基百科的模拟:模拟对象是以受控方式模仿真实对象行为的模拟对象。程序员通常会创建一个模拟对象来测试其他对象的行为,就像汽车设计师使用碰撞测试假人来模拟人类在车辆碰撞中的动态行为一样。

请参考这个

于 2012-11-23T04:14:18.210 回答