【问题标题】:How to choose TDD starting point in a real world project?如何在现实世界的项目中选择 TDD 起点?
【发布时间】:2011-11-06 08:04:06
【问题描述】:

我已阅读大量文章,看过大量有关 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 所述的婴儿步骤。我可以在 Client 中模拟出一些东西,但是我必须预先知道要模拟这个东西,所以我需要进行一些前期设计。

也许我应该从底层开始,先测试这个消息格式化组件,然后在正确的客户端测试中使用它?

哪条路是正确的? 我们是否应该始终从头开始(如何处理所需的这一巨大步骤)? 我们可以从实现所需功能的一小部分的任何类开始(如本例中的 Formatter)吗?

如果我知道在哪里进行测试,那么我继续进行会容易得多。

【问题讨论】:

    标签: unit-testing tdd


    【解决方案1】:

    我将从这一行开始:

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

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

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

    当然,一开始它不会编译——直到我编写了一个 NotificationClient 类,该类具有一个接受字符串参数的构造函数和一个返回字符串的 clientId() 方法——但这是 TDD 循环的一部分。

    public class NotificationClient {
        public NotificationClient(string clientId) {
        }
        public string clientId() {
            return "";
        }
    }
    

    此时,我可以运行我的测试并观察它失败(因为我已将 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。)

    这有帮助吗?

    【讨论】:

      【解决方案2】:

      不要将钩子到应用程序每一端的acceptance tests 混淆,并与unit tests 形成executable specifications

      如果您正在执行“纯”TDD,您需要编写一个验收测试来驱动驱动实现的单元测试。 testClientSendInvalidEventCommand 是您的验收测试,但根据事情的复杂程度,您会将实现委托给多个可以单独进行单元测试的类。

      在您必须拆分它们以测试和正确理解它们之前,事情变得多么复杂,这就是为什么它被称为测试驱动设计

      【讨论】:

      • 但是如何找到那些类来编写单元测试以及从哪里开始呢?我想我需要预先设置一些类才能选择 TDD 的起点。但那么这个点应该在哪里呢?它应该在系统的边缘,还是可以在系统的中间——就像提到的格式化程序一样?
      • 您可以自下而上或自上而下进行 TDD。我看不出格式化程序如何适合您的示例。给定一个测试,您应该编写 最少 量的代码以使其通过。
      【解决方案3】:

      您可以选择让测试自下而上或自上而下推动您的设计。两者都适用于不同情况下的不同开发人员。任何一种方法都将迫使做出一些“预先”的设计决策,但这是一件好事。为编写测试做出这些决定是测试驱动的设计!

      在您的情况下,您知道您正在开发的系统的高级外部接口应该是什么,所以让我们从那里开始。写一个测试,看看你认为通知客户端的用户应该如何与它交互并让它失败。此测试是您的验收或集成测试的基础,它们将继续失败,直到它们描述的功能完成。没关系。 现在降一级。提供高级接口需要执行哪些步骤?我们可以为这些步骤编写集成或单元测试吗?他们是否有你没有考虑过的依赖关系,这可能会导致你改变你已经开始定义的通知中心界面?继续深入研究失败测试的深度优先定义行为,直到您发现您实际上已经完成了单元测试。现在实现足以通过该单元测试并继续。让单元测试通过,直到你已经构建了足够的东西来通过集成测试等等。您最终将完成测试树的深度优先构建,并且应该拥有一个经过良好测试的功能,其设计是由您的测试驱动的。

      【讨论】:

        【解决方案4】:

        TDD 的一个目标是让测试为设计提供信息。因此,您需要考虑如何实现您的NotificationClient 是一件好事;它迫使您(希望)预先考虑简单的抽象。

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

        所以我会直接进入并从您建议的测试开始。在创建模拟时,您需要为模拟的实际实现创建测试。你会发现事情是有意义的,需要重构,所以你需要随时修改你的测试。这就是它应该工作的方式......

        【讨论】:

        • 好的,假设我在这里确定了两个合作者:Formatter 用于创建所需格式的消息,ConnectionHandler 用于处理连接和发送低级消息。是否足以开始测试?那从哪里开始呢?通过测试 ConnectionHandler 还是 Formatter?或者可能是客户端与这两个部门被模拟出来了?
        • 随便选一个——我认为你处于分析瘫痪状态——只要这样做,它就会自行解决......
        猜你喜欢
        • 2018-04-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-12-17
        • 1970-01-01
        • 2013-03-07
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多