Write automated tests at the lowest level possible

Identify the boundaries of actual code that you are trying to test. Is it all in one method, one class, a small set of classes, behind one Java service class or facade class, behind one web service, or is it scattered throughout the product?

  • If you can write the test as a unit test then do that.
  • If you can’t do that but you can write it as a component level integration test then do that.
  • If that is not feasible but you can write it as a service/facade level integration test then do that.
  • If that is not feasible but you can write it as a controller level integration test then do that.
  • If you can’t do that but you can write it as a web service API level integration test then do that.
  • If that is not feasible but you can write it as a sequence of web service calls then do that.
  • If you can’t do that but you can write it as a ui-driven front-to-back automated test then do that.
  • If you can’t do that but you can write it as a ui-driven automated user journey test then do that but …
  • … instead of writing numerous test at this level consider using  manual exploratory testing sessions as a team

Why?

Tests written at lower levels or finer granularity use less surrounding code to get to the actual useful part of the test and back again. This makes them more resilient to change and less subject to hijacking by unrelated bugs.

In a  typical layered service for example, a data access object may check that an object meets certain criteria just before it is saved to the database. Testing this validation logic from a ui-driven or web service test requires the test run through Java facades and service code before reaching the logic being tested, and requires any validation exception to propagate back up through that service and facade code. This makes the test:

  1. less resilient to change. Any changes to the web service, Java facade and service classes could require changes to the test code. Looking at it from a different perspective, it makes refactoring of the web service, Java facade and service classes potentially more expensive because more test code could be affected and require modifying or at least checking.
  2. subject to test hijacking where an issue in the surrounding code causes the test to fail, requiring more time to debug and fix, and potentially masking problems in the code you are intending to test with this test case.

In contrast, testing the logic in the data access object using a component level integration tests or even a unit tests means the test code is:

  1. more resilient to change because any changes made to the surrounding facades and services cannot affect the test code.
  2. less likely to be hijacked because the test is not using so much surrounding code.

Knowing that the logic is tested properly at the lower level means we only need one or two integration tests at the highest levels to ensure things are hooked up properly.