However example suggests a problem elsewhere (the constructor of the Calculator class). Your proposal will necessitate helpers like newCalculatorWithCosine() newCalculatorWithoutCosine() ad infitum for every permutation of the constructor parameters.
In fact should those not be helpers (factories) in the Calculator itself? But we digress from your main point....
I've seen one method that looked ok, which was to have a CalculatorBuilder class, that then has fluent style methods that you can chain like .WithCosine() and then a final .Build() method that constructs your instance. Not actually got around to using it in practise yet unfortunately... need to write more tests :)
It's a good point that you should definitely be paying attention to the clarity of your constructors - if there are a lot of places in your code base where you have to invoke a clunky six-argument constructor, something is probably wrong! The builder pattern that Sam mentions (http://www.javacodegeeks.com/2013/01/the-builder-pattern-in-practice.html) is a very good way to make instances easier to construct, and will clean up your tests too.
However, there are a lot of situations where a many-argument constructor is only really called once during your application's setup. This is especially common if you're doing dependency injection, manually or via a framework like Guice. In these situations there often isn't much point in creating builders for your production code since you don't ever reuse them, but it can definitely still be useful to define builders for tests. Builders used in this way are essentially a generalization of helper methods that allow you to specify an arbitrary number of named parameters.
There is often a trade off between readability and maintainability. Modularizing your test cases reduces it's readability while improves maintainability. But I agree that the test cases can be served as documentation of the system too.
I am going to suggest something for the hidden constructor variants here, and call it table driven testing. It looks like this test-code uncovered 2 test-cases, one for ENABLE_COSIN_FEATURE and another for (I assume) DISABLE_COSIN_FEATURE, where either the result or the inputs can be partitioned as the tester sees fit into a table of csv data: ENABLE_COSIN_FEATURE, 2,3,5 DISABLE_COSIN_FEATURE,2,3,5 I have a co-worker who does this kind of breakdown in their test-code regularly, it removes hard-coded stuff from the test-code, and results in hundreds of tiny custom csv files, but it rocks - only because its easy to test new corner cases without making a code-change. The downside is longer time to write the test, and ability to get carried away with the possibilities.
We put lot more effort in breaking dependencies but on the other hand we introduce constructor dependencies and the above example constructor dependencies is horrible . Here I see more of coding style than TDDing itself. If one does not follow the good design patterns and practises and write would end up in writing test what has been shown by Erik.
I agree with your recommending clarity of code.
ReplyDeleteHowever example suggests a problem elsewhere (the constructor of the Calculator class). Your proposal will necessitate helpers like newCalculatorWithCosine() newCalculatorWithoutCosine() ad infitum for every permutation of the constructor parameters.
In fact should those not be helpers (factories) in the Calculator itself? But we digress from your main point....
I've seen one method that looked ok, which was to have a CalculatorBuilder class, that then has fluent style methods that you can chain like .WithCosine() and then a final .Build() method that constructs your instance. Not actually got around to using it in practise yet unfortunately... need to write more tests :)
ReplyDeleteIt's a good point that you should definitely be paying attention to the clarity of your constructors - if there are a lot of places in your code base where you have to invoke a clunky six-argument constructor, something is probably wrong! The builder pattern that Sam mentions (http://www.javacodegeeks.com/2013/01/the-builder-pattern-in-practice.html) is a very good way to make instances easier to construct, and will clean up your tests too.
ReplyDeleteHowever, there are a lot of situations where a many-argument constructor is only really called once during your application's setup. This is especially common if you're doing dependency injection, manually or via a framework like Guice. In these situations there often isn't much point in creating builders for your production code since you don't ever reuse them, but it can definitely still be useful to define builders for tests. Builders used in this way are essentially a generalization of helper methods that allow you to specify an arbitrary number of named parameters.
There is often a trade off between readability and maintainability. Modularizing your test cases reduces it's readability while improves maintainability. But I agree that the test cases can be served as documentation of the system too.
ReplyDeleteI am going to suggest something for the hidden constructor variants here, and call it table driven testing. It looks like this test-code uncovered 2 test-cases, one for ENABLE_COSIN_FEATURE and another for (I assume) DISABLE_COSIN_FEATURE, where either the result or the inputs can be partitioned as the tester sees fit into a table of csv data:
ReplyDeleteENABLE_COSIN_FEATURE, 2,3,5
DISABLE_COSIN_FEATURE,2,3,5
I have a co-worker who does this kind of breakdown in their test-code regularly, it removes hard-coded stuff from the test-code, and results in hundreds of tiny custom csv files, but it rocks - only because its easy to test new corner cases without making a code-change. The downside is longer time to write the test, and ability to get carried away with the possibilities.
We put lot more effort in breaking dependencies but on the other hand we introduce constructor dependencies and the above example constructor dependencies is horrible . Here I see more of coding style than TDDing itself. If one does not follow the good design patterns and practises and write would end up in writing test what has been shown by Erik.
ReplyDelete