How to step up your automated tests game

fixing unit tests

This is a follow-up of my previous post here. If you’ve ever had to write automated tests, you might have some bad memories lingering around. Automated tests have a way of becoming troublesome as time goes by. You end up spending more time fixing the tests than adding actual features. When that happens, you don’t care about test anymore, you don’t run them, you’re not worried when they fail and you just don’t add new ones. That leads to brittle code and things break really bad really fast. I’m here to tell you there is a way to keep automated tests manageable.

Simple is better

Simple tests are the better tests. They should be simple to read, simple to run and simple to add. Clean up the setup. Wrap complicated setup in nicely named functions. Keep the logic linear. You should never have an if in your tests and you should scarcely use for loops or other exotic structures and/or logic. It’s as simple as this. When you write simple tests, you get easy to maintain tests.

Be precise

It should be clear what you are testing. A very good tip I have for you on this is to use the given/when/then format. What is that, you ask?! It’s a way of explicitly splitting your tests into three distinct sections.

Given should deal with setting up the data. Everything your test needs to run smoothly. This includes, but is not limited to: seeding the database, adding any mocks required, pre-generating any data and constructing all the models that you will use later.

When is where the magic happens. You test whatever you need to test here. You should make sure it’s obvious what you are testing. If you feel you’re not being clear enough, then wrap the call in function and name it accordingly.

Then is where you check your data. You have to make sure your test did what you want after all. The checks should be clear and self-explanatory. Additionally, the checks should be the direct effects of the when section. Don’t do unrelated assertions, even if they are true, unless you like to fix unrelated tests for every change.

I prefer using comments prepending each of the sections to make the tests even more explicit.

Test one thing at a time

Try to keep your test scope as narrow as possible. There is a two-fold benefit to this approach. First you get a clean list of failing tests when you break something. Hopefully you have named them well enough to diagnose the issue with just that. Secondly, updating the tests to match the code after you make a change should be a lot easier.

As I have hinted, naming is important here. The test name should hint at the non-trivial setup required and contain the assertion being made. A big red flag is if you find yourself having to use and in the test name. Some examples of some good test names are:

if the user is unauthenticated the server should return unauthorized
if the user is unauthenticated the server should log the request

A bad test name would be:

if the user is unauthenticated the server should return unauthorized and log the request

You’re actually trying to test two behaviors here.

Can you think of any other useful advice that could help us all with our tests.

Testable code design patterns

As you might have realized by now if you follow this little blog (and you should), I’m a huge fan of automated tests. If you’re not, I assume it’s because you either don’t find them useful, or you find them hard to add.

Tests are useful because code breaks. My code, his code, everyone’s code breaks eventually (and that includes yours too). What you aim achieve with automated tests is to break your code before you deploy it. It’s much easier to think of a fix when you don’t have people running around with their hair on fire because the site is down. Also, it doesn’t do you any good as a programmer to write code that breaks as soon as they leave your door.

Now that we decided tests are useful, let’s see what we can to make them easier to add.

Write testable code

You should see testing as an integral part of development. In the same way a programming language and/or framework impose some patterns in your code, in the same way the decision that you’re adding tests should change your code. I’m here to tell you there is a way to make your life a lot easier. There are a lot of design patterns out there. Together we are going to riff through a few that should make testing your code a piece of cake.

If your code is easy to test, you will test it more. With this you can ensure a more resilient project in the harsh production environment.

Dependency inversion.

You have more details here. It sounds a bit fancy at first. It’s not. The idea is this: Whenever a function/class depends on another module, they should get that module as a parameter and not instantiate it themselves. Combine that with automatic dependency injection and you’re in business. Mocking and stubbing will never be an issue again. You can completely isolate modules and mock out their dependencies. From then on the sky’s the limit on what you can test.

State is evil when testing

This is not so much a design pattern, as it is advice. Nothing will make your head hurt more than handling global state in tests. Then why don’t you save yourself the trouble and keep the state to a minimum. As a rule of thumb, all functions should be stateless. If called with the same params, then they should always return the same result.

If you remove state from modules, the problem becomes how do you deal with modules that inherently should have a state, things like db connections and network connection. My approach on this is to isolate the state in a shared model. Then pass that model around anywhere it is needed.

Be in the right mindset

Accept the fact that you’re going to have to test whatever you are working on now. Always keep that in mind when writing the code. Thinking How in the world will I test this, is a good way to spot anti patterns and code smells early on.

You have to think about module dependencies and if you should another. Then where should the logic and state live. Every now and then, you get to a point where you need to do some hackish workaround because the testing framework just hates you. That is OK, it’s just like every other workaround you had to do to get past a framework limitations.

These may not all be design patterns. However, I do feel that the advice above covers the gist of what you need to do so that automated tests don’t become a burden. See you next time with some advice on how to write better tests for your code.

Soft skills do matter

Let’s start at the beginning: what exactly are soft skills ? Soft-skills, is an umbrella term, covering the less technical skills you need for your work. Stuff like communication, time-management, delegating and other fun stuff you have to do outside of an IDE. I sure hope you are one of those programmers that do consider these skills important. Whatever your beliefs are, stay with me and you might just learn something.

You work with people

Obviously, but think for a second of the implications. Your work is based on other people’s work. People decide your tasks. People evaluate your work. And, ultimately, people pay you for your work. When you think about it this way, it seems important now, to learn how to deal with people. This includes negotiating, giving and asking for feedback. Some of the most efficient programmers I know, are great at working with people. They know what the client actually means when they ask for something. They know how people react when they are given both good and bad news. I would argue that a big chunk of their professional success has to do with their people skills. I’m not saying that you can’t be a good code without people skills, I’m saying that improving your soft skills make you a better programmer.

Prioritize

You are a professional and you should treat yourself as one. You should work on the most valuable piece or work first, and then the next one and the next one and so on. Now, the most valuable task might not be the most fun to do, nor the most challenging. It’s not the work you want, but it is the work you need to do now. Do that! It’s that simple. If you put the project above everything else and you treat your time as a valuable resource (which it definitely is), then, what you should be doing now becomes obvious. Do whatever needs to be done to move the project forward by the biggest margin. Additionally, your time is a valuable resource. Don’t waste it on useless tasks, don’t slack off, and never ever half-ass the work.

Opinions do matter

Imagine this: Someone asks one of your colleagues to recommend an awesome programmer for this cool project they have going on at the moment. Will they pick you? Would you like them to pick you? Be aware of your colleagues opinion on you. There are two easy steps to get there: ask and then listen! Learn what your peers want, what they value and fine tune your discourse to emphasize what they value in your work. Now, this might sound a bit mischievous. My advice here is: be honest. There is no harm in glossing over the details someone does not care about, in order to focus on the details they do care about, but don’t lie to your peers.

Communication is important

If you stop and think about it. During your average workday, you have a lot of discussing to do. Some are not that important, like the ones about the weather, while other matter a lot more, like the ones about your promotion. You spend so much time communicating, why don’t you try to do it right ? Communications  are the result that the sum of all your soft-skills produce. You should start by listening to your manager and try to understand their motivation. Then, you should make sure you work on the most valuable piece of work available. Trust me that will make your manager really happy. Then, think about the way you are presenting your work. Does your manager understand that your task is difficult, does your manager understand that you care about the same things he cares about ?

I know this sounds tedious, trust me. However, the good news is this: these are all skills, and even soft skills can be learned and, when practiced, improved upon.