So what’s your approach to testing? Perhaps you take the easy and obvious approach: Run the program, interact with it and see if it does what you expect.
May be this is fine if you’re learning programming or not doing serious work. But let’s say you’re a professional programmer working on a project for your employer… what are the problems with this approach?
The problems with manual testing
The most obvious one is that it’s repetitive and time consuming, so you don’t bother to test your whole program whenever you make a change. You just test the changes or the new feature you’ve added. After all, that’s where the thrill of programming comes from – seeing your code work how you wanted! But once you’ve seen it work a few times, the thrill wears off… So you don’t test the code you wrote last week.
Once you get into serious software development, there’ll come a time when you’re hit by the shock of finding an old feature of your program has broken! “But it was working before! What on earth happened??” you think. Did something you (or a colleague) changed in the code break previously working functionality? In the industry we call this regression. You fix the bug, and you quickly return to working on that new feature. But soon enough… boom! It’s broken again! “How did that happen??” Again you fix it, but soon enough, there’s another bug! This time it’s in a complete different feature and, frustratingly, the change that cause the new bug was in yet another feature! What you have now is fragile code. And how did you get into this mess? Insufficient testing. And now you have this nagging feeling in the back of your mind that says “will this change I’m about to make break something?”. This isn’t a good position to be in and can suck the joy out of programming.
This is a major issue with this approach to testing (which we might call manual testing). What other problems are there? What if you’re writing an API or code library. How do you test that? There’s no user interface to interact with! What if you’re working on a large application and the feature you want to test is buried deep in the system. You don’t really want to have to wait for the program to load, then login, then find the 5th menu item behind the second sub-menu of the ‘Tools’ menu item before you get to the feature you want to test. This is just dull repetition. Again, it sucks out the joy of programming.
Enough about problems. What’s the solution? This is where test automation comes in. In essence you’re telling the computer how to test your programme by writing tests, in code! Test automation is a big topic and there’s many approaches, but in this article I’ll focus on the most basic, lowest level approach, that we call unit testing.
Unit testing is when we test ‘units’ of code. What is a ‘unit’? A unit is the smallest parts of an application that can be isolated and tested independently. What this amounts to in practice depends on what language you using and other factors, but often it is an individual function or method. The goal is to show that these units of code are correct and to find bugs as early in the development cycle as possible, when they’re easier to fix.
Now imagine you had a unit test for every single line of code in your application. The full suite of unit tests can be ran with a single click of a button and within a second or two get the results are available, telling you whether or not your code works as you expect it to. Can you see how valuable that would be – every time you make a code change you can quickly check whether that change breaks anything. Furthermore, if you’re only making small changes between tests, it’s quick and easy to undo those changes and get back to working state and try again.
Having 100% code coverage with unit tests might sound far-fetched and idealistic, but it is entirely feasible, with the right technique: test-driven development (TDD). The basic concept is to write a failing, write code that passes the test, refactor the code to improve readability and repeat this cycle in very short iterations, sometimes less than a minute long!
TDD can take some getting used to and when starting out, you’ll likely be less productive than normal. But given time and persistence, you’ll get back up to normal speed. This is important, since one of the primary benefits of TDD is consistent development speed.
Go fast forever
If you want to be able go fast forever, one way is to work alone and develop all the code yourself. That way you know how all your code works, how to navigate and make changes. But we don’t all have the luxury of working alone. Some applications are too big coded by one person – a team is needed. The challenge with team development project is working with code you didn’t write. There’s only one thing worse that someone else’s code – that’s someone else’s bad code. If you want to go fast forever in a team project, you have to have clean code.
Clean code is simply code that’s well structured, easy to read and understand, but it takes thought and effort to write. So how do you keep code clean? Refactoring, which is one of the steps in the TDD cycle. Refactoring is change the structure of the code without changing what it does, and in the process cleans the code. If you’ve gotten used to your code breaking whenever you make a change, refactoring is probably the last thing you want to do, but remember what I said previously about having a full suite of unit tests: every time you make a code change you can quickly check whether that change breaks anything. The result is you can refactor your code with confidence, since you know right away if you break something. Refactoring keeps your code clean and keeps you and your team working fast. The practice of TDD helps ensure your test coverage remains high (ideally 100%), so you can keep refactoring your code to keep it clean. And clean code is a joy to read and manipulate!
There’s much more I could write about TDD. I’ve explained why you should do it, but I’ve not really explained how. I might do this in a future article, but in the meantime they are plenty of other good teaching resources on TDD.
- By writing tests first, TDD helps us create a comprehensive suite (high test coverage) of automated tests that beats manual tests hands down for speed and efficiency.
- A comprehensive test suite enables a development team to make changes with confidence, enabling safe and continuous refactoring, resulting in clean code.
- Clean code allows us to make changes fast, so we go fast forever!
Links to more on TDD
Thanks for reading to the end of my very first article on the topic of Unity game development using a test-driven development approach. Before you go check out the list below of other resources on the net that helped me write this article.
- Why TDD by Matthew Parker.
- The Art of Agile Development: Test-Driven Development by James Shore
If you can’t wait to get started learning TDD, here are some very useful tutorials on the practice:
- Unity Blog – Testing Test-Driven Development with the Unity Test Runner by Sophia Clarke
- Writing Unit Tests in Unity by Scott Doxey
- Practical Unit Testing in Unity3D by Kuldeep Singh
Also, please leave a comment on what thought of my article. Was it interesting? Did you enjoy/understand it well? Did I explain terms and concepts clearly enough? I’d love to hear your thoughts!