Software Engineering: A Modern Approach
1 A Small Collection of Real and Interesting Tests
1.1 Introduction
In Chapter 8, we explained the main concepts of software testing and showed at least a dozen code examples.
In this article, we will expand this list of examples, showing and explaining real examples of tests implemented in open source systems. We find this relevant, as understanding these examples can assist you in writing your own tests.
To go directly to the examples of each system, use the following links:
1.2 Guava
Guava is an open-source library—implemented by Google—that offers a set of functions for concurrency, caching, I/O, and for working with graphs. It also includes utility functions for working with primitive types. Thus, the system can be seen as a complement to the standard Java library.
We have already shown an example of Guava test in Chapter 8, but here we will show three more examples.
Example 1: Ints.contains
Guava implements a class called Ints
that offers static methods for working with integer values. Among them,
we have the following method:
public static boolean contains(int[] array, int target)
It checks whether an array of integers (array
) contains
a certain integer (target
).
The test for this method is as follows:
public void testContains() {
assertFalse(Ints.contains(EMPTY, (int) 1));
assertFalse(Ints.contains(ARRAY1, (int) 2));
assertFalse(Ints.contains(ARRAY234, (int) 1));
assertTrue(Ints.contains(new int[] {(int) -1}, (int) -1));
assertTrue(Ints.contains(ARRAY234, (int) 2));
assertTrue(Ints.contains(ARRAY234, (int) 3));
assertTrue(Ints.contains(ARRAY234, (int) 4));
}
This test is almost self-explanatory. In fact, all you need to understand is that the uppercase identifiers are constants defined in the test class, called IntsTest, like so:
private static final int[] EMPTY = {};
private static final int[] ARRAY1 = {(int) 1};
private static final int[] ARRAY234 = {(int) 2, (int) 3, (int) 4};
Example 2: Ints.reverse
Let’s take this opportunity to show another test from the
Ints
class, this time for the reverse
method,
which reverses an array of integers.
public void testReverse() {
testReverse(new int[] {}, new int[] {});
testReverse(new int[] {1}, new int[] {1});
testReverse(new int[] {1, 2}, new int[] {2, 1});
testReverse(new int[] {3, 1, 1}, new int[] {1, 1, 3});
testReverse(new int[] {-1, 1, -2, 2}, new int[] {2, -2, 1, -1});
}
This test calls a second method, also called
testReverse
, but that takes two arrays of integers as
parameters, where one is the reverse of the other. Here is its code:
private static void testReverse(int[] input, int[] expectedOutput) {
input = Arrays.copyOf(input, input.length);
Ints.reverse(input);
assertTrue(Arrays.equals(expectedOutput, input));
}
The code is very simple: first, it creates a copy of the
input
array (so the test won’t change its elements when it
returns); then the method under test (Ints.reverse
) is
called and, finally, an assert
checks if the result is as
expected, that is, expectedOutput
.
Example 3: Files.copy
Guava has a Files
class, with utility methods for
working with files. Among them, we have a method
copy(File from, File to)
that copies all bytes from one
file to another.
One of the tests for this method is the following:
public void testCopyFile() throws IOException {
File i18nFile = getTestFile("i18n.txt"); // setup
File temp = createTempFile();
Files.copy(i18nFile, temp); // method being tested
assertEquals(I18N, Files.toString(temp, Charsets.UTF_8));
}
First, it opens a file that already exists in the test directory,
named i18n.txt
. Then, it creates an empty file, also in the
test directory. Both tasks are performed by utility functions:
getTestFile
and createTempFile
.
Then, the copy
method is called to copy the content of
one of the files (i18nFile
) to the other file
(temp
).
Finally, there is an assert
statement. It verifies
whether the content of the temp
file has indeed become the
content previously read from i18n.txt
. This content is
already known and it is stored in a string constant called
I18N
.
For the record, the Files.toString
function used in the
assertEquals
reads the content of a file and returns it in
a string.
Before we finish, we want to highlight that this is an integration test, as it accesses the disk to read and write files.
1.3 Spring PetClinic
Spring PetClinic is a demonstration application of Spring, a framework for web development for Java. PetClinic implements a simple system for controlling a veterinary clinic. For example, the system stores information about the animals admitted at the clinic and their respective owners.
The system has some interesting integration tests that benefit from the services provided by Spring.
For example, the ClinicServiceTests class implements a method that tests the service of updating the name of an animal’s owner:
@Test
@Transactional
void shouldUpdateOwner() {
Owner owner = this.owners.findById(1);
String oldLastName = owner.getLastName(); // setup
String newLastName = oldLastName + "X";
owner.setLastName(newLastName);
this.owners.save(owner); // method being tested
owner = this.owners.findById(1);
assertThat(owner.getLastName()).isEqualTo(newLastName);
}
This test retrieves the name of the owner whose ID is 1. Then, it
changes this name by adding an X
at the end and saves the change
to the database. Finally, it retrieves the same name again and verifies
if it now ends with an X
.
This is an integration test as it retrieves and saves data from the application’s database. Besides, it uses two interesting services from Spring:
- The value of the
owners
attribute—with methods for retrieving and saving data from pet owners—is automatically injected by Spring, which also implements a dependency injection service. For that, the declaration of this attribute inClinicServiceTests
class has a@Autowired
annotation:
class ClinicServiceTests {
...
@Autowired // Spring will inject this dependency
protected OwnerRepository owners;
...
}
To learn more about dependency injection, you can check out this article on our website.
- The test is also annotated with
@Transactional
to ensure a semantics of transactions in its execution. In the case of integration tests, this notation indicates that we want to automatically roll back at the end of each transaction. Here is a comment in the test class itself:
Each test method is executed in its own transaction, which is automatically rolled back by default. Thus, even if tests insert or otherwise change database state, there is no need for a teardown or cleanup script.
1.4 JUnit
JUnit also has tests, which, of course, are run by JUnit itself. The first versions of these tests were implemented by Kent Beck and Erich Gamma, creators of JUnit and, to a large extent, of the concept of automated unit testing.
The JUnit tests—version 4—are in a directory called test, which is next to the main directory (main) with the system’s code.
Some interesting tests are implemented in SuiteTest.java. The first version of this class was implemented by Erich Gamma, in December 2000 (check out the commit). It works as described below.
First, in the same directory as SuiteTest.java
, there
are files that implement some test cases. Let’s show one of them (the
comments were added by ourselves):
// OneTestCase.java
public class OneTestCase extends TestCase {
public void noTestCase() { // does not start with test
}
public void testCase() { // ok, test method!
}
public void testCase(int arg) { // has parameter
}
}
This file declares a simple TestCase
, which uses the old
JUnit convention for implementing tests. According to this convention:
(1) test cases should be implemented in TestCase
subclasses; (2) each test should start with the prefix
test
, not have any parameters, and return
void
.
Therefore, OneTestCase
has only one valid test method
(testCase()
), which has an empty body. This property of
OneTestCase
is tested by the following method of
SuiteTest
:
public void testOneTestCase() {
TestSuite t = new TestSuite(OneTestCase.class); // setup
t.run(fResult); // method being tested
assertTrue(fResult.runCount() == 1);
assertTrue(fResult.failureCount() == 0);
assertTrue(fResult.errorCount() == 0);
assertTrue(fResult.wasSuccessful());
}
To make it clear: as JUnit is a testing framework, its test methods run tests and verify if the result is expected. Thus, in this case, JUnit has a dual role: it is both the test framework and the SUT, i.e., the system under test.
As shown in the code above, testOneTestCase
runs the
test implemented in the OneTestCase
class (the first two
lines). When running the test, an object (fResult
) is
passed as a parameter, which stores the result of the execution. For
clarity, fResult
is of type TestResult
and was
initialized in the setUp
of the test.
Finally, four assert
commands are used to ensure
that:
The test suite contains a single test case (subclass of
TestCase
);None of the tests failed.
Also, no error or exception occurred;
And finally, the test as a whole was a success.
1.5 Vue.js
Vue.js is a JavaScript framework for implementing Single-Page Applications. The system allows creating components that have data, operations and also an HTML presentation, i.e., the visual part of the component that will be presented in the browser.
Here is a Vue.js test, implemented using the Jest testing framework.
it('chained usage', () => {
const vm = new Vue({
template: '<div>{{ msg | upper | reverse }}</div>',
data: {
msg: 'hi'
},
filters: {
upper: v => v.toUpperCase(),
reverse: v => v.split('').reverse().join('')
}
}).$mount()
expect(vm.$el.textContent).toBe('IH')
})
First, a Vue component is instantiated (new
) and
displayed (mount
). This component has an
HTML template
, data (msg
), and two filters.
The first filter (upper
) converts a string to uppercase and
the second (reverse
) converts a string to an array of
characters, inverts their order, and concatenates the results back
together.
The filters are used in the component’s template in the following way (which reminds of Unix command line pipes):
msg | upper | reverse
Since msg
is the string hi
, the test
expects (expect
), in its last line, that the text displayed
by the component to be equal to IH
.
1.6 PowerToys
PowerToys is a set of Windows utilities. For example, one of the
utilities allows pinning a window so that it appears on top
of
any other window in the system.
The code for these utilities, implemented in C#, is publicly available in a repository on GitHub. And they come with an interesting set of end-to-end tests implemented using two frameworks: Appium and WinAppDriver.
Example 1: FancyZones
First, we show a test for the FancyZones utility, which is a
window manager that makes it easy to create complex window layouts and
quickly position windows in these layouts
.
private void SaveChanges() {
string isEnabled = _saveButton.GetAttribute("IsEnabled");
Assert.AreEqual("True", isEnabled);
_saveButton.Click();
isEnabled = _saveButton.GetAttribute("IsEnabled");
Assert.AreEqual("False", isEnabled);
}
Initially, it tests whether the Save
button of the utility is
available (enabled). Then it simulates a click on it. Having
done that, the button should no longer be enabled, as we have just made
a save and so it makes no sense to save again.
Example 2: Configuration Menu
The second test simulates the opening of the PowerToys configuration menu, which is located in the Windows icon bar. The comments in the test greatly help in understanding its logic.
[TestMethod] public void SettingsOpenWithContextMenu() {
//open tray
trayButton.Click();
WaitSeconds(1);
isTrayOpened = true;
//open PowerToys context menu
AppiumWebElement pt = PowerToysTrayButton();
Assert.IsNotNull(pt);
new Actions(session).MoveToElement(pt).ContextClick().Perform();
//open settings
session.FindElementByXPath("//MenuItem[@Name=\"Settings\"]").Click();
//check settings window opened
WindowsElement settingsWindow =
session.FindElementByName("PowerToys Settings");
Assert.IsNotNull(settingsWindow);
isSettingsOpened = true;
}
Exercises
1. Spring PetClinic: study and document the logic of the shouldInsertOwner()
test method, from the ClinicServiceTests
class.
2. JUnit: study and document the logic of the testInheritedTests, which tests the following test.