I have recently read a blog post claiming that functional tests are not “true” tests. The author also claims that unit testing shows you where the problem is occurring, while functional testing simply identifies that a problem exists. This argument is deceptive and the conclusion dangerous. Different kinds of tests are not mutually exclusive. One is not superior to the other. They have different goals and can happily coexist. Let me explain the kinds of tests so that you could make enlightened decisions.
Think of it as testing your application in the browser, but in an automated fashion. There are many ways to automate. Some tools can go as far as launch a specific browser and programmatically simulate user input, other tools can take a step back and send appropriate HTTP requests while inspecting the HTTP response.
In order to perform these, you do not need to know how the software is implemented. They are very high level tests. All you need to know is what the software is expected to do. For example, when you enter an invalid phone number and submit the form, the form should reload with an error message on top of the page. If the form saves at this point, you know that you have a problem, possibly in your validation or the validation may have been skipped. You know the general location of the problem, now you just have to dive into the code.
Functional tests will always prove useful and can do things that others cannot. For example, I use functional testing to simulate a sequence of user actions. Here is a simple scenario, written in natural language for your benefit:
- Authenticate a user that has only reading permissions.
- Navigate to an article’s draft.
- Assert that the status code is 404.
- Assert that the page contains a link to the most recently published article.
- Click the link and wait for the page to load.
- Assert that the status code is 200.
- Assert that the title of the article is correct.
There is almost no limit to what you can verify with functional tests. The more assertions you write, the easier it will be to pinpoint the location of a potential bug.
Unit tests are low level tests. They are meant to test your code rather than the high-level features. You need to have an understanding of how the code works and how the functions are linked. For example, you could have a method called canOpenArticle which will take a
user and an
article argument. Based on the permissions of the user and the status of the article, the function would return true or false. You expect specific output based on the input. Examples:
- Call canOpenArticle with a reader and a draft article.
- The output should be false.
canOpenArticlewith an author and a draft article.
- The output should be true.
User tests should operate on a single function or a small group of functions to be most effective. This will allow you to pinpoint the exact location of the issue. If you mix too many of your application functions in one test case, it will be much harder to tell which one of them is at fault when that test case fails. More on this in my next article: “The Missing Intro to Unit Tests”.
There are many definitions to this floating around. To avoid overlap with other tests, I define this as the testing of self-contained modules that you expect to be working on their own. All you want to do is make sure that you use them correctly. For example, if you’re using the Google Maps API, you don’t expect it to have bugs. Yes, it’s possible that they have bugs, but for the sake of sanity and good practices, your responsibility should stop here. You want to make sure that you are using the API correctly, that’s all. When I write integration tests, I send parameters to the API and expect the output to be exactly as documented. If it doesn’t match, I first assume that I’m using it wrong. Did I forget a parameter? Did I send a date in the wrong format? Did I misspel something? Once all my integration tests pass, I expect the API to keep behaving. If the API changes without telling me, I’ll know pretty quickly with my tests and will fix my API calls accordingly.
Here are some more examples of integration tests: framework, plugins, libraries, payment gateway, etc. For some of them you may have access to the code, for others you can’t see the code. In any case, always treat these as black boxes and focus your integration tests on the communication between your software and these external components. Bonus: you can do something similar between your own modules for better decoupling, so that each module could be swapped as long as it uses the same communication protocol.
Now go and try all of these. Don’t favor one over the other, as they are all meant to work together like a seat belt and an airbag. You wouldn’t trust your safety to only one of these, right?