29.07.2016 | Alexander Methke | comment icon 0 Comment

Agreements under Test – Part I

I18N is often implemented with resource bundle files. Some enumeration or set of constants represents the entries from the files. Each file holds all the entries for one language only. Instead of using a String expression to render text to a button a constant expression is used. The resource
bundle implementation does all the work and the user interface shows an OK-button:

When I add a new value to the enum, not knowing the agreement or skipping an important part of it, I won’t reckon the error until integration testing. My unit tests run without a GUI, even before all the different components are wired up. They fail fast and I tend to cover a lot of branches (I prefer branch instead of line coverage). Why not also checking resource translation?

Therefore the first thing I do with enums is writing an automatic test to hammer all values into a test method. Parameterized tests are perfect for this. Each enum entry maps to an instance of the test execution. One test per value automatically with no further coding effort. Details about parameterized tests and the special @Parameters-annotated method can be found in JUnit’s JavaDoc. With the resource bundle and its enum from above the follwing basic structure emerges:

Now for the flexible part: what if a tool tip or long text description is optional? I always add flags or descriptive types to enums. If a tooltip has to be present, a flag is set. This is something totally different than using a hasTooltip-method, that only checks availability on the resource bundle. What if someone provided a tooltip for his mothertongue but missed the translated resource? I add a safety net:

Assume.assumeThat disables the test in case there is no tooltip available. Now imagine a more complex scenario requires different types of resources, like pure texts, control items (buttons, menu) or messages to be formatted. If your sources grew towards a god resource bundle knowing everything – your single point of information – the unit test introduces the need for a structural change. Items are now split into groups. Each enum value is now one of TEXT_ONLY, CONTROL_ITEM or FORMATTED_MESSAGE. An agreement hidden in the code bubbled up to the surface with the help of a unit test.

Agreements among development team members silently creep in as evolution of design become rules. Rules must be tested whether they’re fulfilled. Rules change over time or evolve. Let’s assume there was only a single language to be translated. Add another and with the test above create the cross product of necessary locales and the enum values and check if the translation is complete. Doesn’t this sound like test first? It’ll continue to fail until all the translations were made available. Sometimes an external translation service does this job. My tests quality check their work basically.

I don’t stop with the enum values and forward testing. I also add the reverse test: each element of ResourceBundle.getKeys() must match to an enum value. Now it is symmetric and no enum value is missing nor superfluous. The parameters are derived from Collections.list(Enumeration<T>):

Now the source of test parameters is limitless, let us have another example for parameterized tests. One of our resource enums representing error codes needs unique keys. It was designed to use int values, passed as constructor argument. As of today developers spend some time scrolling two or three pages to find the next value. Some elements will be removed over time, gaps appear, others sort lexicographically and after 536 comes 997, followed by 10. A unit test makes it easy for me to pick a random number, run it and see if it passes. But be aware that uniqueness cannot be easily tested with the pattern above: test methods are independent of each other, they share no state. I also want to see all errors, not only the first duplicate. Thus the presumable unique elements cannot be parameter of the test. Instead a single method and elegant use of Matchers (Hamcrest) helps:

It is possible to write the above example as parameterized test. The memoization of already successfully unique numbers would require a state above the test method. First choice could be an instance attribute, which leads to issues with parallel test execution based on multiple test instances. Each instance could be lucky and only test the unique subset of error codes. This could be solved with the help of a synchronized state, collecting the already unique numbers. But test execution could also fork different virtual machines and it is uncertain, how elaborate parameterized tests can be split in future test execution frameworks.

In this case I don’t use parameterized tests. Instead a single test method records all the failures and asserts the list is empty. I often use parameterized tests to cover a large area with few lines of code. But I don’t let them lead me to bad design like static attributes memoizing test stages. I also don’t rely on todays test execution frameworks, but stay on the safe side and assume nothing.

hamcrest junit

Leave a Comment