Test Seams and Test Injections simplify ABAP Unit Tests
I will describe my experiences with ABAP Unit Tests and Test Seams. The techniques described here can be used for new code, but they are especially valuable while working with Legacy code.
Legacy Code is defined by Michael Feathers as Code without tests. Andrea Goulet defines Legacy Code as code without communication artifacts. This is a modern interpretation of the word. So in this definition a code that is new, can be called Legacy Code even by the developers who wrote the code. Just because they did not add the tests or communication artifacts they would have needed to maintain their own code properly.
The whole work described here makes sense only if the Design Stamina Hypothesis by Martin Fowler is true and the “functionality crossed the design payoff line”.
I do not think, that a single way of building tests exists that is appropriate to improve all kinds of coding. Depending on the application to be developed, different ways to implement tests should be used and there are cases when no tests might be needed at all. It is the duty of the reader to decide whether to use or not to use the techniques described here.
I started to make Unit tests a few years ago, but I had problems to break dependencies. There are techniques to use mock instances, but these required a specific more sophisticated design, and the mock classes itself added complexity to the applications. In some cases I decided to invest this effort to have Unit tests also in cases where dependencies are an issue. But in most cases I did not write Unit tests.
So for a long time I wrote Unit tests only for methods that are complex and independent from other parts of the applications. These tests where very valuable, but most of the code I wrote remained without automatic tests. In case of existing code this was even worse, as adding Unit tests to such code would have required near to always big changes. So I hardly ever added Unit tests to existing code.
This changed when I learned of the statement ABAP Test Seams that is available with ABAP 7.50. Please read the blogs by Klaus Ziegler and Horst Keller on this new statement and its potential.
A Test Seam can be added to most statements in functions and methods of classes. Test Seams can be added also to static methods, where most older techniques to add Unit Tests could not be used.
The previous techniques to handle Unit Tests in case of dependencies where complex and required a somehow sophisticated object oriented design. Applying Test Seams is at a first glance easy. There are in my opinion no good excuses anymore to write no automatic tests at all, if Test Seams can be used. Unit Tests that are written with Tests Seams might be not as good than Unit Tests made with conventional techniques, but they are better than no Unit Tests. In other cases Test Seams appear to be the best option as they are comparable simple to use.
Left without any excuse, I write now near to always Unit Tests.
Transport variable values into a Test Injection
In the test injection it is not possible to access values of variables that are defined in the test methods. Klaus Ziegler describes in his above linked blog how to do this by using public static attributes of a local class of type FOR TESTING. See the following example of a local class:
class test_container DEFINITION FOR TESTING.
result type i.
The attribute result can be read and set in the Unit Test and in the test injection. It cannot be used in normal code.
This can for instance be used to check whether a code is executed during a Unit Test. For this an empty Test Seam statement is used. In the Unit Test such an attribute can be set inside the statement Test Injection. To check whether the code is executed, test whether the variable is set.
A typical pattern while changing Legacy code
Michael Feathers described various techniques to work with Legacy Code in his above linked book. By searching for the statement “Legacy Code” a lot of resources can be found that helps while working with such code. My standard workflow when I work with Legacy Code is currently:
The tests are Characterization tests, they are made to prevent unwanted changes in the logic. They do not check for correctness. How to check for correctness is a different topic not discussed in this blog.
See package SABP_UNIT_SAMPLE (in ABAP 7.50) or the test cases made for the SAP Inside Track Hamburg 2017 on Github. This is also a good opportunitiy to test abapGit and learn how to collaborate in openSource projects for ABAP.
There is no single pattern how to code or how to change existing code. But there are two techniques I currently use nearto always when I develop new code or change existing code:
My 2 cents ...
While Test Seams are really helpful when writing Unit Tests for "legacy" code with proper SoC; for new applications we must make sure that the procedures are "testable" (e.g., with proper SoC)
IMO with test-seams the developer has to be disciplined and not overuse this feature.
you are right, the developers should not overuse the feature. There are various options to break dependencies in Unit Tests, we should always try to use the option that fits best.
Writing good Test Seams is not trivial. I often find myself forced to correct the inserted coding, because it causes tests to pass or fail which should behave the other way round. This happens for instance if a select is replaced in a test seam and the where condition is not correctly simulated in the injection. Or the SY-SUBRC is not set, as a real SELECT would do this.
But my impression is, that the biggest problem is currently not how to write the best Unit Tests, but to motivate developers to start doing it at all. And TEST-SEAMS make it much easier and faster to write tests for many types of real world coding with dependencies. The result might not be optimal, but it is good enough to document how a class or function group works.