A Java Test Class Doesn’t Test a Class
If you write tests badly, you end up with a Curdled Test Fixture, where there’s an unholy mess of unrelated tests in the same class.
At Interview
A great job interview question is to ask someone what test automation they’ve constructed and how they came to do it.
A not-great answers is “none”. In my experience, worse than that is the person who answers “I have to write a test class for every class, and a test method for every method”. By fixating on the code under test, rather than the behaviours and nature of constructing a scenario, you often do precisely the wrong sort of testing.
Test Behaviour from Different Perspectives
The objective is to set up some sort of scenario and then test the behaviour within that scenario. The same code under test may behave differently depending on what we interface it with. It’s situational.
It, therefore, does not follow that a single class should be tested from one other test class. Nor does it follow that a single test class is only ever going to focus on one part of the system.
From the test pyramid we might assume that the lowest level tests will have fixtures focussing on only a small part of the system. However, other fixture may look further afield.
Fixtures Happen to be Classes
It gets confusing because the convention happens to be to create a test class to test a class. So the natural assumption is that you keep all tests in that class. It’s a good working assumption, as often it’s true.
However, watch out for whether the test starts to perform a diverse set of different tests. The easiest way to spot that is by looking at the properties of the test class, set up each time. Does each test case use the majority of the available resources?
How To Decide a Test Fixture
We may instinctively start by creating the usual mechanics of a test class. Something like this:
class MyTest { // some test input or expected output private static final SomeObject COMPLEX_DATA = new ...; private Thing whatWeAreTesting = new ...; // ... other resources @BeforeEach void beforeEach() { // some additional setup } @AfterEach void afterEach() { // some tidy up } @Test void testOne() { ... } }
I tend to start a test with some of the above in place, usually adding the beforeEach
and afterEach
on a case by case basis if needed.
But what if we weren’t allowed to use any of the object level values? What if we couldn’t create static resources? What would go in each of our test methods?
There would be some repeated setup stuff, there would be some stuff that may be repeated, but is not common set up, so much as the test examples which may be used in different contexts from one test to the next, and there would be some stuff that’s unique or used rarely.
If you were to imagine the test refactored to independent methods you may encounter a few situations:
- Some global setup is being repeated across multiple tests – implying these tests should live in a single fixture with a
beforeAll
/afterAll
pattern - Some set up is repeated across all tests – suggesting the object-level set up, perhaps with
beforeEach
andafterEach
depending on how easy it is to set things up - Some things are used for a subset of tests – suggesting that there’s are multiple fixtures required
Tests Can Share Implementation
There can be base classes for tests, and inheritance of test implementation is not, in my opinion, an anti-pattern.
Similarly, tests may share a test data factory for producing complex objects for both test input and expected output.
Summary
Tests need first-class design as well as the code they test. A test fixture is distinct from a test class, though one requires another, of course.
Published on Java Code Geeks with permission by Ashley Frieze, partner at our JCG program. See the original article here: A Java Test Class Doesn’t Test a Class Opinions expressed by Java Code Geeks contributors are their own. |