This question is about designing unit tests, something I started learning short ago.
I know the principle that if you are testing too much in a single unitary test it is a smell, either of code or design itself.
Scenario
I am computing a tax return form based on tax figures computed over the course of a year. Those figures are stored in the database in different rows on a monthly basis.
In order to compute the figures for the tax returns, some columns have to be copied "in the right place", other have to be summed up across 12 months.
The final form has a set of few flat columns, a Map
divided by month with 10 columns for each entity and another Map
that is a view on a different aggregation (hence the sum), where each entry has 4 columns.
Mocking the database is a joke, so I have a consistent set of tax figures along with the expected results hardcoded (this is a kind of code duplication I like to implement).
The problem here is that while I am still writing test code I discover myself writing a lot of testing code.
The method that computes the form should be atomic (i.e. set all the columns at once) because it runs in a single transaction creating a new consistent object, but is all about setting dozens of different columns. I have ended up testing more than 30 different properties.
Simple question
- Given that my unitary method works on columnA..columnZ at the same time
- Given that failure of one assertion (thanks to Junit's
ErrorCollector
) causes the test to fail but evaluates and reports other failed assertions all together
From the test design perspective, and in particular to the principle "don't test too much", is it preferrable to...
- Write a single test that performs 26 assertions each every column?
- Write multiple tests that run the same method with the same data set but each tests the outcome of a very single property of the output?
- Other strategy?
Pseudo code
The following is a sample of how the current code is structured. I really have plenties of assertions
@Test
public void testCreateTaxForm(){
//Create the mocks, e.g.
MonthlyDao dao = mock(MonthlyDao.class);
when(dao.list(any)).thenReturn(getTheTestSet());
TaxFormManager uut = new TaxFormManagerImpl();
uut.setDao(dao);
//Test
TaxForm form = uut.create(....);
//Verify
getErrorCollector().checkThat(form,hasProperty("flatColumnA",equalTo(expectedValueForA));
getErrorCollector().checkThat(form,hasProperty("flatColumnA",equalTo(expectedValueForB));
-----
getErrorCollector().checkThat(form,hasProperty("flatColumnA",equalTo(expectedValueForZ));
//Following is wrapped in a method for my comfort
for (Month month: Month.values()) {
getErrorCollector().checkThat(form,hasProperty("monthMap",hasEntry(equalTo(month),hasProperty("flatMonthlyColumnA",equalTo(....)));
.............
}
for (ExemptionType exemption: ExemptionType.values()) {
getErrorCollector().checkThat(form,hasProperty("exemptions",hasEntry(equalTo(month),hasProperty("flatExemptionColumnA",equalTo(....)));
.............
}
}
Pseudo alternative
In the pseudo alternative, I would have to write dozens of different methods (at least 120, each testing a column in a month) trying to reuse as most initialization logic as possible to avoid the test code base to grow too fat.
public void testTaxReturn_flatColumnA
public void testTaxReturn_flatColumnB
public void testTaxReturn_flatColumnC
public void testTaxReturn_january_columnA
public void testTaxReturn_january_columnB
public void testTaxReturn_january_columnC
public void testTaxReturn_retirementFunds_columnA
public void testTaxReturn_retirementFunds_columnB
public void testTaxReturn_retirementFunds_columnC
checkThat(form, equalTo(expectedForm))
?