I’m new to TDD and wondering about methodolody.
Given:
A simple project which implements functionality of, for example, a console calculator.
It has the following structure:
- Fairly simple top-level class that takes console input, delegates it to a buisness-logic class and shows output in the console with some fancy formatting.
- The forgoing buisness-logic class which does all the calculations and which is relatively complex.
Also let’s assume we have a nice thorough suite of acceptance tests for the whole project, which performs through the user interface.
Do I need a suite of unit tests for inner business-logic class? Since it’s going to replicate the acceptance test suite for 90%.
Additional, but closely related questions:
-
Will the answer for the question remain the same if, for example, that acceptance test suite takes 30 sec to run? 5 mins? 1 hour?
-
If the inner business-logic class is not yet implemented, do I need write that suit of unit tests to guide its development, or it’s fine to remain only with acceptance tests?
0
Do I need a suite of unit tests …
Unit tests are no end in itself – you should better ask “Do I need a suite of unit tests if I want to reach a certain goal”. If your goal is just to make sure the software you are writing behaves correctly “as a black box” from outside, you only need acceptance tests.
But if your goal is also that your business-logic is properly structured into small components (instead of one big “god” class), and each of that components should behave correctly, then you will need unit tests for each of that components. The reason for having components is that this keeps your software more evolvable (it will become easier to add new requirements) and more maintainable (when you added a new requirement, and you had to change the existing code, and your acceptance tests shows that you introduced a bug, the unit tests might help you to spot the root cause of that bug more quickly).
So the gist is – unit tests make only sense in the presence of units.
If the inner business-logic class is not yet developed, do I need write that suit of unit tests to guide its development?
TDD is not about writing a suite of tests beforehand. It is about writing unit tests and code “almost in parallel” (“write test – write code – refactor – rinse and repeat”).
EDIT (to your comment): if you have acceptance tests in place, your program works as it should, there is no direct reason to introduce unit tests yet.
But, as soon when you have to change something in your code (probably because of new requirements), and you want to do TDD, then its a good idea to start writing unit tests for exactly the parts of your code you want to change (and not a “full test suite”).
Maybe you start with a test for a function to verify its current behaviour (“green”). The acceptance tests will prevent you from introducing a bug in this stage. Then you modify the test or add an additional test to verify the intended behaviour (which makes your unit test “red”). Afterwards you add the new functionality to your function (which makes you tests green again). And now comes the important part: you check if your function has become so complex that you better refactor it now – which can be done very painlessly because of the tests you just introduced.
So the idea of unit tests (in a TDD context) is not to duplicate the purpose of acceptance tests, but to help you with your actual coding and low-level design improvements.
2
Let’s assume that you have at least 3 layers of code, a presentation layer, an application/domain layer, and a persistence layer.
Since your acceptance test exercise the entire stack in your application, once you experience a bug, there is quite a lot of places where the bug could have been introduced, and after the first initial implementation of the code for a single test case, there could easily be multiple bugs present. Detecting the bug/bugs can take a considerable amount of time.
But if you do develop each of these layers in a test driven way, then there is a much higher probability that you find the bugs early, and the location of the bug is more isolated. Thus you would probably spend a lot less time fixing bugs.
Also remember that TDD is about tests driving the implementation. When practicing TDD, you are constantly changing focus from tests to implementation. This change of context is healthy for the though processes as it is a lot easier to discover patterns in code. If you on the other hand focus solely on the implementation, you brain tends to be locked onto the details of the implementation and fails to see the big picture*.
This last part, I did not write as much as a reason as to whether or not you should, or should not, write unit tests for individual software components. But more because you mention TDD as part of the question, and I wanted to bring into focus, that TDD is not just about writing unit tests – it’s more of a development process.
For more info on the the process of TDD, I can recommend Ken Beck’s book: Test Driven Development: By Example
* One of the reasons pair programming is effective is because the person not typing is more free to think about the big picture.
You have multiple responsabilities in your example; all of them should be covered by their own tests.
- You say you take console input, so you probably need something to parse and validate arguments. What if for example you don’t pass numbers, but text? What are the upper and lower boundaries of the arguments you’re willing to accept? Etc…
- You have the business logic classes itself, which you can probably easily drive through tests
- You have a formatter, which can also be driven by tests
You can use acceptance tests as sort of a high-level test, which you’d write first. It will ofcourse fail, because you’ll be invoking all sorts of classes that don’t even exist yet. But at this point you’ll have a rough idea of the components you’ll need.
Then you can drive each of the individual components through tests and when you’re done with that, the acceptance test should pass as well.
1
If your business-logic class is not yet written then your acceptance tests can only be complete for what WAS written and not what the program WILL do. One of the ideas of TDD is to write the tests such that they fail now, write the code, and then see if all the tests pass, when they do then you know you are done. That only works however if the tests you write full define what the requirements of the project are, at a business level.
You need to decide what the tests are supposed to be doing.
Is the point of them to let you know when you have finished writing code, in which case you need to have the tests full define what your requirements are, see them as an enumeration of all the acceptance criteria that will make your stack holders happy.
Is the point of them to give you confidence in the codes quality, this will allow you to refactor the code and verify that it still does what it is supposed to. In this case you need to focus more on testing interfaces between components so that internal changes can be show to exhibit the same external behaviour.
Is the point to verify to a developer, while developing, that they haven’t broken old requirements or that they are meeting new requirements, in which case the speed of the suite could have an impact. If you have a very tight loop where developers can run the tests so quickly they do so frequently, with time to known quality very short, then you need to include only tests that can be run quickly. These tests can then be split up in to different suites to be run at different times, e.g. in the editor after ever save, on commit, along side a build, after build, weekly, continuously etc.
Is it enough to use those acceptance tests which are already in place, or I need to develop additional unit tests?
It depends. What is the point of adding these new tests? What goal are you striving for? Acceptance tests are great to keep around if they are giving you extra information, confidence in the code, helping identify bugs. You might not agree with everything in this piece but take a look at Why Most Unit Testing is Waste as he suggests that even keeping the old tests might not be that valuable. Decide upon what you want and structure your test approach to that.
0
“Acceptance tests for the whole project, which performs through the user interface” are not scriptable. You cannot automate them and do continous integration.
That’s why you have to have unist tests anyway.
Also most times, “acceptance tests” don’t test limit cases, exceptions etc.