tags:

views:

178

answers:

7

A few weeks ago I started my first project with TDD. Up to now, I have only read one book about it.

My main concern: How to write tests for complex methods/classes. I wrote a class that calculates a binomial distribution. Thus, a method of this class takes n, k, and p as input, and calculates the resp. probability. (In fact it does a bit more, that's why I had to write it myself, but let's stick to this description of the class, for ease of the argument.)

What I did to test this method is: copying some tables with different n I found in the web into my code, picking randomly an entry in this table, feeded the resp. values for n, k, and p into my function, and looked whether the result was near the value in the table. I repeat this a number of times for every table.

This all works well now, but after writing the test, I had to tank for a few hours to really code the functionality. From reading the book, I had the impression that I should not code longer than a few minutes, until the test shows green again. What did I do wrong here? Of course I have broken this task down in a lot of methods, but they are all private.

A related question: Was it a bad idea to pick randomly numbers from the table? In case of an error, I will display the random-seed used by this run, so that I can reproduce the bug.

A: 

It's difficult to answer your question without knowing a little bit more about the things you wanted to implement. It sounds like they were not easily partinioable in testable parts. Either the functionality works as a whole or it doesn't. If this is the case, it's no wonder you tool hours to implement it.

As to your second question: Yes, I think it's a bad idea to make the test fixture random. Why did you do this in the first place? Changing the fixture changes the test.

+1  A: 

You should TDD using baby steps. Try thinking of tests that will require less code to be written. Then write the code. Then write another test, and so on.

Try to break your problem into smaller problems (you probably used some other methods to have your code completed). You could TDD these smaller methods.

--EDIT - based on the comments

Testing private methods is not necessarily a bad stuff. They sometimes really contain implementation details, but sometimes they might also act like an interface (in this case, you could follow my suggestion next paragraph).

One other option is to create other classes (implemented with interfaces that are injected) to take some of the responsibilities (maybe some of those smaller methods), and test them separately, and mock them when testing your main class.

Finally, I don't see spending more time coding as a really big problem. Some problems are really more complex to implement than to test, and require much thinking time.

Samuel Carrijo
Yes, I could TDD these smaller methods, but then I had to test private methods. And as I understood it, they are part of the implementation-details, and should not tested.
matthias
just edited my answer
Samuel Carrijo
If it helps to do TDD the way you want to, you could start with these private methods being public/protected, and test drive those smaller pieces of functionality. When you have created the public methods, which call down to these, you can remove the tests which directly test the private methods.You should also ask yourself if having them at protected/default visibility, in order to test from the same package, is going to spoil encapsulation. If not, I'd say that's a fair trade off.
Grundlefleck
+1  A: 

"I had the impression that I should not code longer than a few minutes, until the test shows green again. What did I do wrong here?"

Westphal is correct up to a point.

Some functionality starts simple and can be tested simply and coded simply.

Some functionality does not start out simple. Simple is hard to achieve. EWD says that simplicity is not valued because it is so difficult to achieve.

If your function body is hard to write, it isn't simple. This means you have to work much harder to reduce it to something simple.

After you eventually achieve simplicity, you, too, can write a book showing how simple it is.

Until you achieve simplicity, it will take a long time to write things.

"Was it a bad idea to pick randomly numbers from the table?"

Yes. If you have sample data, run your test against all the sample data. Use a loop or something, and test everything you can possibly test.

Don't select one row -- randomly or otherwise, select all rows.

S.Lott
Thanks for your comment. I get the feeling that you are right. I simply have to learn a lot. And as long as I do not have months/years of experience, I have to accept that my solutions are not optimal.
matthias
It's not a question of "optimal". The issue is that "simple" is really, really hard to achieve. Precious few can do a good job of achieving true simplicity. Contrived book examples are the worst, since the author has had a lot of time to get to simple. We all have to work at it; they can conceal the vast amount of effort required to get to a simple example.
S.Lott
A: 

Avoid developing complex methods with TDD until you have developed simple methods as building blocks for the more complex methods. TDD would typically be used to create a quantity of simple functionality which could be combined to produce more complex behaviour. Complex methods/classes should always be able to be broken down into simpler parts, but it is not always obvious how and is often problem specific. The test you have written sounds like it might be more of an integration test to make sure all the components work together correctly, although the complexity of the problem you describe only borders on the edge of requiring a set of components to solve it. The situation you describe sounds like this:

class A { public doLotsOfStuff() // Call doTask1..n private doTask1() private doTask2() private doTask3() }

You will find it quite hard to develop with TDD if you start by writing a test for the greatest unit of functionality (i.e. doLotsOfStuff()). By breaking the problem down into more mangeable chunks and approaching it from the end of simplest functionality you will also be able to create more discrete tests (much more useful than tests that check for everything!). Perhaps your potential solution could be reformulated like this:

class A{ public doLotsOfStuff() // Call doTask1..n public doTask1() public doTask2() public doTask3() }

Whilst your private methods may be implementation detail that is not a reason to avoid testing them in isolation. Just like many problems a divide-and-conquer approach would prove affective here. The real question is what size is a suitably testable and maintainable chunk of functionality? Only you can answer that based on your knowledge of the problem and your own judgement of applying your abilities to the task.

J.Churchill
+1  A: 

You are correct about short quick refactors, I rarely go more than a few minutes between rebuild/test no matter how complicated the change. It takes a little practice.

The test you described is more of a system test than a unit test though. A unit test tries never to test more than a single method--in order to reduce complexity you should probably break your problem down into quite a few methods.

The system test should probably be done after you have built up your functionality with small unit tests on small straight-forward methods.

Even if the methods are just taking a part of the formula out of a longer method, you get the advantage of readability (the method name should be more readable than the formula part it replaces) and if the methods are final the JIT should inline them so you don't lose any speed.

On the other hand, if your formula isn't that big, maybe you just write it all in one method and test it like you did and take the downtime--rules are made to be broken.

Bill K
+4  A: 

I don't agree with people saying that it's ok to test private code, even if you make them into separate classes. You should test entry points to your application (or your library, if it's a library you're coding). When you test private code, you limit your re-factoring possibilities for later (because refactoring your privates classes mean refactoring your test code, which you should refrain doing). If you end up re-using this private code elsewhere, then sure, create separate classes and test them, but until you do, assume that You Ain't Gonna Need It.

To answer your question, I think that yes, in some cases, it's not a "2 minutes until you go green" situation. In those cases, I think it's ok for the tests to take a long time to go green. But most situations are "2 minutes until you go green" situations. In your case (I don't know squat about binomial distribution), you wrote you have 3 arguments, n, k and p. If you keep k and p constant, is your function any simpler to implement? If yes, you should start by creating tests that always have constant k and p. When your tests pass, introduce a new value for k, and then for p.

Virgil Dupras
A: 

I think the style of testing you have is totally appropriate for code thats primarily a computation. Rather than pick a random row from your known results table, it'd be better to just hardcode the significant edge cases. This way your tests are consistently verifying the same thing, and when one breaks you know what it was.

Yes TDD prescribes short spans from test to implementation, but what you've down is still well beyond standards you'll find in the industry. You can now rely on the code to calculate what how it should, and can refactor / extend the code with a degree of certainty that you aren't breaking it.

As you learn more testing techniques you may find different approach that shortens the red/green cycle. In the meantime, don't feel bad about it. Its a means to an end, not an end in itself.

Frank Schwieterman