我已经阅读了许多关于 TDD 主题的书籍和网站,它们都很有意义,尤其是 Kent Beck 的书。然而,当我尝试自己做 TDD 时,我发现自己盯着键盘想知道如何开始。有你使用的过程吗?你的思考过程是什么?你如何识别你的第一个测试?
大多数关于该主题的书籍都很好地描述了 TDD 是什么,但没有描述如何在现实世界的重要应用程序中实践TDD。你是怎么做TDD的?
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.
我曾经有同样的问题。我曾经通过启动一个窗口设计器来为我想要实现的第一个功能创建 UI 来开始大多数开发。由于 UI 是最难测试的事情之一,因此这种工作方式并不能很好地转化为 TDD。
我发现 Presenter First 上的 atomic object 论文非常有帮助。我仍然从设想我想要实现的用户操作开始(如果你有用例,这是一个很好的开始方式)并使用 MVP 或 MVC-ish 模型我开始为第一个屏幕的演示者编写测试。通过模拟视图直到演示者工作,我可以通过这种方式快速入门。http://www.atomicobject.com/pages/Presenter+First这里有更多关于以这种方式工作的信息。
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 用例
就是这样。这很简单,但我认为这很耗时。不过我喜欢它,我坚持下去。:)
如果我有更多时间,我会尝试在 Enterprise Architect 中建模一些时序图。
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.
我认为您不应该真正从 TDD 开始。说真的,你的规格在哪里?您是否已就您的系统的通用/粗略整体设计达成一致,这可能适合您的应用程序?我知道 TDD 和敏捷不鼓励预先进行大设计,但这并不意味着您不应该在通过实施该设计进行 TDD 之前先进行预先设计。
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.
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.