2011-11-02 38 views
5

我读过大量的文章,看过大量有关TDD的截屏视频,但我仍然在现实世界的项目中使用它。我的主要问题是我不知道从哪里开始,什么测试应该是第一个。 假设我必须编写客户端库调用外部系统的方法(例如通知)。 我希望此客户端的工作方式如下如何在真实世界的项目中选择TDD起点?

NotificationClient client = new NotificationClient("abcd1234"); // client ID 
Response code = client.notifyOnEvent(Event.LIMIT_REACHED, 100); // some params of call 

有幕后的一些翻译和消息格式的准备,所以我想从我的客户端应用程序隐藏它。

我不知道在哪里以及如何开始。 我应该为这个图书馆设置一些粗略的类吗? 我应该开始测试NotificationClient如下

public void testClientSendInvalidEventCommand() { 
    NotificationClient client = new NotificationClient(...); 
    Response code = client.notifyOnEvent(Event.WRONG_EVENT); 
    assertEquals(1223, code.codeValue()); 
} 

如果是这样,这样的测试,我不得不一次写完整的工作执行情况,作为TDD状态没有婴儿的步骤。我可以在客户端嘲弄某些东西,但是我必须知道这件事会被嘲笑,所以我需要一些前期设计。

也许我应该从底层做起,测试这个消息格式化部件,然后再使用它在正确的客户端测试?

什么方法是正确的?我们应该总是从顶端开始(如何处理这个巨大的步骤)? 我们可以从任何实现所需功能的小部分开始(如本例中的Formatter)吗?

如果我知道在哪里碰到我的测试,那对我来说会更容易。

回答

1

不要混淆acceptance tests挂钩到您的应用程序的每一端,并与unit testsexecutable specifications

如果你正在做的“纯” TDD编写验收测试驱动单元测试驱动的实现。 testClientSendInvalidEventCommand是您的验收测试,但取决于复杂的事情,您可以将实施委托给多个班级,您可以单独进行单元测试。

如何复杂的事情让你必须将它们分开来测试和了解它们之前妥善就是为什么它被称为测试驱动设计

+0

但如何找到这些类写单元测试和从哪里开始?我想我需要设置一些类,以便能够选择TDD的起点。但是,这一点应该在哪里?它应该在系统的边缘,还是可以在系统中间 - 就像Formatter提到的那样? –

+0

您可以自下而上或自上而下进行TDD。我看不出Formatter如何适合你的例子。给定一个测试,你应该写**必需的**最少量的代码让它通过。 –

0

TDD的一个目标是测试通知设计。所以你需要考虑如何实施你的事实是一件好事,它迫使你想到(希望)在前面简单的抽象。

此外,TDD排序假定不断重构。你的第一个解决方案可能不会是最后一个;因此,当您完善代码时,测试会告诉您什么会发生,从编译错误到实际的运行时问题。

所以我只是跳进来,并开始你的建议。当你创建mock时,你需要为你正在嘲笑的实际实现创建测试。你会发现事情有意义,需要重构,所以你需要随时修改你的测试。这就是你可以选择让测试驱动你的设计从下往上或从上往下它应该工作的方式...

+0

好吧,假设我在这里确定了两个协作者:用于以所需格式创建消息的格式化程序,以及用于处理连接和发送低级消息的ConnectionHandler。开始测试足够了吗?那么从哪里开始?通过测试ConnectionHandler或Formatter?或者也许有这两个蠢客的客户嘲笑? –

+0

只要选择一个或另一个 - 我认为你在分析瘫痪 - 只是做它,它会自行出来... – hvgotcodes

1

。对于不同情况下的不同开发者来说,这两种方法都很好。任何一种方法都会强制进行一些“前期”设计决策,但这是一件好事。为了编写测试而做出这些决定是测试驱动的设计!

在你的情况下,你有一个想法,你正在开发的系统的高级外部接口应该是那么让我们开始吧。编写一个测试,了解您认为通知客户端的用户应该如何与其交互并让其失败。此测试是您接受或集成测试的基础,并且在他们描述的功能完成之前,它们将继续失败。没关系。 现在降低一个级别。需要发生什么步骤来提供高级接口?我们可以为这些步骤编写集成或单元测试吗?他们有没有考虑过的依赖关系可能会导致您更改已经开始定义的通知中心界面?继续深入探索深度优先的定义行为,直到您发现实际上已经达到单元测试。现在实现足够的通过单元测试并继续。获取单元测试,直到您构建足够的可传递集成测试为止。您最终将完成测试树的深度优先构建,并且应该有一个经过良好测试的功能,其设计由您的测试驱动。

2

我这一行开始:

NotificationClient client = new NotificationClient("abcd1234"); // client ID 

听起来我们需要一个NotificationClient,这需要一个客户端ID。这是一件容易的事情来测试。我的第一个测试可能看起来像:

public void testNewClientAbcd1234HasClientId() { 
    NotificationClient client = new NotificationClient("abcd1234"); 
    assertEquals("abcd1234", client.clientId()); 
} 

当然,它不会编译起初 - 直到我写一个NotificationClient类与构造函数的字符串参数和一个clientId()方法返回一个字符串 - 但这是TDD循环的一部分。在这一点上,我可以运行我的测试,并观察它失败(因为我硬编码的clientId()返回一个空字符串)。一旦我有我的失败的单元测试,我编写足够的产品代码(在NotificationClient)来获得测试通过:

public string clientId() { 
     return "abcd1234"; 
    } 

现在我所有的测试都通过了,这样我就可以考虑接下来做什么。最明显的(当然,明显)下一步就是要确保我可以创建用户的ID不是“ABCD1234”:

public void testNewClientBcde2345HasClientId() { 
    NotificationClient client = new NotificationClient("bcde2345"); 
    assertEquals("bcde2345", client.clientId()); 
} 

我跑我的测试套件,并观察testNewClientBcde2345HasClientId()失败而testNewClientAbcd1234HasClientId()传递,现在我已经有了一个很好的理由,一个成员变量添加到NotificationClient:

public class NotificationClient { 
    private string _clientId; 
    public NotificationClient(string clientId) { 
     _clientId = clientId; 
    } 
    public string clientId() { 
     return _clientId; 
    } 
} 

假设没有印刷错误已经悄悄在,那将让我所有的测试通过,我可以继续下一步。 (在你的例子中,它可能会测试notifyOnEvent(Event.WRONG_EVENT)返回ResponsecodeValue()等于1223.)

这有帮助吗?