-
Notifications
You must be signed in to change notification settings - Fork 0
Double Loop TDD
- Functional tests
- Unit tests
- The unit-test/code cycle
- Refactoring
You may be wondering how these JavaScript tests fit in with our "double loop" TDD cycle. The answer is that they play exactly the same role as our Python unit tests.
- Write an FT and see it fail.
- Figure out what kind of code you need next: Python or JavaScript?
- Write a unit test in either language, and see it fail.
- Write some code in either language, and make the test pass.
- Rinse and repeat.
Functional Tests verify the system from the user's perspective. They are the ultimate judge of whether our application satisfies the needs of our customers.
Unit Tests help us "along the way" while implementing customers features. With TDD and Unit Tests we manage to write well factored and maintainable code in a sustainable way.
-
Each test should test one thing
-
Small Design When Necessary ^^
When new code breaks some aspect of the application which used to work.
When a test fails in a way we weren’t expecting. This either means that we’ve made a mistake in our tests, or that the tests have helped us find a regression, and we need to fix something in our code.
Another way of describing the TDD process. Write a test and see it fail (Red), write some code to get it to pass (Green), then Refactor to improve the implementation.
Adding a test case with a new specific example for some existing code, to justify generalising the implementation (which may be a "cheat" until that point).
A rule of thumb for when to remove duplication from code. When two pieces of code look very similar, it often pays to wait until you see a third use case, so that you’re more sure about what part of the code really is the common, re-usable part to refactor out.
A place to write down things that occur to us as we’re coding, so that we can finish up what we’re doing and come back to them later.
Different tests shouldn’t affect one another. This means we need to reset any permanent state at the end of each test. Django’s test runner helps us do this by creating a test database, which it wipes clean in between each test. (See also isolation-chapter.)
Our natural urge is often to dive in and fix everything at once…but if we’re not careful, we’ll end up like Refactoring Cat, in a situation with loads of changes to our code and nothing working. The Testing Goat encourages us to take one step at a time, and go from working state to working state.
You ain’t gonna need it! Avoid the temptation to write code that you think might be useful, just because it suggests itself at the time. Chances are, you won’t use it, or you won’t have anticipated your future requirements correctly. See outside-in-chapter for one methodology that helps us avoid this trap.
Each test should test one thing *
The heuristic is to be suspicious if there’s more than one assertion in a test. Sometimes two assertions are closely related, so they belong together. But often your first draft of a test ends up testing multiple behaviours, and it’s worth rewriting it as several tests. Helper functions can keep them from getting too bloated.
-- Harry
In the beginning of chapter 6 we look at a dependency between our functional tests. This dependency is caused by using a real database and not cleaning up between tests: One way to tackle this would be to roll our own solution, and add some code to functional_tests.py which would do the cleaning up. The setUp and tearDown methods are perfect for this sort of thing.
Since Django 1.4 though, there’s a new class called LiveServerTestCase which can do this work for you. It will automatically create a test database (just like in a unit test run), and start up a development server for the functional tests to run against. As a tool it has some limitations which we’ll need to work around.
As usual... pictures and of time of writing practically all content is lifted from Harry (www.obeythetestinggoat.com)
Just as you use multiple files to hold your application code, you should split your tests out into multiple files.
Use a folder called tests, with a __init__.py.
For functional tests, group them into tests for a particular feature or user story.
For unit tests, you want a separate test file for each tested source code file. For Django, that’s typically test_models.py, test_views.py, and test_forms.py.
Have at least a placeholder test for every function and class.
(->)
Does this feel a bit like development-driven tests? That’s OK, now and again.
When you’re exploring a new API, you’re absolutely allowed to mess about with it for a while before you get back to rigorous TDD. You might use the interactive console, or write some exploratory code (but you have to promise the Testing Goat that you’ll throw it away and rewrite it properly later).
Here we’re actually using a unit test as a way of experimenting with the forms API. It’s actually a pretty good way of learning how it works.
