Reasons Why You Should Write Proper Tests
laceysnr.com
laceysnr.com
Be it because requirements keep changing, new features are added, many people working on the same code, refactoring, whatever... Having good unit and functional tests catch a huge amount of problems that would be hard to find latter.
The other two points are pretty minor in my view.
And the suggestion to improve code coverage is just cheating. There's no excuse for that. Either don't track stupid metrics, don't add unnecessary catches, or properly test exception code.
EDIT: Clarified text.
For example, this week I've been working on refactoring some code that had some performance problems. Digging into the code I found a lot of confusing logic and had to waste a lot of time stepping through the code trying to understand what it was doing.
If there were unit tests, I could have treated it as a replaceable black box.
I use unit tests, but only after the code's finished or there's a good stopping point. Otherwise I have to do too much refactoring every time I change a function's name or signature.
Maybe TDD is forcing you to architect the program in the beginning, but I find it cumbersome because stuff changes so much throughout the course of finishing a project.
However, it's true that when refactoring and changing a lot of details here and there, I prefer to return type those tests afterward instead since a small change in a function can require a lot of change in the tests.
Writing tests first:
* Leads to better code design by encouraging smaller units of code which you can combine to achieve your larger goals.
* Having tests/specs lets you refactor with confidence.
* Writing the specs first means that they are going to be more complete, so you can have more confidence in them.
* Since you saw the specs fail, you know that they can fail. If you only test afterwards, you don't know if it's passing because your code is good or because the test is bad.
* The more confidence you have in your tests/specs, the faster you can improve your existing code.
1. This may be the case but I don't think this requires that you write your tests up front just that you don't leave all of your testing until the very end
2. This is just about having tests
3. I don't really see why writing your tests up front would make them more complete unless you already know exactly what you want to build. I find that I often come up with most of my edge cases while I am writing the code.
4. In many cases, if you tests before you still don't know if your tests pass because of your code is good or because the test is bad. Many tests, up front, fail for very obvious reasons (such as the function you are testing simply doesn't exist yet). Just because they fail in the beginning doesn't mean they are good tests. What you actually want is mutation testing [1]. Testing up front is just a really crude version of this. I usually do mutation testing manually by removing the code I am trying to test, at the end, to ensure that my test fails if it is not there.
5. Again, just about having tests
Ultimately I think you want interactive testing development. Write some code, write some tests, repeat. The actual ordering of the first 2 steps needs to be decided on a case by case basis. Writing all of your tests up front can be awkward. Writing all of your tests at the end is sometimes nearly impossible.
[1] http://dev.theladders.com/2013/02/mutation-testing-with-pit-...
If you write the test before you've written the code, then you'll be expecting the test to fail (in a very specific way) the first time it is run. By observing this you ensure that the test actually exercises the application code that you intended.
If you write the test after you've written the code, then you'll be expecting the test to pass the first time it is run. However if there is a bug in the test, it may pass for the wrong reason - e.g. if you forget some part of the test setup that causes the relevant application code to get skipped entirely.
But TDD is really about reaching a good design, not about creating useful tests along the way. One of the reasons is exactly what you state. The unit tests that are created in the course of TDD are extremely tied to the particulars of your implementation, and end up being a maintenance pain point in a large application. It is important for your tests to survive a refactor without needing to be changed. So, it is not uncommon for tests created in the course of TDD to be thrown away after you write some good integration tests / feature specs / whatever you'd like to call them for your code.
TDD is not, however, the only way to get to good design. You can also use behavior driven development (BDD) -- which is similar. Or you can just get in front of a whiteboard. Whatever works for you.
At the end of the day what matters is getting to good design and good tests in a way that's efficient and enjoyable for you.
It's 2013. Your editor of choice can do this for you.
Writing the tests first forces you to think about how a given interface will actually be used, which tends to lead to better code, because you're writing an interface that makes sense for the client rather than for the called code. In turn that means better encapsulation. It's probably possible to do this in the abstract, but if you're writing the test code anyway it doesn't cost you anything.
Don't get me wrong, I am incredibly sympathetic to the problem. I just have not seen incredible evidence that one can get better software, just because one wrote tests. Same for types. (Which, I realize, can be seen as a form of tests.)
The point I keep flirting with is that learning to write good testable code is just as hard as writing good usable code. One does not prevent the other any more than one causes the other.
> for this:
> you'll start to see those coverage numbers rising
I'd rather focus on better coverage of the most important elements than waste time on such irrelevant stuff. I got a lot of tests that don't increase the coverage factor, so what. Tools are meant to work for you, not the other way around.
Error handling should be the FIRST thing you test.
For example, some HTTP client implementations require you to consume any HTTP entity to avoid leaking connections and/or exhausting a finite connection pool. If you missed a ... finally { } block but otherwise do the right thing with your exceptions, you're storing up trouble there.