Friday, June 10, 2016

Guide: Approaching test coverage for Legacy Code

A standard question in teams just transitioning to agile software development is: "How can we be agile when our Legacy product has (close to) 0 Test Coverage? Doesn't it take years to reach decent test coverage?"
In a previous article, we covered the Testing pyramid: We will base our approach on this model.

The test pyramid

One basic assumption: The system works

Without automated tests, legacy systems are often bug-ridden, so developers are in a vicious circle of "We can't write tests without knowing where the defects are, and we can't fix the defects without tests". We simply break this loop by assuming that the system, as it is currently being delivered to customers, works. Then, we build tests based on how the system currently works. As we discover "That's not what it should be doing", we are still free to add the intended behaviour into the Product Backlog. Creating automated tests can start without further delay when we simply produce tests for the system we already have.

Start by introducing some E2E Tests

Depending on testing knowledge and available tools, starting with E2E Process tests is always an option - Selenium IDE covers you for all web applications. 
Approach: Grab your favorite testing tool and start making automated tests.

Advantages: E2E process tests can cover a huge portion of the application with very few test cases in a very short time. For example, the E2E process chain of an entire e-commerce site may be roughly covered by half a dozen E2E tests.

Disadvantages: E2E process tests require a massively complex setup. They are often flaky and hard to maintain. 

It is desirable to invest effort to refactor E2E process tests so that their test objective can be reached with less complexity. As more scenarios get covered by component integration tests, the amount of required E2E tests should shrink.

Turning E2E into Component Integration Tests

When new features are realized, the interactions, data models and transitions of a specific test step may change. These changes should be tested on a smaller scope by moving the test to component integration level. 
  1. Extract the relevant test (steps) from the test code of an E2E test (or start an entirely new test).
    This may require you to rewrite existing test activities using a different technology. 
  2. Create a specific integration test for the sub-scenario.
  3. Refactor your product source code in whatever ways necessary to make the integration test executable.
    This may require major refactoring, and even the introduction of new mockable interfaces into the productive system.
Advantages: Integration tests pinpoint problematic areas much better than E2E tests. It is significantly easier and faster to add additional test coverage or modify tests when the system changes.

Disadvantages: Integration tests are still fairly slow and difficult to set up and debug. They are oftentimes still unnecessarily complex. 

Therefore, it is desirable to refactor integration tests so that the risks are already mitigated on Component level.

Turning Integration into Component Tests

When the underlying logic of in one area of the product is changing, it should be tested on the smallest possible scope by moving the test all the way down to component level. 
  1. Extract the relevant test steps from the test code of an Integration test (or start an entirely new test).
    If you didn't do this already, you will now need to rewrite the test activities in the language of the source code.
  2. Create a specific unit test for each specific logic element you are working on.
    Mind the pre- and post-conditions of your tests!
  3. Refactor your product source code in whatever ways necessary to make the unit test pass.
    This may require major refactoring.
Advantages: Component tests are extremely fast to execute, extremely accurate in exposing defects and easy to maintain.

Disadvantages: Component tests may require significant re-engineering on Legacy code. They are not intended to uncover problems occurring exclusively when components interact.


With this approach, you can cover a broad spectrum of your application in a very short time in order to increase agility when working with Legacy code. 
E2E process coverage removes major risks from development quickly, but developers will have major headaches with flaky tests. 
Moving test activity to an integration level becomes a parallel activity to the development of new features. Your tests will become faster and more stable, but you may require significant changes to the existing architecture in doing so.
Unit tests will over time become the most reliable and fastest way to assure product quality and maximize developer feedback Their fine granularity means that, you will need a lot of tests to cover the product well. This takes time.

No comments:

Post a Comment