I've heard this quite a bit when it comes to TDD, "You should always test before you code" actually I've never done full TDD or probably havent take advantage out of it, but how is it possible to test something 开发者_Python百科that you havent even done???
Can you give me a clear example on how to do this??
Lately I've been thinking that this aspect of TDD is the same as the concept of programming by "wishful thinking" that I've read about in Structure and Interpretation of Computer Programs. For example, in lecture 2A of the MIT course taught by the authors, the following example is given for computing square roots as a function of fixed points:
(define (sqrt x)
(fixed-point
(lambda (y) (average (/ x y) y))
1))
This is shown before the fixed-point
procedure is defined. You don't really need the definition to understand that you can use a fixed-point
procedure to compute square roots. Once you see how you're going to use it, you can go about defining it.
(define (fixed-point f start)
(define tolerance 0.00001)
(define (close-enuf? u v)
(< (abs (- u v)) tolerance))
(define (iter old new)
(if (close-enuf? old new)
new
(iter new (f new))))
(iter start (f start)))
This is the same simple idea used in TDD. By writing tests for your methods before you write the methods themselves, you're showing yourself how those methods are to be used.
I too have doubts about the way the masses of the interwebs that hold test-driven development on high. While it's often a good idea, I sometimes think it is a hinderance for my workflow because mocking streaming data from a socket can sometimes be less effective than trusting that my serial port is actually working. That said, when you want to test-drive as opposed to "test-ensure" (writing unit tests afterward to prevent regressions on working code), here is what that looks like.
Essentially, here's the idea:
Writing sqrt
First, when you realize the need for a new feature or function of your software, you come up with an outline.
"My, I believe I'm going to need a function that takes a number, finds the square root, then returns that"
Now in TDD, instead of just writing the code and checking that the numbers come out right.. the TDD'er says:
"I'll write a function that that calls my non-existent function and compares the results with the ones I have in my head"
So he writes:
void testSqrt () {
if (sqrt(9.0) == 3.0) {
printf("ALL IS FINE AND DANDY HERE");
}
else {
printf("THE SYSTEM IS DOWN! THE SYSTEM IS DOWN!"); /* untz untz untz utnz */
}
}
Now, his non-existent sqrt
has a purpose in life... to make sure it always returns 3 when fed a 9! The idea is that whether he writes
float sqrt(float n) {
long i; float x, y;
const float f = 1.5;
x = n/2.0;
y = n;
i = *(long *)&y;
i = 0x5f3759df-(i >> 1);
y = *(float *) &i;
y = y*(f-(x*y*y));
y = y*(f-(x*y*y));
return n * y;
}
or
float sqrt(float n) {
float n_t = n/2.0;
int i = *(int*)&n;
i = 0x5f375a86 - (i>>1);
n = *(float*)&i;
n = n*(1.5f-n_t*n*n);
return n;
}
He can always be assured that 3 is really 3 and he hasn't eaten the brown acid. I hope this is a good summation of the thought-process involved.
Before you coding something, you define APIs for it. Then you write unit test for these APIs, and all of case will be failed(because you didn't implement them). After most case are prepared, you have a bunch of failure cases. After that, you can start to implement these APIs. As you make some progress, failure case should become fewer. Once all cases passed, your design is finished. That is TDD.
Read Test Driven Development: By Example for a thorough explanation.
A summary is: You don't write all of your tests before you write your code; you write one test, run it to be sure it fails (if it passes before you write the code, you have a bad test), then code enough to make it run. Now you know you have that functionality written and tested. At this point you refactor (if there is any refactoring to do), then move on to write the next test.
The advantage is that by adding small pieces of functionality with tests, you end up with both a full suite of tests and a slim, well-organized design. The claim is that this design will be better than what you probably would have written had you not used test-first design (and there is some evidence to back this up).
I think what is meant is that you should write your tests before you write the actual code. Doing this gives you a few advantages: You have no preconceived idea of the code looks that is to be tested. You get thoroughly acquainted with the problem you are about to tackle.
def test_add_numbers
assert_equal( 42, add(40,2) )
assert_equal( 42, add(42,0) )
assert_equal( 42, add(44,-2) )
assert_equal( 42, add(40,1,1) )
assert_equal( 42, add(42) )
assert_raises(ArgumentError,"Add requires one argument"){
add()
}
end
You haven't written an add
function yet, but now you know that it requires at least one argument, can accept more than two, and you need to be sure to account for negative and zero values.
You test to the interface you plan to support, and then iteratively write code and test until all your tests pass.
The idea is that writing the test first will:
1) help you sort out the API
2) help you write the minimum amount of code.
lets say you need a method that will take a list and a String and return a number. you start with the test, to help you define how the function will behave:
public void testMyFunc() {
List arg1 = new List()...
String arg2 = 'test';
int returned;
int expected = 3;
// ok what do i need to write
returned = myFunc(arg1, arg2);
assertEquals(expected, returned);
}
at this point the code won't even compile -- but you have a complete template of how the myFunc method is supposed to behave. Now you write the minimal amount of code to make the test pass....
Here's an example that should do just fine - http://www.theserverside.net/tt/articles/content/TDD_Chapter/ch2.pdf
The idea is similar to "wishful thinking" or "scenario based design" - you imagine that the component that you want already exists and imagine the simplest API you would like to have. This leads to interface discovery. Your test specifies the requirement/behavior, not the implementation. Once you have the test, you then proceed to make the test pass by choosing the simplest implementation that you can. It's a simple yet effective method...
A colleague recently said that the first test he writes in any test set just does:
Assert.Fail("Bootstrapping the test");
just to get into the swing of it.
精彩评论