It's easier than you think, actually. You just use TDD on each individual class. Every public method that you have in the class should be tested for all possible outcomes. So the "proof of concept" TDD examples you see can also be used in a relatively large application which has many hundreds of classes.
Another TDD strategy you could use is simulating application test runs themselves, by encapsulating the main app behavior. For example, I have written a framework (in C++, but this should apply to any OO language) which represents an application. There are abstract classes for initialization, the main runloop, and shutting down. So my main() method looks something like this:
int main(int argc, char *argv[]) {
int result = 0;
myApp &mw = getApp(); // Singleton method to return main app instance
if(mw.initialize(argc, argv) == kErrorNone) {
result = mw.run();
}
mw.shutdown();
return(result);
}
The advantage of doing this is twofold. First of all, all of the main application functionality can be compiled into a static library, which is then linked against both the test suite and this main.cpp stub file. Second, it means that I can simulate entire "runs" of the main application by creating arrays for argc & argv[], and then simulating what would happen in main(). We use this process to test lots of real-world functionality to make sure that the application generates exactly what it's supposed to do given a certain real-world corpus of input data and command-line arguments.
Now, you're probably wondering how this would change for an application which has a real GUI, web-based interface, or whatever. To that, I would simply say to use mock-ups to test these aspects of the program.
But in short, my advice boils down to this: break down your test cases to the smallest level, then start looking upwards. Eventually the test suite will throw them all together, and you'll end up with a reasonable level of automated test coverage.
I used to have the same problem. I used to start most development by starting a window-designer to create the UI for the first feature I wanted to implement. As the UI is one of the hardest things to test this way of working doesn't translate very well to TDD.
I found the atomic object papers on Presenter First very helpful. I still start by envisioning user actions that I want to implement (if you've got usecases that's a great way to start) and using a MVP or MVC-ish model I start with writing a test for the presenter of the first screen. By mocking up the view until the presenter works I can get started really fast this way. http://www.atomicobject.com/pages/Presenter+First here's more information on working this way.
If you're starting a project in a language or framework that's unknown to you or has many unknown you can start out doing a spike first. I often write unit tests for my spikes too but only to run the code I'm spiking. Doing the spike can give you some input on how to start your real project. Don't forget to throw away your spike when you start on your real project
I don't think you should really begin with TDD. Seriously, where are your specs? Have you agreed on a general/rough overall design for your system yet, that may be appropriate for your application? I know TDD and agile discourages Big Design Up-Front, but that doesn't mean that you shouldn't be doing Design Up-Front first before TDDing your way through implementing that design.
I agree that it is especially hard to bootstrap the process.
I usually try to think of the first set of tests like a movie script, and maybe only the first scene to the movie.
Actor1 tells Actor2 that the world is in trouble, Actor2 hands back a package, Actor1 unpacks the package, etc.
That is obviously a strange example, but I often find visualizing the interactions a nice way to get over that initial hump. There are other analogous techniques (User stories, RRC cards, etc.) that work well for larger groups, but it sounds like you are by yourself and may not need the extra overhead.
Also, I am sure the last thing that you want to do is read another book, but the guys at MockObjects.com have a book in early draft stages, currently titled Growing Object-Oriented Software, Guided by Tests. The chapters that are currently for review may give you some further insight in how to start TDD and continue it throughout.
The problem is that you are looking at your keyboard wondering what tests you need to write.
Instead think of the code that you want to write, then find the first small part of that code, then try and think of the test that would force you to write that small bit of code.
In the beginning it helps to work in very small pieces. Even over the course of a single day you'll be working in larger chunks. But any time you get stuck just think of the smallest piece of code that you want to write next, then write the test for it.
He insists on the fact that you have to separate business logic from object creation code (i.e. to avoid mixing logic with 'new' operator), by using the Dependency Injection pattern. He also explains how the Law of Demeter is important to testable code. He's mainly focused on Java code (and Guice) but his principles should apply to any language really.
The easiest is to start with a class that has no dependencies, a class that is used by other classes, but does not use another class. Then you should pick up a test, asking yourself "how would I know if this class (this method) is implemented correctly ?".
Then you could write a first test to interrogate your object when it's not initialized, it could return NULL, or throw an exception. Then you can initialize (perhaps only partially) your object, and test test it returns something valuable. Then you can add a test with another initialization value - should behaves the same way. At that time, I usually test an error condition - such as trying to initialize the object with a invalid value.
When you're done with the method, goes to another method of the same class until you're done with the whole class.
Then you could pick another class - either another independent class, or class that use the first class you've implemented.
If you go with a class that relies on your first class, I think it is acceptable to have your test environment - or your second class - instantiating the first class as it has be fully tested. When one test about the class fails, you should be able to determine in which class the problem lies.
Should you discover a problem in the first class, or ask whether it will behave correctly under some particular conditions, then write a new test.
If climbing up the dependencies you think that the tests you're writing are spanning over to many classes to be qualified as unit-tests, then you can use a mock object to isolate a class from the rest of the system.
If you already have your design - as you indicated in a comment in the answer from Jon LimJap, then you're not doing pure TDD since TDD is about using unit tests to let your design emerge.
That being said, not all shops allow strict TDD, and you have a design at hand, so let's use it and do TDD - albeit it would be better to say Test-First-Programming but that's not the point, as that's also how I started TDD.
发布评论
评论(8)
实际上,这比你想象的要容易。 您只需在每个单独的类上使用 TDD。 类中的每个公共方法都应该测试所有可能的结果。 因此,您看到的“概念验证”TDD 示例也可以用于具有数百个类的相对较大的应用程序。
您可以使用的另一种 TDD 策略是通过封装主要应用程序行为来模拟应用程序测试运行本身。 例如,我编写了一个代表应用程序的框架(用 C++ 编写,但这应该适用于任何 OO 语言)。 有用于初始化、主运行循环和关闭的抽象类。 所以我的 main() 方法看起来像这样:
这样做的好处是双重的。 首先,所有主要应用程序功能都可以编译到静态库中,然后将其链接到测试套件和此 main.cpp 存根文件。 其次,这意味着我可以通过为 argc 和 argc 创建数组来模拟主应用程序的整个“运行”。 argv[],然后模拟 main() 中会发生什么。 我们使用这个过程来测试许多现实世界的功能,以确保应用程序在给定特定的现实世界输入数据和命令行参数语料库的情况下准确地生成它应该执行的操作。
现在,您可能想知道对于具有真正的 GUI、基于 Web 的界面或其他内容的应用程序来说,这将如何改变。 对此,我只想说使用模型来测试程序的这些方面。
但简而言之,我的建议可以归结为:将测试用例分解到最小的级别,然后开始向上查找。 最终,测试套件会将它们全部组合在一起,您最终将获得合理水平的自动化测试覆盖率。
It's easier than you think, actually. You just use TDD on each individual class. Every public method that you have in the class should be tested for all possible outcomes. So the "proof of concept" TDD examples you see can also be used in a relatively large application which has many hundreds of classes.
Another TDD strategy you could use is simulating application test runs themselves, by encapsulating the main app behavior. For example, I have written a framework (in C++, but this should apply to any OO language) which represents an application. There are abstract classes for initialization, the main runloop, and shutting down. So my main() method looks something like this:
The advantage of doing this is twofold. First of all, all of the main application functionality can be compiled into a static library, which is then linked against both the test suite and this main.cpp stub file. Second, it means that I can simulate entire "runs" of the main application by creating arrays for argc & argv[], and then simulating what would happen in main(). We use this process to test lots of real-world functionality to make sure that the application generates exactly what it's supposed to do given a certain real-world corpus of input data and command-line arguments.
Now, you're probably wondering how this would change for an application which has a real GUI, web-based interface, or whatever. To that, I would simply say to use mock-ups to test these aspects of the program.
But in short, my advice boils down to this: break down your test cases to the smallest level, then start looking upwards. Eventually the test suite will throw them all together, and you'll end up with a reasonable level of automated test coverage.
我曾经也有同样的问题。 我过去常常通过启动窗口设计器来为我想要实现的第一个功能创建 UI 来开始大多数开发。 由于 UI 是最难测试的东西之一,因此这种工作方式不能很好地转化为 TDD。
我发现 Presenter First 上的原子对象论文非常有帮助。 我仍然首先设想要实现的用户操作(如果您有用例,这是一个很好的开始方式),并使用 MVP 或 MVC 式模型,我首先为第一个屏幕的演示者编写测试。 通过模拟视图直到演示者工作,我可以通过这种方式快速开始。 http://www.atomicobject.com/pages/Presenter+First 这里有更多信息以这种方式工作。
如果您正在使用您不熟悉或有很多未知的语言或框架启动一个项目,您可以首先开始进行峰值。 我也经常为我的峰值编写单元测试,但只是为了运行我正在峰值的代码。 进行峰值可以为您提供一些有关如何开始实际项目的信息。 当你开始真正的项目时,不要忘记扔掉你的钉子
I used to have the same problem. I used to start most development by starting a window-designer to create the UI for the first feature I wanted to implement. As the UI is one of the hardest things to test this way of working doesn't translate very well to TDD.
I found the atomic object papers on Presenter First very helpful. I still start by envisioning user actions that I want to implement (if you've got usecases that's a great way to start) and using a MVP or MVC-ish model I start with writing a test for the presenter of the first screen. By mocking up the view until the presenter works I can get started really fast this way. http://www.atomicobject.com/pages/Presenter+First here's more information on working this way.
If you're starting a project in a language or framework that's unknown to you or has many unknown you can start out doing a spike first. I often write unit tests for my spikes too but only to run the code I'm spiking. Doing the spike can give you some input on how to start your real project. Don't forget to throw away your spike when you start on your real project
我首先考虑需求。
foreach UseCase
就是这样。 这很简单,但我认为这很耗时。 但我喜欢它并且坚持下去。 :)
如果我有更多时间,我会尝试在 Enterprise Architect 中对一些顺序图进行建模。
I start with thinkig of requirements.
foreach UseCase
That's it. It's pretty simple, but I think it's time consuming. I like it though and I stick to it. :)
If I have more time I try to model some sequential diagrams in Enterprise Architect.
我认为你不应该真正从 TDD 开始。 说真的,你的规格在哪里? 您是否已就系统的一般/粗略总体设计达成一致,该设计可能适合您的应用程序? 我知道 TDD 和敏捷不鼓励预先进行大型设计,但这并不意味着在以 TDD 方式实现该设计之前,您不应该首先进行预先设计。
I don't think you should really begin with TDD. Seriously, where are your specs? Have you agreed on a general/rough overall design for your system yet, that may be appropriate for your application? I know TDD and agile discourages Big Design Up-Front, but that doesn't mean that you shouldn't be doing Design Up-Front first before TDDing your way through implementing that design.
我同意引导这个过程特别困难。
我通常会尝试将第一组测试视为电影剧本,也许只是电影的第一个场景。
,但我经常发现可视化交互是克服最初困难的好方法。 还有其他类似的技术(用户故事、RRC 卡等)非常适合较大的群体,但听起来好像您是独自一人,可能不需要额外的开销。
另外,我确信您最不想做的就是阅读另一本书,但是 MockObjects.com 的人有一本书处于早期草稿阶段,当前标题为 不断发展的面向对象软件,以测试为指导。 当前供回顾的章节可能会让您进一步了解如何开始 TDD 并继续进行下去。
I agree that it is especially hard to bootstrap the process.
I usually try to think of the first set of tests like a movie script, and maybe only the first scene to the movie.
That is obviously a strange example, but I often find visualizing the interactions a nice way to get over that initial hump. There are other analogous techniques (User stories, RRC cards, etc.) that work well for larger groups, but it sounds like you are by yourself and may not need the extra overhead.
Also, I am sure the last thing that you want to do is read another book, but the guys at MockObjects.com have a book in early draft stages, currently titled Growing Object-Oriented Software, Guided by Tests. The chapters that are currently for review may give you some further insight in how to start TDD and continue it throughout.
问题是您看着键盘想知道需要编写哪些测试。
相反,想想您想要编写的代码,然后找到该代码的第一小部分,然后尝试考虑迫使您编写那一小段代码的测试。
一开始,它有助于以非常的小片段进行工作。 即使在一天的时间内,您也会分更大的部分工作。 但是,每当您遇到困难时,只需考虑接下来要编写的最小代码段,然后为其编写测试即可。
The problem is that you are looking at your keyboard wondering what tests you need to write.
Instead think of the code that you want to write, then find the first small part of that code, then try and think of the test that would force you to write that small bit of code.
In the beginning it helps to work in very small pieces. Even over the course of a single day you'll be working in larger chunks. But any time you get stuck just think of the smallest piece of code that you want to write next, then write the test for it.
有时您不知道如何进行 TDD,因为您的代码不“测试友好”(易于测试)。
由于一些良好的实践,您的类可以变得更容易单独测试,以实现真正的单元测试。
我最近看到了一位 Google 员工的博客,其中描述了如何设计类和方法,以便它们更容易测试。
这是他最近的演讲之一我推荐的。
他坚持认为,您必须通过使用依赖注入模式将业务逻辑与对象创建代码分开(即避免将逻辑与“new”运算符混合)。 他还解释了德米特定律对于可测试代码的重要性。 他主要关注 Java 代码(以及 Guice),但他的原则应该适用于任何语言真的。
Sometimes you don't know how to do TDD because your code isn't "test friendly" (easily testable).
Thanks to some good practices your classes can become easier to test in isolation, to achieve true unit testing.
I recently came across a blog by a Google employee, which describes how you can design your classes and methods so that they are easier to test.
Here is one of his recent talks which I recommand.
He insists on the fact that you have to separate business logic from object creation code (i.e. to avoid mixing logic with 'new' operator), by using the Dependency Injection pattern. He also explains how the Law of Demeter is important to testable code. He's mainly focused on Java code (and Guice) but his principles should apply to any language really.
最简单的方法是从一个没有依赖关系的类开始,该类被其他类使用,但不使用另一个类。 然后你应该进行一个测试,问自己“我如何知道这个类(这个方法)是否正确实现?”。
然后你可以编写第一个测试来询问你的对象,当它没有初始化时,它可能返回 NULL,或者抛出异常。 然后你可以初始化(也许只是部分)你的对象,并测试它返回一些有价值的东西。 然后,您可以添加具有另一个初始化值的测试 - 行为应该相同。 当时,我通常会测试错误条件 - 例如尝试使用无效值初始化对象。
完成该方法后,转到同一类的另一个方法,直到完成整个类。
然后你可以选择另一个类 - 另一个独立的类,或者使用你实现的第一个类的类。
如果您使用依赖于您的第一个类的类,我认为让您的测试环境 - 或您的第二个类 - 实例化第一个类是可以接受的,因为它已经过充分测试。 当有关该类的一项测试失败时,您应该能够确定问题出在哪个类中。
如果您在第一堂课中发现问题,或者询问它在某些特定条件下是否会正确运行,则编写一个新的测试。
如果爬升依赖关系,您认为您正在编写的测试跨越了许多类以符合单元测试的资格,那么您可以使用模拟对象将类与系统的其余部分隔离。
如果您已经有了自己的设计 - 正如您在 Jon LimJap 的答案中的评论中指出的那样,那么您就不是在进行纯粹的 TDD,因为 TDD 是关于使用单元测试来让您的设计显现出来。
话虽这么说,并非所有商店都允许严格的 TDD,并且您手头有一个设计,所以让我们使用它并进行 TDD - 尽管最好说“测试优先编程”,但这不是重点,因为这也是我的方式开始了TDD。
The easiest is to start with a class that has no dependencies, a class that is used by other classes, but does not use another class. Then you should pick up a test, asking yourself "how would I know if this class (this method) is implemented correctly ?".
Then you could write a first test to interrogate your object when it's not initialized, it could return NULL, or throw an exception. Then you can initialize (perhaps only partially) your object, and test test it returns something valuable. Then you can add a test with another initialization value - should behaves the same way. At that time, I usually test an error condition - such as trying to initialize the object with a invalid value.
When you're done with the method, goes to another method of the same class until you're done with the whole class.
Then you could pick another class - either another independent class, or class that use the first class you've implemented.
If you go with a class that relies on your first class, I think it is acceptable to have your test environment - or your second class - instantiating the first class as it has be fully tested. When one test about the class fails, you should be able to determine in which class the problem lies.
Should you discover a problem in the first class, or ask whether it will behave correctly under some particular conditions, then write a new test.
If climbing up the dependencies you think that the tests you're writing are spanning over to many classes to be qualified as unit-tests, then you can use a mock object to isolate a class from the rest of the system.
If you already have your design - as you indicated in a comment in the answer from Jon LimJap, then you're not doing pure TDD since TDD is about using unit tests to let your design emerge.
That being said, not all shops allow strict TDD, and you have a design at hand, so let's use it and do TDD - albeit it would be better to say Test-First-Programming but that's not the point, as that's also how I started TDD.