Skip to Content
Technical Articles
Author's profile photo Klaus Haeuptle

Testability: Critical, but sometimes forgotten

Testability: Critical, but sometimes forgotten

The aim of this blog is to stress the importance of testability for software architectures – especially with regards to frameworks or other reuse components.

What is Testability

Testability is the degree to which a system or component / module

  • is itself testable with unit tests, component tests, integration tests and system tests
  • enables testability of consuming systems

This is especially important for reuse components, programming language, utilities and frameworks. These systems on the one hand should have a very high testability for ensuring high quality and on the other hand provide means for efficient testability of the consuming applications.

Why is Testability so important

Being able to deliver sustainable high-quality features, you can only achieve with a very high automation and high quality. Testability is a main factor, which allows engineers to achieve that. The effort of considering testability as one goal of the architecture is low and the impact very quickly pays off in terms of improved productivity and quality.

Main benefits:

  • Higher quality of application: As an application developer I depend on the architecture of my application and the used reuse systems enabling me to write automated tests efficiently. These need to be fast, stable and allow to detect potential bugs.
  • Higher quality of reuse system: A system which is mockable for consumers can also tested by the developers of that system, leading to a higher quality.
  • Higher productivity of developers: Testability is probably the cheapest way to improve developer productivity, since every developer is affected by bad testability. And ensuring quality or fixing bugs consumes a huge amount of overall productivity
  • Executable documentation: Well written tests can be used by consuming developers as executable documentation, which can help to understand how an API works
  • Detect design and architecture flaws early: It forces architects and engineers to look on how someone is going to use your code or API. Besides, you need to deal with principles like separation of concerns and single responsiblity principle early.

Problem Categories

For many of the examples described in the following chapters, solutions have been implemented. The examples have been taken based on relevance for a broad developer community and ensure that the rules are understood. As a provider of a reuse system, you should consider testability from the beginning in your architecture. It does not only simplify the life of the consumer of you system, but also your own life!

Test Isolation not possible or difficult

In order to write fast, stable and maintainable tests, a developer needs to be able to isolate from dependencies efficiently in order to control what are the inputs and observe the outputs for the system under test. In the following sub chapters we describe several categories of problems with regards to test isolation. The provider of a reuse system needs to provide means to overcome those.

Test Isolation on integration test (isolated) or component test level

Test isolation should not only be as efficiently possible for unit tests, but also for component and integration tests. This chapter refers to isolated integration tests. In our terminology integration test are testing some components and isolated from other components. For application depending strongly on frameworks (like Spring Boot applications) these kind of tests can include the framework in the tests.

Examples are:

  • SAPUI5 integration testing: The MockSever for OData allows to isolate the UI5 application from the backend. And by that test the UI5 applications in a robust and fast way.
  • Integration Tests using in memory Databases. In Spring and other frameworks it is common for integration tests to replace a heavyweight database with an in-memory database to speed up test execution and simplify test setup.

Static Dependencies

Static Dependencies to foreign components should be avoided.

  • ABAP static classes: Many ABAP classes only provide static methods and handle their state using class attributes instead of instance attributes. Not only is this a surefire way to create classes which are hard to maintain, the call of a static method of a class is a hard dependency which cannot be easily mocked; a wrapper is needed around each call. An approach to simplify testing of the application is to define clear interfaces and factories for components, together with an injection mechanism.

Builder pattern in APIs

The builder pattern can greatly simplify writing code, but is usually a bad choice in outward facing code because it forces detailed knowledge of a foreign domain’s structure onto its consumers and thus ultimately contradicts the Law of Demeter.

You usually notice this when attempting to write unit tests for this pattern, which quickly becomes unbearable because you have to mock a large amount of objects and methods.

Encountered for example in Spring’s HttpSecurity – try writing a unit test for the @EnableWebSecurity configuration:

http.authorizeRequests()
  .antMatchers(GET, HealthCheckController.ENDPOINT)
    .anonymous()
  .antMatchers(POST, SomeOtherController.ENDPOINT)
    .authenticated()
  {tens of lines of code}
  .anyRequest()
    .denyAll()
  .and()
    .sessionManagement()
				.sessionCreationPolicy(SessionCreationPolicy.STATELESS);

Instable and fragile Tests

Instable tests can cause a huge loss in productivity. Therefore the writer of the reuse system needs to provide means for avoiding instable tests. The following sub-chapter lists some of the aspects, which can lead to instable tests.

Asynchronity is handled in a bad way

The tests should not directly deal with asynchronity. This should be hidden from the tests by providing abstractions for testing.

  • Example from SAPUI5 application testing: Before the test tool OPA the application developer needed to consider the asynchronity in their tests.

Instable Tests: Testing against implementation details

The tests should not be implemented against private attributes or functions / methods. If the tests are defined against such private details, the danger is that the tests won’t work anymore with a future release. If the provider of is changing some of these details.

Verifying the outcome: Observability

In order to be able to test the outcome, the test needs to be able to observe state changes or other results. The reuse system needs to consider how to make this efficiently observable for tests on different levels.

Observability determines how easy it is to observe the behavior of a system in terms of its outputs, effects on the environment, and other software components. It focuses on the ease of observing outputs. If user of the system cannot observe the output, they cannot be sure how a given input has been processed.

  • For example if a system under test has dependencies to other components. It should be possible to inject for those dependencies spys or mocks.

Fast Feedback

Fast Feedback: Tests should be executed often to detect bugs immediately and lower the effort to find them. Therefore it essential that the tests run very fast. Otherwise it has a very detrimental effect on productivity.

Summary

Therefore consider testability from the beginning in the software architecture to boost developer productivity and quality of your products or reuse components. More on patterns for improving the testability can also be found on xunitpatterns.

Assigned Tags

      Be the first to leave a comment
      You must be Logged on to comment or reply to a post.