Expedia: 3 Tips for More Effective Unit Testing
There’s absolutely no point doing unit tests if they aren’t done correctly. Expedia’s Senior Software Engineer Lorenzo Dell’Arciprete recently wrote a helpful blog post that points out common unit test mistakes that cause some to be ineffective.
Writing unit tests is quite possibly the least interesting part of the software development process, except maybe for the moment of relief when they pass. Sometimes. Because not all passes are passes and not all fails are fails. It’s important to write tests that distinguish between the two.
Dell’Arciprete offered three tips to make unit testing more effective: testing for exceptions, working with unit test mocks, and using test data rather than production data. Read on to learn more about each of these.
Test for Exceptions
At first thought, it might seem simple to write a unit test to check if a specific illegal scenario will trigger a particular exception getting thrown.
The example above does check for the exception, meaning it will fail if anything other than MyCystomException is thrown. But something is still missing because if forbiddenMethod doesn’t throw at all, the test will still pass. In that case, the test isn’t a pass, the test is just slightly incomplete.
Dell’Arciprete offers two solutions — use a framework (the original post referenced JUnit) with a specifically designed feature for this instance or create the checks manually.
The framework provided feature looks like this:
With manual checks, the test should fail if it doesn’t follow the expected course which looks like this:
Reset the Mocks
Mocks are commonly used to simulate external states and/or behavior in unit testing. Consider the mock environment as a whole rather than each specific test when writing these tests. Consider the example below:
Run the tests in order and they’ll pass, but not in the reverse. The initial state of the test is mockExternalStore.isValid == true but because the second test alters the state and keeps it altered for future tests, the second test will pass but the first will automatically fail if run in reverse order.
Dell’Arciprete explained that writing a function to reset the mocks before each test solves this problem. Most frameworks have a setup function, likely named something similar to beforeEach, to ensure each test starts from the same context. The revised code is shown below.
Use Test Data in Tests
This one is simple and straightforward. Don’t use production data in unit testing, rather provide regular and edge case parameters for tests either manually or via a data provider (@DataProvider). He wrote that using production data in tests, “defeats the purpose of the test itself.”
Consider testing a hashing function based on a constant key. The function in production looks like this:
Meaning the unit test looks like this:
But then the thought creeps in, “the test will break if someone changes MY_KEY,” followed by the temptation to automate the generation of the parameters. Dell’Arciprete warns against that:
Heed the warning. The production code replication in the test here is clear, and while Dell’Arciprete does admit this is an “extreme” example, he follows the admission by saying that, “it can happen at a smaller scale unless you watch out for this.”
While there isn’t a wide delta between useful tests and sorta-useful tests, unit tests are nonetheless incredibly powerful when executed correctly. Dell’Arciprete provided specific examples but they can be applied more widely: Just a few simple tweaks can make a world of difference to your unit tests.