Best way to learn TDD is to have someone show you while pairing with you. Short of that, I have set up an eclipse project for you where you can give it a try:
- hg clone https://bitbucket.org/misko/misko-hevery-blog/
- Open project blog/tdd/01_Calculator in Eclipse.
- It should be set up to run all tests every time you modify a file.
- You may have to change the path to java if you are not an Mac OS.
- Project -> Properties -> Builders -> Test -> Edit
- Change location to your java
- Right-click on Calculator.java -> Run As -> Java Application to run the calculator
Your mission is to make the calculator work using TDD. This is the simplest form of TDD where you don’t have to mock classes or create complex interactions, so it should be a good start for beginners.
TDD means:
- write a simple test, and assert something interesting in it
- implement just enough to make that tests green (nothing more, or you will get ahead of your tests)
- then write another test, rinse, and repeat.
I have already done all of the work of separating the behavior from the UI, so that the code is testable and properly Dependency Injected, so you don’t have to worry about running into testability issues.
- Calculator.java: This is the main method and it is where all of the wiring is happening.
- CalculatorView.java: This is a view and we don’t usually bother unit testing it has cyclomatic complexity of one, hence there is no logic. It either works or does not. Views are usually good candidates for end-to-end testing, which is not part of this exercise.
- CalculatorModel.java: is just a PoJo which marshals data from the Controller to the View, not much to test here.
- CalculatorController.java: is where all of your if statements will reside, and we need good tests for it.
I have started you off with first ‘testItShouldInitializeToZero’ test. Here are some ideas for next tests you may want to write.
- testItShouldConcatinateNumberPresses
- testItShouldSupportDecimalPoint
- testItShouldIgnoreSecondDecimalPoint
- testItShouldAddTwoIntegers
I would love to see what you will come up with and what your thoughts are, after you get the whole calculator working. I would also encourage you to post interesting corner case tests here for others to incorporate. If you want to share your code with others, I would be happy to post your solutions.
Good luck!
PS: I know it is trivial example, but you need to start someplace.
26 responses so far ↓
camestres.com » Links for 16/11/2009 // Nov 17, 2009 at 9:48 am
[...] How to get Started with TDD – [...]
I know this isn’t related to the topic at hand, but I can’t help but notice that you advice most of the logic to go to the controller.
So far I believe that the beef of your application should belong to the Model, while the Controller only does wiring work. What are your thoughts on this? Particularly, which have you found to be more/less testable? Which is more flexible?
@Andreas,
Model/Controller, is there a difference what you call it? The important thing here is that it is separated from the UI and is testable. I always thought that controller controls. Wiring in my opinion is better left to factories not controllers.
Whoops, I just realized that “wiring” is misleading. I meant to say that I see the Controller merely as a translator between user intent and the business logic – sort of like pure message passing. For example, no conditional logic is allowed.
I see your point about testability being what matters in the end, but in my experience I’ve found that logic-heavy controllers are harder to test/extend than logic-heavy models. However, it is entirely possible that I’ve been doing it wrong
This is my testcode so far. But now I am stuck and not sure if I am on the right track.
So here are some questions come to my mind:
Is it possible I started with the wrong tests?
In other words, how important is the order of the added tests?
The suggested testcode is an integration test (Controller and Model are testet together), isn’t it?
Should one who wants to starts with TDD concentrate on pure Unit Testing?
public void testReplaceZeroWithNumber() throws Exception {
controller.push(’1′);
assertEquals(“1″, model.getDisplay());
}
public void testItShouldConcatinateNumberPresses() throws Exception {
controller.push(’1′);
controller.push(’2′);
controller.push(’3′);
assertEquals(“123″, model.getDisplay());
}
public void testItShouldIgnoreLeadingZeroes() throws Exception {
controller.push(’0′);
assertEquals(“0.”, model.getDisplay());
}
public void testItShouldSupportDecimalPoint() throws Exception {
controller.push(’0′);
controller.push(‘.’);
controller.push(’5′);
assertEquals(“0.5″, model.getDisplay());
}
public class CalculatorController {
private final CalculatorModel model;
private boolean ignoreZeroes = true;
public CalculatorController(CalculatorModel model) {
this.model = model;
}
public void push(char charAt) {
if (ignoreZeroes && charAt == ’0′) {
return;
}
if (charAt == ‘C’) {
model.setDisplay(“0.”);
ignoreZeroes = true;
} else {
if (model.getDisplay().equals(“0.”)) {
model.setDisplay(String.valueOf(charAt));
} else {
model.setDisplay(model.getDisplay() + charAt);
}
ignoreZeroes = true;
}
}
}
@Andreas,
view is not allowed to have conditional logic, since we can not test it. Everything else is fair game. As long as you do test first, you will not have problems with testability, as no one will write a complicated test before the code.
@Michael,
Looks great so far! what are you stuck about?
Is it possible I started with the wrong tests?
Unlikely, these tests look good to me, and are the kind of test I would write. The only decision which you are facing with testing is whether you want to make additions working before decimal points, but it is no different by normal implementation. So I think you started with the right tests.
In other words, how important is the order of the added tests?
not very important, as long as you write tests and than implement only what the tests asks for, in other words don’t get ahead of yourself.
The suggested testcode is an integration test (Controller and Model are testet together), isn’t it?
Yes, this is not a problem, and is quite common. If you look at it closer, there are lots of classes involved, String os an example. The question here is subjective. If test fails where am I going to look for error? And it your case it is clear that it is in the Controller. A unit tests is something (1) which runs fast (2) if it fails you know exactly where the error is. Integration tests are different, they test that the whole thing is wired together properly and they tests factories. This test code is not set up for integration, as the wiring is in the main method, and main methods cannot be tested. But if we had a test where the whole app with the UI would run, and than we would be issuing mouse click events that would be an integration test. Those tests are complex and slow, and so you don’t want to tests drive your controller through them. Plus you only need very few such test. If you can prove that 1+1=2 than you are done with your integration testing as you have proven that the wiring is right, all other functionality you prove through unit tests.
Should one who wants to starts with TDD concentrate on pure Unit Testing?
Yes, and you are doing it well here, keep going.
Here are some more tests I can think of.
testOperatorShouldActAsEqualsAndEvaluate {
1+2+ => 3
1+2++ => 3
1+2= => 3
1+2== => 5
}
testTest(Plus|Minus|Muiltiply|Divide|Power|Sqrt)
testTrailingDecimalPointsAreIgnored {
0.1000= => 0.1
}
Here is a bug I think you have
testSecondDecimalPointIsIgnored
Implemented basic ops so far (+, -, *, /). Code can be viewed here: http://gist.github.com/237867
Anyone feel free to comment or fork.
Next step will be the decimal point stuff.
@Michael,
here are my comments: Overall very nice, you are right on track
1) assertDisplayShows takes double and hence it will suppress 0 so you may have all kinds of issues which you will not be able to tests for. I would make it take string and than there is no ambiguity to formatting.
2) pushButtons nice idea
3) take it a step further how about assertThat(“1+2=”, “3″);
4) testItShouldInitializeToZero put some junk on display before you clear
5) there are whole bunch of test cases missing around lastNumber and lastOp and “C”
6) you have speculatively implemented the clear() as there is no test to force you that way. This is one of the hardest things to learn about TDD, DON’T GET AHEAD OF YOUR TEST! you did! what you should have done, was to realize that you had no test, and quickly add one.
7) Get rid of the switch on enum. but the behavior directly on the enum. in calculateAndDisplayResult() just say lastOp.execute(lastNum, currentNum) all other ifs/switches can go.
ad. 6) (…)DON’T GET AHEAD OF YOUR TEST!(…)
When TDDing the tests are actually more important then the code that make them pass. For me it’s easier to think of it that way.
Is there a reason you’re not using @Test annotation? It would make test’s name more readable.
There is also an interesting thing I’d like to share with you. When we’re teaching TDD we’re using given-when-then comments in the code, which basically makes your test more verbose and keeps you focused on single behavior. Your sample test would look like this:
@Test
public void shouldInitializeToZero() throws Exception {
// given
CalculatorModel model = new CalculatorModel();
CalculatorController controller = new CalculatorController(model);
// when
controller.push(‘C’);
// then
assertEquals(“0.”, model.getDisplay());
}
It works very well for people starting with TDD. Surprisingly it is also addictive for more advanced programmers. Once you start using it you won’t go back to tests without those comments.
@Bartosz,
no reason we should use @test, old habbits die hard.
Given/When/Then is a good way to think about it: check out http://www.getangular.com/test/Runner.html, when you click on the test it will expand and you can see the Given/When/Then notation.
In case anyone else setup the Eclipse project on Windows, in
Project -> Properties -> Builders -> Test -> Edit
The classpath separator needs to be ‘;’ on windows, as opposed to ‘:’. It caused me a few minutes of frustration until I remembered this (as it was causing the class AllTests to not be found). Argh @ platform differences.
Slick auto-run setup – I am curious, though, Is there a way to get the Eclipse JUnit runner to auto-run on file save? I like seeing the green bar.
@Joshua
Try this plugin http://improvingworks.com/products/infinitest/
It doesn’t fire eclipse JUnit runner, but makes something very similar – you’ve got a green/red bar in ecilpse bottom bar. When you modify a file it checks which tests could be “affected” with your change and run those tests ( so it doesn’t run all tests ) if some test donesn’t pass you see red, more: some annotations are being added in your editor window and in “problems” view you see detailed message – the same as with compile errors.
Einführung in TDD & Clean Code Talks | geekcloud.org // Nov 22, 2009 at 7:50 am
[...] über das Interview mit Miško Hevery auf DZone berichtet. Kurz zuvor hat dieser eine kleine Einführung mit einem entsprechendem Beispiel zum Thema Test Driven Development verfasst. Das ganze ist als [...]
TDD unlimited « monkey island // Nov 26, 2009 at 3:40 pm
[...] please try to influence others to write more tests and learn TDD. How much inspirational is this or that! Anyway, it’s just my humble request to the world. Of course, you can write whatever you [...]
Hi Misko,
I’ve been working on your TDD exercise, and i’ve pretty much got all functionality of the calculator. TDD really makes me being more focused and confident that my code is working as expected. It has been a great practice, especially because it made me understand my bad decisions!
After implementing sum and decimals, i’ve noticed that the CalculatorController was getting too big, so I’ve created an interface (Calculation) and a single implementation (StandardCalculation) and passed it to the Controller. The goal of this class was to take care of the calculation itself, while the CalculatorController would do the conversion between character->number and back to string for the view. However, i now can tell that it was a bit too premature. I’ve ended up getting a big StandardCalculation class (carried away with adding more functionality and not stopping to think about refactoring it), and also realized that the interface definition that i’ve forced at that point was not the best choice. It now seems not flexible at all because this Calculation interface only accepts the numbers digit by digit (funny, i’ve realized this when refactoring test code of CalculatorController).
Another thing that I’ve noticed is that i’ve ended up almost replicating my tests between the CalculatorController and StandardCalculation. Not sure how to improve that though..
Other than that, i’m having plenty of conditionals in the StandardCalculation class, because I’ve ended up not dividing concerns in this class, so I should now try to refactor it to look more OO.
Here’s a gist with the most important classes. Anyone feel free to comment on it: http://gist.github.com/244657
Cheers,
Raul
@raul:
There are some files missing.
I started a fork at bitbucket
https://bitbucket.org/michaelkebe/misko-hevery-blog/overview/
Any comments are as always welcome.
http://bitbucket.org/niedvied/calculator/changeset/0e9853c1f48f/
Comments are welcome too.
Sorry for my comment above.
I didn’t have time so I wrote my nick instead of first name
(it’s a bad practice) and I didn’t give you feedback.
It was my first application with tests and I think it was a very
positive experience.
* I almost didn’t run application during development, very cool;]
* I can refactor without stress and I my application still work at the end.
* Writting test wasn’t to much overhead
I know It’s like from books feedback but It’s really true.
It would be great if you find some time to review my code
PS. Sorry for my english
http://bitbucket.org/niedvied/calculator/changeset/0e9853c1f48f/
@Misko
looking at Marcin’s solution it seems it is well OO designed and implemented. Anyway I think there are places for fixes.
1) I am not sure, but I think he mixes business concert with graph instantiation. If you take a look at this class http://bitbucket.org/niedvied/calculator/src/0e9853c1f48f/blog/tdd/01_Calculator/src/calculatorstate/AddingSecondNumber.java and all other state classes, there is plenty of new operator. He creates other State and CalculatorNumber objects. According to yours advises all new operators should go to factories.
2) http://bitbucket.org/niedvied/calculator/src/tip/blog/tdd/01_Calculator/src/operator/OperatorProvider.java. What about this static initialization and field ??
Can you conform my issues? If I am right can you explain how to fix it.
I’d be grateful, thx
@Lukasz,
1) yes the new operators, may be a problem, but in this case they are not since what they are instantiating are essentially new operations, which are leafs. see: http://misko.hevery.com/2008/09/30/to-new-or-not-to-new/ This is a thin line to walk, since as soon as your operations become more of services the whole thing will need to become refactored for proper DI. So my comment is that new operators here are OK for now, since they don’t prevent testability and there is no need to control the wiring in a different state
2) This is another tricky one. I don’t like it out of principle, but again it is not a problem since there is no global state hidden in the operators. But, I don’t like it since it is too easy for someone to add state and bring the whole thing down, so I am with you that it should be avoided. A better way to do that which is structurally equivalent is through Enumerations in java, as that is a bigger hint that you should not put state in there as they are global.
@Misko
I see the point, at this moment all State and CalculatorNumber objects are newable. But lets go further with this example. Let’s say the state objects have dependency to object A, so I will have to ask for it in a constructor. In that case all state objects become incjectables while Operator objects and CalculatorNumber remain newable. In current implementation state objects ask for newables (Operator, CalculatorNumber), but after adding the dependency A, I would break rule: “Injectable can never ask for a non-Injectable (Newable) in its constructor”. What do you suggest, how to deal with new requirement??
cheers
@ Łukasz
I think in that example State design pattern can’t be used anymore,
then you will be forced to replace it by “ifology”.
I’m looking for answer too;]
I did a Python version of this exercise. You can have a look at http://gist.github.com/487345
Thanks Misko for some great posts!
Apprium – Right way to add bibliography and external links on your site // Dec 23, 2011 at 4:29 am
[...] Misko. "How to Get Started With TDD". The Testability Explorer Blog, 2009. [...]