Monday, February 1, 2021

A Java Test Class Doesn’t Test a Class

Oracle Java Tutorial and Material, Oracle Java Certification, Oracle Java Preparation, Oracle Java Learning

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:

Oracle Java Tutorial and Material, Oracle Java Certification, Oracle Java Preparation, Oracle Java Learning
◉ 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 and afterEach 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.

Related Posts

0 comments:

Post a Comment