What is TDD/BDD development?
TDD comes from “Test Driven Development”. OK, but what does “doing TDD” mean? Doing TDD means letting the tests drive your design, hence the “Driven” part in the name. What that boils down to is writing your tests first, watching them fail and then making them pass by writing your code.
Yes, this can sound pretty confusing! But trust me, it’s worth it. Read on to rest of the post, where I present, a step by step process of writing code using TDD.
FYI: There is a derivative of TDD called BDD “Behavioural Driven Development”. Basically, it’s the same thing as TDD: you write tests for your code first, but the difference consists in how these tests are written. BDD describes the tests using the Gherkin language (a known tool for this is Cucumber).
Test TypesWhen you write code you must know if it runs well. This means you must know if there are bugs or logic errors. You achieve this by executing the code against an input.
Let’s say we have the following code:
The succ function receives an initial number (start) and will return an array with its successors. The number of returned successors is limited by the second parameter, the range, if it was not limited we would have had an infinite loop.
After we have the code we want to check that it really returns the successors of the received number. I guess everyone does this after finishing writing the code.
The easiest way to do this is by calling the function and then printing out the result.
This type of testing is called integration testing. The integration tests are test case scenarios that exercise that multiple parts of your app are working correctly as a whole. Usually, integration tests are written to test the interoperability of two distinct systems. For example, in web development; your app is fetching data from the database, you also test your app and the database too. Practically, we are testing that both entities are communicating well.
Usually programmers prefer another type of testing which is called unit testing. Unit tests decouple your application in small parts and each part is tested independently from the others. In our example the two pieces are obviously the succ and the add functions. Each one must be tested separately.
First we test the add function and only then do we test the succ function, that internally uses the add function, because if the add functions doesn’t run correctly neither the succ function will run correctly. This type of tests are poor unit tests, because they don’t isolate all the pieces, that’s why it’s optimal to mock things, but we will speak about that later.
The code presented so far may be only a tiny part of a really big project, and this is only a small feature. How do we test an entire application flow?
Our app does more stuff with the received number, besides computing the perfect squares.
The unit and integration tests can guarantee that our code performs as designed, but now we want to check that our whole app is functioning well.
We can achieve this through functional testing, also called end to end testing (E2E). Functional tests test your entire app, from the beginning to the end. In web development functional testing means a way to automate a browser instance, in which we can simulate a real user interaction, like clicking around, etc. A tool for this is Selenium.
|Purpose||prevents bugs or logic errors||checks interoperability between parts||validates complete functionality|
|Scope||individual units||a cluster of units||the whole app|
|Quantity||many||fewer than unit||fewer than integration|
AssertionsAn assertion is the way to validate that a test has passed or failed. Let’s take an example:
When we run the succ function against the given input we have the expectation that the result will always be: 11, 12, 13, 14, 15.
However, inspecting things by hand isn’t a feasible option, especially since the code might change sometime in the future. We must automate this in some way. How? We do an assertion:
This is saying: “I expect that the result of running the succ function against 10 and 5 will be equal to the above array”. This assertion is equivalent to:
If it finds that an element is not equal, it will notify us through an Error, which means that the test has failed, else it does nothing and that means that the test has passed. This is exactly the behaviour of assertion libraries, a well known one being Chai. We need this type of libraries because it’s much easier and comfortable to do one-liner assertions than to write the same code every time, like above.
Test runnersWe need to separate our testing code from our actual code. Now how will you structure your tests? Will you call every function and then expect something? Sounds good, but if one test fails the others will not have the chance to get executed. Another disadvantage is if you assert in more than one test, things to be equal and one test will fail, you will get as output only “Not equal”, accordingly to the code in the above section, how will you know which test has failed?
We need another solution, which is a test runner. We will use Mocha.
This is a test, there are two possible cases:
- the assertion fails, i.e. throws an Error, which the test runner catches and will mark the test as failed,;
- the assertion passes, i.e. it doesn’t throw any Error and won’t return anything. The test runner will notify us that the test has passed.
MockingI guess you may have already heard of this, because it’s a common practice in the unit testing process. If you remember the unit test for the succ function, you saw that it is a poor unit test, because if the add function is broken the succ function will fail too. So we must replace the add function, but only in the unit test for the succ function.
The best explanation is an example (please read the comments):
You will think what a mess, so much code to write, but of course there are mocking libraries. I will present to you Sinon.
Nice! It’s totally equivalent with the precedent snippet.
Summing upI know that sometimes it’s hard to write tests and you may not be willingly doing this, but believe me it’s worth it and it can relieve you from horrible bugs and painful debugging. I hope you found my explanations helpful and will consider writing tests for your code from now on. I can tell you from my co-op experience at Hootsuite I’m a happier programmer because of it.
About the Author
Simion Florentine is a Co-op on the Analytics UI team. He loves anything that is related to programming or technology, especially web development. In his free time he likes to party hard! Follow him on Twitter @flore7Seven and Github @flore77