
031: Recycling-Heroes - Unit Testing (Configuration API)
Now that we've finished the Configuration API, let's take a look at unit tests and how we can automatically test our API. This will save us the effort of manual testing later on.
Table of contents
Introduction
In the last episode, we created and released our API for the configuration. To ensure we have a clean and stable API for the future, in this episode we will write the unit tests and test various scenarios with the API. As a first step, we will use the configurations we created last time to test our values and methods.
Preparation
Let's switch to our class, which implements the actual logic. First, we need to move the test code into the correct include; we made a mistake here last time. However, the code basically worked even in this location. Once the class is activated, we'll enable code mining in the settings, which you'll find in the Unit Tests section. This allows us to run our unit tests faster, as we don't need shortcuts but can do it directly in the code. We can then start our existing test directly via the "Run" button.
To write our unit tests more comfortably, we'll adjust the editor and focus on our current class. Double-clicking the current tab maximizes the UI to provide more space for development. We can now access the Split Editor view via the menu under "Window -> Editor -> Toggle Split Editor". Activate the split editor and develop code and unit tests side by side.
Unit Test
Let's start writing the unit test. We'll move the creation of our "Class Under Test," or CUT for short, into the setup method and create a class attribute. Using Rename, we can rename the attribute and clarify what we're actually testing. Since we want to work with a new instance for each test, we create the setup method. You can find more information about the phases in the blog; link in the comments.
Now let's define our first unit test and check if the Price API is active, as we already saw with the test data. We'll use the IS_ACTIVE method, which checks the flag at the database level. We use the method to check against ABAP_TRUE. If the ABAP Cleaner recognizes the check, it usually switches directly to the appropriate comparison method. Let's now run the test once and check if our data is correct so far. The unit test is also green; our method is executed and returns the correct result.
Let's create a second test and check the default capacity. Using the GET_VALUE method, we retrieve our value from customizing and compare it against the comparison value in our test. In this case, we adjust the value to 150 to provoke a false result and check if the test generates an error. When the test is executed, a red error message appears, and the test shows an error. In the trace on the right, you can also see the comparison that went wrong. Our logic expects 150 as the value, but the customizing returns 120.
We correct the values and create two more tests. We can directly copy one test where we check whether the Location API is active. Except for adjusting the constants, we can adopt the other values as they are. For the last method, we want to retrieve a range from the API and implement the GET_VALUES method for this. Here, we have a little more work to do to define the comparison value, since we first have to create a range.
By clicking the "Run" button on the test class, we can execute all methods and receive a green result for the 4 defined test methods.
Coverage
For many developers and quality managers, coverage is an important point. It shows us how much of our code we cover with automated tests. Therefore, we will run the unit test with coverage from the menu. In the popup, we select Coverage and execute the unit test. In the first step, our code is highlighted in color; there we can see which parts have already been executed by the unit test and which code is therefore being tested automatically.
We also receive key figures in the Coverage view. At the top level, we see the metric for the coverage of the entire class. There, we can also look at the individual methods and see that we are only testing about 60% of the GET_VALUES method. You can clear the code highlights using the button at the top.
Dependencies
Currently, we have a large dependency in our tests. Let's adjust the customization because we are further developing the software or, for example, another colleague wants to test something. To do this, we change a few values in the configuration, which now correspond to our new test state. If we now run our defined unit tests, 3 out of 4 methods no longer work. Unit tests are only of limited help to us because we often have to make adjustments and reconcile the customizing later.
Test Double Framework
For this, we use the Test Double Framework from the SAP standard. The framework helps us during unit testing by allowing us to use specific data for the test instead of the real data. This saves us from having to modify the code, and we can test our actual production code.
To do this, we define a class attribute for the environment and implement the setup and teardown for the class. This means the double is only initialized once. In the teardown, we ensure that the artifact is deleted after our test. In the setup, we create the double for the Core Data Service and, in this case, define the dependencies. As the next step, we define an internal table for our new test data. This replaces the data in the Core Data Service with our new data. First, let's define the data as it was previously, so that our current tests work again. Finally, we pass the data to the test double and activate it.
If we now run the test, we get an error. Currently, the double cannot be created correctly. You can find the solution in the description of the CREATE method. We don't need to populate the DEPENDENCY_LIST for the unit test. So, let's clean up the method again and start the unit test. Now all methods are executed again, and we receive a green result. If we perform the entire process with coverage measurement, we obtain the same result as at the beginning.
Test Data
Since we now want to increase the test coverage, we need new test data with different values. We usually cannot cover this with a single set of data, so we create a new method to generate additional test data for the same keys. This allows us to specify in each test which test data set we want to use. In the new method, we copy the data generation from CLASS_SETUP and call the CLEAR_DOUBLES method in the environment. This clears the data from the double, and we can add our new test data.
Since unit tests are not always executed in the same order, we need to ensure that the correct data is available. Therefore, we create another method for data generation and store the data elsewhere. In our current tests, we are still using dataset 1.
Now we can create further test methods for our dataset 2. We want a test case for a disabled function where a `to` value is populated, and a range with a `between` to check the last test cases of the `GET_VALUES` method. Now that we have defined the second dataset and the Price API should be disabled, we run the unit test for it. The result is green; our additional data is considered in the test. Let's now implement the remaining test cases, which are mostly similar to the first cases, usually just with different values and results.
Correction
If we now run all the tests, our test for HIGH_VALUE fails. In the method, we use the GET_VALUE method and would expect the HIGH value to be considered, especially if no LOW value is defined. This is currently not the case. In the coverage, we also see that the logic intended to handle this special case was not executed. The GET_VALUE method directly uses the database result, and we find no special logic for the HIGH value here. As a correction, we use the GET_VALUES method, read the first row, and return the LOW value.
Summary
Since our class is now equipped with sufficient unit tests, we can implement the modification and validate through the tests that our functionality continues to work as desired. All test methods are green, and we have achieved 100% coverage in every method. However, you should note that 100% coverage is not always practical and is sometimes difficult to achieve. Testing should be carried out here to the best of our knowledge and belief.
That brings us to the end of the episode. Thanks for watching and see you next time.
YouTube
Video