Assuming we are implementing using TDD a Stack
class, we would need, for each bit of functionality of our Stack class, to add a new test that exercises it:
[TestMethod] public void Should_Be_Empty_After_Ins开发者_如何学编程tantiation()
[TestMethod] public void Should_Not_Be_Empty_After_Pushing_One_Item()
...
Now, on the other hand, when doing Unit-Tests, one should focus on what external behaviour our class is supposed to provide, so the set of Unit-Tests check that all the expected contracts for my Stack interface are fulfilled.
My question is in how to conciliate those two aspects.
For example, assuming my Stack
uses internally an array with an initial size of 8, I'll want it to grow if my user wants to insert a 9th item. For adding that resizing functionality, I'll want to have at least one test that drives my class code in that direction (am I right?).
On the other hand, that would be adding a Unit-Test (or isn't this really a Unit-Test?) that exercises not the actual contract of the class(I am assuming the user doesn't care for the internal implementation of the Stack) but its implementation.
So we have a twist here, that I don't know how to solve. Am I making any confusion of concepts here?
Thanks
Edit
After much googling I came to the following link that seems to deal with this issue: http://stephenwalther.com/blog/archive/2009/04/11/tdd-tests-are-not-unit-tests.aspx
You could write a test that pushes a ninth item onto the stack. That would clearly fail if you don't have any resizing logic. However, hard-coding the 9 into the test seems like a bad idea, since you would be incorporating internal implementation details of the Stack into the tests.
Now, the writing of TDD tests often informs the author of possible gaps in his API. In this case, the test would like to be able to specify the initial pre-allocation size of the Stack. Then it could set it to 8 or 2 or whatever and push one more item than that. And, it is not unrealistic to think that other clients might want that too (it is similar to the reserve method of std::vector, for example). So, I would think about adding a constructor parameter to Stack to specify initial reserved size, default it to 8, and add a Should_Not_Error_When_Pushing_More_Items_Than_Initial_Size test.
I would say they are the same thing when refering to TDD and external behaviour of a particular class. When writing TDD style code you are focusing on what the behaviour is when using the public api of the class. So your first two test cases are correct since they test something public on the class (size of stack).
As for internal implementation and whether you use an array, the test is not concerned with how it is done, just that the functionality works against the given test. You may choose to implement it differently at a later stage and your tests will verify that the behaviour remains unchanged (tests don't fail after the refactor)
You've got a couple of aspects of behaviour here:
- a stack which allows items to be pushed and popped
- a storage element which grows its internal array when another item is added.
If you're having problems testing them both, you could always put the behaviour which grows the internal array into another class, keeping it separate from the behaviour of the stack. It's the Single Responsibility Principle in overdrive! You could then just use a very large array in testing the behaviour of your stack, and check that the stack delegates its responsibilities correctly where growing is concerned using mocks (you can roll your own if you don't have a mocking framework).
I've found this a useful technique for timing, threading, or anything else which encapsulates internal behaviour that can't necessarily be seen from the outside. Of course, you sacrifice performance to some degree. If that's important then just worry about the behaviour of the stack from the point of view of its calling class, and use system performance and profiling tests to pick up the rest.
精彩评论