This article isn't meant to convince anyone of the importance of unit tests. There's already a ton of information out there for that. This article looks at a few common practices that make testing harder and might tempt a developer to skip testing altogether. I'm in no way saying the solutions presented here are the best solutions. Every case is different and every developer is different too. If these solutions don't fit your style I'd love to hear of ones that do.
Complex Private Methods
In development we're often taught that we should keep related method calls inside of the class. This is good, but it can often lead to private methods with a lot of logic.
Consider the following code.
The problem: We can't isolate each piece of logic in our tests. Our tests need to account for each private method scenario and makes testing a pain to write and maintain. In each test we also have to account for the logic in the executeSomeLogic method. If we refactor any of the private methods it could possibly cascade to forcing us to refactor all of our tests.
A solution: Pull the private methods out into a separate service. That way we can mock them in our original test and test each private method in isolation. Like this:
The problem: We can't isolate each piece of logic in our tests. Our tests need to account for each private method scenario and makes testing a pain to write and maintain. In each test we also have to account for the logic in the executeSomeLogic method. If we refactor any of the private methods it could possibly cascade to forcing us to refactor all of our tests.
A solution: Pull the private methods out into a separate service. That way we can mock them in our original test and test each private method in isolation. Like this:
Static Util Methods
Static methods can be very handy to use since we don't need to worry about instantiating. And, generally every static method will include its own test suite so we can be confident the static method will execute how we expect. But, because of their static nature we must always execute that logic, even when we're testing something else.
Consider the following code.
The problem: Logic that uses static methods will be executed during the test and can lead to more complex setup. Basically, you would be testing your logic and the logic of the static method.
A solution: Wrap the static methods in an interface that can then be mocked. Like this:
Obviously (I hope), if the logic is very simple then this would not be worth it.
Using @Before to setup for every test
Tests can get pretty complicated and the junit @Before annotation can be abused to the point where the test becomes hard to maintain.
Consider the following code.
The problem: The setup up becomes too complicated so that when we refactor some existing logic and we need to update the test we find ourselves taking a lot of unnecessary time understanding how the test is setup before we can alter it.
A solution: Write more setup code in each test and keep the code in the @Before block to a minimum. This allows you to be flexible and it is easier to modify without any unforeseen consequences. Like this:
The problem: The setup up becomes too complicated so that when we refactor some existing logic and we need to update the test we find ourselves taking a lot of unnecessary time understanding how the test is setup before we can alter it.
A solution: Write more setup code in each test and keep the code in the @Before block to a minimum. This allows you to be flexible and it is easier to modify without any unforeseen consequences. Like this:
And if you find yourself repeating a lot of code create little helper methods.
Long if...else or switch clauses
If..else and switch clauses are a great way to easily step through a logical ladder to get the desired behavior. But with that quickness comes more complex testing.
Consider the following code:
The problem: A long logical selector forces us to write a lot of tests in order to cover each possible scenario.
A solution: Refactor to use a common interface with an implementation for each scenario. This allows us to isolate our tests and to refactor without affecting any other code. Like this:
Understandably your particular case probably isn't going to fall into one of the above problems exactly and you probably won't be able to make your tests and business logic 100% simple. That's okay in my opinion. My goal here is only to get people thinking about alternatives to some common practices and why some of these practices cause problems.
No comments:
Post a Comment