开发者

TDD is a top-bottom or bottom-top design?

开发者 https://www.devze.com 2023-02-25 07:59 出处:网络
In my memory, most people told me I should design from top to bottom. If I want to implement a web page, I should image or draw this page on paper and then divide it into some functionality. For each

In my memory, most people told me I should design from top to bottom. If I want to implement a web page, I should image or draw this page on paper and then divide it into some functionality. For each functionality, I try design the external API, and implement their inside respectively.

But in TDD, they say I should consider a very very small functionality(a method?) first, write its test, fail, implement it and pass test. Composing them is the last step. I can't image how it gets good API.

And most strangely, they say TDD is not only unit tests and also function tests. I think it means top-bottom. If there is a functionality A composed of methods B, C and D. Because of TDD I write the function test for A first. But... B, C, D are all unimplemented. Should I use three stubs? If B depends on other three methods?

I used TDD to write some small programs. But when I striked an appl开发者_如何学Goication with GUI, I got stuck.


Since TDD starts with what you can see from the outside (of whatever item you are working on at the moment), I'm not sure how it could qualify as bottom-up.

Taking TDD to the extreme (e.g., as in XP, aka extreme programming), you would certainly start from the end-user perspective, and only ever write as much code as you need to pass the tests created so far. If you find yourself starting with the tests for some internal function before reaching the point where the higher-level tests (plus good design for the code you are writing to make those tests pass) require this routine, you are working on some other paradigm, not strict TDD – because there was no test telling you to write that method in the first place. Not that this is necessarily a bad thing, but any problems you have with that is not really one of the TDD methodology.

For GUI programming, of course, you have the standard problem of automating tests, even before you created code. I only know of good tools for web apps for that; if you have good pointers on this topic in the desktop case, I'd sure love to see them.


I've been heavily writing rspec tests for my rails projects keeping a ratio of about 1:1.6 between code/tests. I never really had an issue of what to write first or what it depends on. If the method A that i want to write consists of B and C, then i would first implement B and C, again using the proper testing. To me the sequence is not so important as long as the tests are good and precise.

So, I don't really use stubs the way you describe it, but only if the functionality is already there and i just want to bypass/circumvent it.

BTW, it is considered a top-down approach. This is an excerpt from Rspec Book :

If you ask experienced software delivery folks why they run a project like that, front-loading it with all the planning and analysis, then getting into the detailed design and programming, and only really integrating and testing it at the end, they will gaze into the distance, looking older than their years, and patiently explain that this is to mitigate against the exponential cost of change—the principle that introducing a change or discovering a defect becomes exponentially more expensive the later you discover it. The top-down approach seems the only sensible way to hedge against the possibility of discovering a defect late in the day.


I would say it's top down. Say I had a single PDF that had 4 distinct documents in it and I was writing software to split them into 4 individual documents instead of a single document, the first test I would probably write is:

// Note: Keeping this very abstract
@Test
public void splitsDocumentsAccordingToDocumentType() {
   List docs = new DocumentProcessor().split(new SinglePdfWithFourDocs());
   assertEquals(4, docs());
   ...
}

I would consider the DocumentProcessor.split() method to be similar to "A" in your example. I could now implement the entire algorithm within the single method split and make the tests pass. I don't even need "B" or "C" right? Knowing that you're a good developer and you cringe at the thought of that 1500 line method you would start to look for ways to restructure your code to a more suitable design. Maybe you see that two additional objects (responsibilities) could be split out of this code:

1) Parsing the contents of the file to find the individual documents and 2) Reading and writing of the document from the file system

Let's tackle #1 first. Use a couple of "Extract Method" refactorings to localize code related to the parsing of the contents then an "Extract Class" refactoring, pulling out those methods into a class named, say DocumentParser. This could be analagous to "B" in your example. If you'd like, you could move tests related to document parsing from your DocumentProcessorTest to a new DocumentParserTest and mock or stub the DocumentParser in the DocumentProcessorTest.

For #2 it's pretty much lather, rinse, repeat and you'll end up with something like a DocumentSerializer class, AKA "C". You can mock this as well in your DocumentProcessorTest and you now have no file I/O and have test driven a component that has two additional collaborators without having to design your entire class (with individual methods). Notice that we took an "outside in" approach, which really enables the refactoring.

Thanks

0

精彩评论

暂无评论...
验证码 换一张
取 消