在现实项目中如何选择TDD起点?

发布于 2024-12-13 12:13:26 字数 954 浏览 0 评论 0原文

我读过大量文章,看过大量关于 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)吗?

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

I've read tons of articles, seen tons of screencasts about TDD, but I'm still struggling with using it in real world project. My main issue is I don't know where to start, what test should be the first one.
Suppose I have to write client library calling external system's methods (e.g. notification).
I want this client to work as follows

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

There is some translation and message format preparation behind the scenes, so I'd like to hide it from my client apps.

I don't know where and how to start.
Should I make up some rough classes set for this library?
Should I start with testing NotificationClient as below

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

If so, with such test I'm forced to write complete working implementation at once, with no baby steps as TDD states. I can mock out sosmething in Client but then I have to know this thing to be mocked upfront, so I need some upfront desing to be made.

Maybe I should start from the bottom, test this message formatting component first and then use it in right client test?

What way is the right one to go?
Should we always start from top (how to deal with this huge step required)?
Can we start with any class realizing tiny part of desired feature (as Formatter in this example)?

If I'd know where to hit with my tests it'd be a lot easier for me to proceed.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

捶死心动 2024-12-20 12:13:26

我将从这一行开始:

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

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

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

当然,它一开始不会编译 - 直到我编写了一个带有采用字符串参数的构造函数和返回字符串的 clientId() 方法的 NotificationClient 类 - 但这是一部分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) 返回一个 Response,其 codeValue() 等于 1223。

)有什么帮助吗?

I'd start with this line:

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

Sounds like we need a NotificationClient, which needs a client ID. That's an easy thing to test for. My first test might look something like:

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

Of course, it won't compile at first - not until I'd written a NotificationClient class with a constructor that takes a string parameter and a clientId() method that returns a string - but that's part of the TDD cycle.

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

At this point, I can run my test and watch it fail (because I've hard-coded clientId()'s return to be an empty string). Once I've got my failing unit test, I write just enough production code (in NotificationClient) to get the test to pass:

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

Now all my tests pass, so I can consider what to do next. The obvious (well, obvious to me) next step is to make sure that I can create clients whose ID isn't "abcd1234":

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

I run my test suite and observe that testNewClientBcde2345HasClientId() fails while testNewClientAbcd1234HasClientId() passes, and now I've got a good reason to add a member variable to NotificationClient:

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

Assuming no typographical errors have snuck in, that'll get all my tests to pass, and I can move on to whatever the next step is. (In your example, it would probably be testing that notifyOnEvent(Event.WRONG_EVENT) returns a Response whose codeValue() equals 1223.)

Does that help any?

岁月蹉跎了容颜 2024-12-20 12:13:26

不要混淆连接到应用程序两端的验收测试,并形成可执行规范,其中 单元测试

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

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

Don't confuse acceptance tests that hook into each end of your application, and form an executable specifications with unit tests.

If you are doing 'pure' TDD you write an acceptance test which drives the unit tests that drive the implementation. testClientSendInvalidEventCommand is your acceptance test, but depending on how complicated things are you will delegate the implementation to multiple classes you can unit test separately.

How complicated things get before you have to split them up to test and understand them properly is why it is called Test Driven Design.

戏舞 2024-12-20 12:13:26

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

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

You can choose to let tests drive your design from the bottom up or from the top down. Both work well for different developers in different situations. Either approach will force to make some of those "upfront" design decisions but that's a good thing. Making those decisions in order to write your tests is test-driven design!

In your case you have an idea what the high level external interface to the system you are developing should be so let's start there. Write a test for how you think users of your notification client should interact with it and let it fail. This test is the basis for your acceptance or integration tests and they are going to continue failing until the features they describe are finished. That's ok.
Now step down one level. What are the steps which need to occur to provide that high level interface? Can we write an integration or unit test for those steps? Do they have dependencies you had not considered which might cause you to change the notification center interface you have started to define? Keep drilling down depth-first defining behavior with failing tests until you find that you have actually reached a unit test. Now implement enough to pass that unit test and continue. Get unit tests passing until you have built enough to pass an integration test and so on. You'll eventually have completed a depth-first construction of a tree of tests and should have a well tested feature whose design was driven by your tests.

阳光的暖冬 2024-12-20 12:13:26

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

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

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

One goal of TDD is that the testing informs the design. So the fact that you need to think about how to implement your NotificationClient is a good thing; it forces you to think of (hopefully) simple abstractions up front.

Also, TDD sort of assumes constant refactoring. Your first solution probably won't be the last; so as you refine your code the tests are there to tell you what breaks, from compile errors to actual runtime issues.

So I would just jump right in and start with the test you suggested. As you create mocks, you will need to create tests for the actual implementations of what you are mocking. You will find things make sense and need to be refactored, so you will need to modify your tests as you go. That's the way it's supposed to work...

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文