Testing Part 2 – Software Testing

Last year at nearly the same time I wrote a blogpost about app and program testing in general. This year I will concentrate on specific test methods. So, if you haven’t heard of unit-testing or integration tests, this is for you.

Software testing can be separated in three different parts which can be shown as a pyramid like the one below.

I am going to explain a little more about the different ways of testing later, but for now I’ll just focus on the schematic structure. The wide base of a software-program are the unit-tests. I wouldn’t recommend deploying without running at least some. Based on the unit-tests are the integrations-tests and on top of them are the UI-tests. But which test tests what? We’ll take a closer look at that now. I just want to note that I coded all my tests for Android and thus, we’ll only deal with this particular OS for now, as it should be more an overview than a tutorial.
Let’s start with unit-tests. Unit-tests are responsible for the basics of every program, for example methods or classes. The tests don’t have to be extensive because (depending on the tested object) you only need the outcome of the specific code-snippet. The tests usually run very fast because they only test the code and are also cheap because you don’t need any extra hardware or an emulator. An example for a good unit-testing tool is JUnit.

A simple test looks like this:
The test portion just adds two integer-variables “one” and “two” and returns the “result”:


public static int testMethod(int one, int two){
int result = one + two;
return result;
}

The testing method now calls the “testMethod” with the variables 1 and 3 and expects 4 as the result:


@Test
public void checktestMethod() {
assertEquals(4,MainActivity.testMethod(1, 3));
}

And the output looks like this:

JUnit is easy to integrate into an Android project. Just add 2 dependencies and you’re ready to go. Add a java class in the test-folder and once every test is written, just start them with the “run” command. If all unit tests passed, you can go on to the middle of the pyramid: the integration tests.

Integration tests can be used to test how the specific methods work together. It doesn’t just check the function of one simple method or class but can also work through a complete list of commands. You can integrate parts of the system which were not available or necessary during the unit-tests. But these tests are also a little bit more extensive. Needed objects may have to be implemented first. These objects are called mock objects. Mock objects are used to run the test-code without changing live data or just because it’s not available yet. You can specifically test function calls or commands.

In my case, I wrote an app for converting different units. With the integration test I can check whether the result is correct with specific input data. It looks like this:


@RunWith(RobolectricTestRunner.class)
public class RoboelectricTestExample {
[…]
@Before
public void setUp() {
Activity activity = Robolectric
.buildActivity(ConvertActivity.class).create().get();
convertButton = (Button) activity.findViewById(R.id.convert);
inputText = (EditText) activity.findViewById(R.id.input);
statusText = (TextView) activity.findViewById(R.id.result);
secondActivity = (Button) activity.findViewById(R.id.sec_activity);
unitButton = (Button) activity.findViewById(R.id.unit);
fromUnit = (TextView) activity.findViewById(R.id.fromUnit);
toUnit = (TextView) activity.findViewById(R.id.toUnit);
}
@Test
public void testConvertButton (){
inputText.setText("5");
fromUnit.setText("m");
toUnit.setText("cm");
convertButton.performClick();
assertEquals("Ergebnis: 500.0 cm",statusText.getText());
}
}

The test uses “Roboelectric”. The notation @Before describes every event that should happen before the test runs. In this case the complete activity is started and after that there is an initialisation of some buttons and text views followed by the actual test, starting with @Test. The program knows know that this is what it should check. I defined the value 5 and the unit “m”. The result should be in “cm”. If the test passes and the result is correct, it shows a hint with “test passed” and “exit code 0”. It can also be used to check whether the correct view is shown after a button click for example. After covering a big scope of operation with unit and integration testing there are still the UI tests to complete the test pyramid.

UI tests are the most expensive because they need at least an emulator to run. They also need much more code than integration or unit tests, which results in a bigger time effort.
As you might have been able to glean from the name, UI tests check the user interface of an app. This means that it checks, after an input and a click, whether the resulting commands are correct and whether the app reacts like it should. There are multiple ways to run these tests. On the one hand it is possible to run it in an emulator and on the other hand it can also run directly on the physical device. The advantage of an emulator is the variety of operating systems and devices it can cover, which is a much more complex task using only physical devices. This results in a larger range of possible tests. There is also the possibility, for example with Espresso on Android, to record a sequence of inputs and replay them as many times as you want, which in turn leads to a certain level of test automation. I also did a small UI-test with my app. You should know, that when you start the app, it asks for the kind of units to convert and after that for the unit itself. The GIF below shows what’s happening. It starts by changing the unit three times and then tests the conversion of the unit.

As you can see, there are many ways to test your code and to make it more functionable. The tools used in this blog are just examples. There are a lot of frameworks that work similarly or for testing other systems. It is also a question of which code should be tested by an external tester, since you need at least some knowledge of the code for some tests. But on the other hand, developers could make the same mistakes in the test as in the code.
UI-tests fall under the category of “grey-box testing”, because you don’t need to know the complete code but you know what is supposed happen. These tests could be done by an external tester.
Unit- and integration tests fall under the category of “white-box tests”. In this case, the code is known and can be tested very specifically.
At the end, the more you test, the fewer errors occur in the final app. But as I said last time, software without any errors, even if they are super tiny, just doesn’t exist.