Part 6 of the Cognitive System Testing series.
The last several blog posts in this series focused on testing at the system and functional layers. You may be wondering if that means that as a cognitive system builder you donâ€™t need to worry about testing at the unit layer. Of course you do! Unit testing is still the foundation of testing a cognitive system and is critical for verifying that each of the system components is working correctly. The simple fact of the matter is that building good unit tests for a cognitive system is not that different from building unit tests for any other kind of software system.
Building your system to support unit testing
The most useful unit testing factor when writing code is to simply think about how you could test each class/unit that you are writing. By simply writing classes with high cohesion and loose coupling, you are 90% of the way there. A cognitive system will involve many moving parts and several potential dependencies. Take care to provide ways to test your classes with minimal dependencies. For instance, very few classes should require a database for testing. Instead use a data access object pattern and dependency injection so that your class can be tested with or without a database.
Useful patterns in unit testing cognitive systems
Most software developers are familiar with Design Patterns, a set of useful blueprints for designing good classes. I donâ€™t mean to suggest you write (or rewrite) your code to use all of the Design Patterns, but there are several that have proven very useful in my testing:
Facade – for abstracting away remote systems including databases and web APIs
Bridge – for writing minimalist classes inside a framework (Servlet API, UIMA) while delegating most of the work to a second class (the bridge) that can be tested independently of the first framework.Â Â This lets me test most of a servlet implementation without a web container, for instance.
Mediator – I like to write classes that donâ€™t know much about the world around them, only interacting with dependencies via dependency injection. Then I use orchestrators to link these classes together and I test the orchestrators with a mocking framework.
For more examples on this topic, I recommend the highly useful book Practical Unit Testing with JUnit and Mockito by Tomek Kaczanowski
Useful frameworks in unit testing
Everyone has their own favorite frameworks, I donâ€™t mean to suggest that these frameworks are the only ones you should consider, only that it is important to have frameworks that help with at least these aspects.
Mocking: If you are abstracting away your dependencies, you need a mocking framework capable of providing at least a â€˜stubbedâ€™ version of the dependency. Most mocking frameworks will let you specify the behavior your mock should have and tell you which mocked methods were called. My favorite mocking framework is Mockito.
Parameterized testing: Itâ€™s often useful to have the same test executed several times with different sets of inputs. I sometimes prefer to write my tests with â€˜test utilityâ€™ methods, which seems to work better with some tooling, but when I want to separate the test data from the test code I used JUnitâ€™s parameterized tests capability.
Web service helpers: Rather than writing lots of boilerplate to call web services and JSON parsers to verify responses, I use REST Assured.
What level of code coverage should I have in my unit tests?
This is a question that seems as contentious as vi vs emacs and where to put your curly braces. I am not a 100% code coverage zealot. There is only so much time to write code and write tests and thus you should write as many tests as you need. A specific code coverage target invites the writing of bad tests simply to ensure code coverage. Instead of coverage focus on test quality – are you writing the tests you need and are they covering the most important aspects of your application? Finally, if you focus on writing high-cohesion/loosely-coupled classes that you could write unit tests for, that pays hefty dividends towards a high quality system – and building high quality systems is why I talk so much about testing in the first place.
Despite this blog seriesâ€™ relative focus on system and functional testing, unit testing still provides the foundation for testing a cognitive system. The system should be built of high-cohesion/loosely-coupled classes that are easily tested. Make use of appropriate design patterns and unit testing frameworks to increase the ease of writing tests. Donâ€™t aim for a specific code coverage target, aim for writing testable classes and high quality systems.
Catch up on past blogs in the series:
- How to test a cognitive system (and why itâ€™s so important)
- Cognitive system testing: Smoke testing
- Cognitive system testing: Testing at the beginning with ingestion verification test