How do you know your test is effective? How do you know your test protects you from breaking changes? And how can you do TDD without doing TDD? The best distillment of TDD taken from a TDD workshop in JFokus.
We all know that tests are good, right? Reduce regressions, and increase confidence. If done right, they can be a live documentation for our code. Many developers do write tests, and that is a day-brightening thought for me.
I’m not one to delude myself. Even though the benefits of TDD are well understood, I know most people do not practice TDD. I’ve been wondering what’s missing in After-the-Fact tests that usually make developers claim that tests are “useless”, “time-consuming” and have small to no value at all.
When I visited JFokus, the first workshop I went to was a TDD workshop ran by Raniz. Besides being a brilliant workshop, one part caught me as it described something I’ve been trying to grasp for a long time: the essential part in writing a test!
This is what Raniz said:
Table of Contents
What is the problem with so many tests that Raniz solves here?
The problem we are talking about is tests that do not really test.
Let me explain. When you write your code, and it works via manual test, you are happy. Then you write a test for this working code and expect it to work. Why, of course! The code is working!
What’s the problem with that? Let’s look at an example:
The test above looks logical.
- We arrange the API to be in the right state –
element.expandMode = 'multi'
. - Act by changing the
expanded
state of the items to true. - We would expect both items to be true.
But… (you expected a “But” here, right?)
This test will pass without any code. Why? Because we set the values of the items’ expanded to true
and expect them to be true. Hence, this should work even if we have an empty class!
Needless to say, this test will pass even if we change the expandMode
to single
.
Can you see the problem? Our test doesn’t really test anything except that setting something to true sets it to true.
The One Trick to Write Effective Tests
What Raniz distilled in his talk is the importance of failing a test. For any TDD practitioner, this is as obvious as the sun rising in the east, the north star shining in the north, and that Sith lords come in twos.
In our example, simply changing the state in the first line of the test case from multi
to single
would let us know if we are expecting the right thing.
Now, for those of you who are not writing tests before implementation, this is great news!
TDD without TDD
I like the phrase TDD for Cheaters
.
Why? Because it allows developers to write more meaningful tests in a kind-of TDD fashion.
Just follow the steps outlined in the TDD for cheaters “algorithm”. How to write TDD after the fact:
- Write the code
- Comment it out
- Write the test
- See that it fails for the right reason
- Make it pass
- Refactor
- Repeat (from step 3)
Summary
The one trick to make sure your tests are solid is to make sure the test fails with the wrong conditions or when you delete the code that’s supposed to make it pass.
This one simple trick will prevent cases in which your test doesn’t really protect. Moreover, the wrong test can mislead a developer trying to understand how the API works.
Looking for more testing tips on how to write better tests? You can look here.
Thanks a lot to Yishai Nachliel for the kind and thorough feedback.