Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Tomek Kaczanowski - Practical Unit Testing with JUnit and Mockito - 2013.pdf
Скачиваний:
228
Добавлен:
07.03.2016
Размер:
6.59 Mб
Скачать

Chapter 4. Test Driven Development

Listing 4.15. compareTo() method recognizes lesser team

public int compareTo(FootballTeam otherTeam) { if (gamesWon > otherTeam.getGamesWon()) {

return 1;

}

else if (gamesWon < otherTeam.getGamesWon()) { return -1;

}

return 0;

}

All tests pass, so we can move on to an equality test:

Listing 4.16. Testing for equality

@Test

public void teamsWithSameNumberOfMatchesWonShouldBeEqual() { FootballTeam teamA = new FootballTeam(2);

FootballTeam teamB = new FootballTeam(2);

assertTrue("both teams have won the same number of games: "

+teamA.getGamesWon() + " vs. " + teamB.getGamesWon()

+" and should be ranked equal", teamA.compareTo(teamB) == 0);

}

Well, this test passes instantly, because our implementation has already returned 0 in cases of equality. So, what should we do now? We have definitely skipped one step in the TDD rhythm. We have never seen this equality test failing, so we do not know why it passes. Is it because the FootballTeam class really implements the expected behaviour, or is it because our test has been badly written and would always pass?

For the time being, let us assume that the test is flawless, and that the implementation of FootballTeam also is. We will come back to this discussion later on, when examining some corner cases of TDD (see Section 4.9).

Now that we have a safety net of tests we can really refactor the tested method. After having thought through the matter carefully, we have ended up with much simpler implementation:

Listing 4.17. compareTo() method simplified

public int compareTo(FootballTeam otherTeam) { return gamesWon - otherTeam.getGamesWon();

}

The rerunning of the tests now tells us that this implementation satisfies all the requirements (written in the form of tests) so far.

4.6. Conclusions and Comments

The actual implementation of the test is rather a detail of TDD. Much more important is the mindset of practicing baby steps, the mindset of gaining insights and evolving through rapid feedback, the mindset of leveraging trial & error as a methodology, where errors are not failures but valuable insights that guide the evolution of the project.

— Jonas Bandi

56

Chapter 4. Test Driven Development

So we have just implemented a class using the test-first approach. We moved in very small steps. We paid attention to the details. We polished the code. We even dealt with assertion messages. And all the time we were making sure that no functionality was broken – by rerunning the tests over and over again. And all the time we were moving forward… step by step… always forward.

I have a lot of questions to ask you now. How did you like it? Did you enjoy the red-green-refactor rhythm? Did it help you with writing the code, or did you feel it got in the way of your style of thinking and coding? Are you confident that the created code really works? Would you have done it better or faster without testing, or – maybe – would you have preferred to have written the tests after the implementation had finished? Did you feel you were wasting time by writing tests even for trivial cases? Did you feel confident refactoring the code?

Such a simple example might not be enough to demonstrate its usefulness. The problem was so trivial that you surely would have had an implementation ready in your mind from the outset, and would not have benefitted much from having written the tests first. However, it will be different when you start using test-first "in the wild". You will be writing tests for features that you have no idea how to implement. Then you will see how having tests helps you come up with a solution.

Personally, I feel I owe so much to the test-first technique that I would be delighted if you also were to like it. However, it is up to you to decide whether test-first really suits you. Throughout the rest of this book I will be relying solely on the test-first technique. If you like it, great! If not, well, please do bear with it anyway!

It would be sensible to have a look at the benefits of TDD as listed in Section 4.3. Have you taken note of them? Is your code 100% covered by your tests? Any "possibly important" features implemented? Is it simple? Is it clean and readable? Have you noticed the difference when continuing your work after an interruption? If so, maybe TDD is worth the effort!

One additional comment regarding our tests written using test-first. As you have probably noticed, each test created its own object of the FootballTeam class. Usually a good idea is to create the SUT in a set-up (which is an example of extract method refactoring) so that each test automatically gets a new one (in a clean, well-known state). However, in this case it was not possible, because each test method required a football team that had been created differently.

4.7. How to Start Coding TDD

We are all the same, under pressure we fall back on what we know; hit a few difficulties in TDD and developers stop writing tests.

— Ian Cooper

So now you have read about the wonders of the TDD approach! And you like it! It seems a little bit awkward, but at the same time sensible. You have heard about some great programmers writing tests before code. You have heard about some projects written test-first and renowned for their superior quality. You have spent some time reading about TDD techniques, you know the rhythm, you are getting more and more excited about the whole idea (up to the point of tattooing a "red-green-refactor" mantra on your back). Eventually… you have made up your mind about it: you want to try it… you want to be a step ahead of your team mates. You are eager to try it right now, with the very next job that you are given!

And you do try it. And it feels like… like trying to break a wall with your own head! Databases, Spring context, static classes, ORM tools, JNDI calls, web services, global variables, deep inheritance hierarchy

57

Chapter 4. Test Driven Development

and console output jump out at you all at once. Nothing looks like it did in the test-first tutorials; none of the good advice you read seems of any help. It is just plain impossible to proceed!

But of course you do not give up without a fight. Struggling, with gritted teeth, you somehow overcome one issue after another, one at a time. It takes time. You need to read some blogs, talk with your colleagues about the necessary design changes, but somehow it works. But at the end of the day, you look at the amount of written code, and you know that this is not good. You would have achieved 10 times more by using the code-first approach that you are accustomed with. You are not even proud of your tests - you know what dirty hacks you had to use to be able to write them.

Let us stop here, before we sink into despair. Dear TDD-Wannabe-Programmer, you were simply trying to do too much at once! You were trying to change your habits (which is hard enough in itself) and, at the same time, to solve multiple problems existing in your codebase. Even if you did have some tests (as I hope you did), your code was probably never written with testability in mind. Trying to add new features in test-first manner is like challenging the old codebase: it will fight back, as you have just experienced. It will attempt to thwart your efforts. Those same solutions that once you were so proud of are now stopping you from moving forwards.

What I would suggest is exactly what TDD itself promotes. Move in small steps. Start with the simplest things. Implement easy tasks using the test-first approach. Cherish every TDD success. Learn from every TDD failure. If you find something that you cannot tackle using the test-first approach, then do not worry too much about it. After a few weeks (hours, days, months?) you will be able to deal with such tasks. Build up your experience in small steps. Day by day… This way it will work – you will see!

It will get easier and easier every day. Firstly, because you are getting better and better at coding testfirst. You "feel the rhythm", you concentrate on the task in hand, you stop thinking about possible and useful issues that are merely hypothetical, etc. Secondly, because you stop doing things you were doing, and start thinking in terms of testing. Thirdly, your design is much more testable now, and the next parts of your code are easier to test. Fourthly, some colleagues have joined you and stopped producing tons of untestable code. …and now you can see the light at the end of the tunnel. Good. :)

4.8. When not To Use Test-First?

TDD works like a charm, and it is very probable that once you get a good grip on it, you will be reluctant to code in any other way. However, there are some circumstances where this approach does not seem to be the best option. This section discusses this.

As explained in the previous section, it is really not advisable to jump at the deep end after just reading a "how-to-swim” tutorial. :) I do have faith in your programming skills (and so on), but it might just happen to be the case that you are not yet ready to implement any real-life programming tasks using the test-first approach. Start with something simple, gain experience and then move on.

This means that the first anti-test-first situation is attacking all possible problems at the same time, while lacking experience, skills and confidence. Do not do that.

And even then, when you have some experience, you will still find yourself stuck with TDD. Like Kent Beck9, who tried to implement a new Eclipse plugin without prior knowledge of the technology:

For six or eight hours spread over the next few weeks I struggled to get the first test written and running. […] If I’d just written some stuff and verified it by hand, I would

9Do I really have to introduce Kent Beck? If so… see http://en.wikipedia.org/wiki/Kent_Beck

58

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]