Writing this to help my friends get started with TDD in a practical manner.
When I first get started learning to do TDD, it is really hard because too many examples on the web are toy examples. Books and videos are also contradicting each other on the definitions of the different types of tests and how to get started. But after some struggling, I am finally able to do TDD on my own since 2016 so here are my tips:
Before we get started, first let's watch a short video of Adam Wathan trying to TDD his very first feature on a Twitter clone app with the Laravel MVC framework. A PHP web framework that is kind of similar to Rails. Watch it and I give my thoughts to the video after that.
Now here are some key observations
- After deciding on the feature 'View another user's tweet', he starts off with the 'Arrange' step of the triple-A in TDD (Arrange, Act, Assert or Given, When, Then). In the Arrange step, he used Factories to generate User and Tweet model objects with dummy data via the PHP faker package, and save them to the database
- In the 'Act' Step, he hit an URL. Since this is just viewing another user tweet, he did not pass in any form data.
- Lastly, in the 'Assert' step, he checks if the the returned webpage displays the values in the tweet. If you are building an API, you can assert against the database to check if the data is insert/updated/deleted.
- He runs the test, it fails and he fixes the issues 1 by 1, even dropping down to write a unit test for findByUsername() method in the User class. For me, I only drop down to write unit tests if the model method is complicated enough.
- After each test, the database is reset through the use of the DatabaseMigrations trait provided by Laravel. Allowing each test to start off again in a clean state.
- Also note how he write his test code assuming all the methods already exist on their respective classes. He calls it wishful thinking programming, a term I first heard of when I tried Peter Norvig's Design of Computer Programs course on Udacity sone years back.
Now if you were like me, you may be wondering, this is really different from the TDD/Unit testing I read in tutorials or workshops, in those tutorials, we create a model like Person, then write tests for each method in the Person class! Should we really be writing our tests in such an outside-in approach?
Well, that brings us to the next topic, the 2 main schools of TDD: Chicago and London.
I don't feel qualified to explain all the differences, pros & cons between the Chicago and London style of doing TDD but in short:
- Chicago / Detroit TDD goes from the inside to outside. Starting from the model classes. This is the style you see most often in tutorials. Also known as the classic approach. Popularized by Kent Beck
- London TDD goes from the outside to inside. I personally prefer testing from the outside in, like how Adam does it. Popularized by Steve Freeman and Nat Pryce
Here are some resources that do a better job of explaining their differences:
- https://youtu.be/aeX5OXO-w30?list=PLIuJbrOVyGjl0keQ-QyiMEOCvmabJEf0t (4 long videos Playlist)
Do note that you need not follow 1 style of TDD strictly, you can alternate between them or mix or match beliefs to suit your style (or your team style) of TDD. There is no 1 right way of doing TDD, just the way that makes it most convenient for you.
Next time you read an article, you know they are not really wrong*, just a different style and people can mix and match elements from both styles.
Test doubles is basically a play on the words 'stunt double' when a stuntman temporarily replace the real actor.
Stubs, Fake, Mock, Spies are just sub categories of test doubles.
Here's my advice, as long as your tests still feels fast, try not to care about them too much
However, there are cases where it may make sense to not test with the real collaborators, such as when it is an external web api and there is a certain rate limit or cost to hitting them. Still, you should at least hit the real web api once in your tests to ensure things are still working and that they did not change their API.
Another reason why I'm against mocking generally is due to the fact that they can get out of sync with the real class over time without you realizing it.
But if you need more suggestions on when and how to use test doubles, here are some resources:
If someone tells you "Code that's hard to test in isolation is poorly designed", you may wish to look up these 2 resources and decide if they are making a valid point for your case or not.
Using too many test doubles could result in code that is very hard to refactor. If you can't refactor your code base easily due to your test suite, why write tests then? Refactoring is a huge part of having tests after all.
- https://youtu.be/LdUKfbG713M (Lies You've been told about Testing)
- Your test method names need not follow the convention of the programming language such as testTweetCannotBeViewedIfTweetAuthorBlockedTheUser. It's quite hard to see isn't it? I very much prefer something like: test_tweet_cannot_be_viewed_if_tweet_author_blocked_the_user as my test method name.
- Consider using an SQLite in-memory database as your test database during development to speed things up
I personally like Jeffrey Way's forum app 'council' very well. Just check out his tests folder and phpunit.xml file. He also test TDD the outside-in way, writing mostly feature tests dropping in to write unit tests for model classes's methods when needed
The link below provides quite a good summary the different styles and definition of the different test doubles. Detroit-school TDD is the same as Chicago School
Feel free to comment below and I update this document or reply to you. Feel free to reply in any language you wish.