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, examining real-world test implementations from popular open source systems. We find this particularly relevant, as understanding these examples can help you develop effective testing strategies for your own projects.
To go directly to the examples, use the following links:
1.2 Guava
Guava is an open-source library—developed by Google—that offers a set of functions for concurrency, caching, I/O, and for working with graphs. The library effectively extends the standard Java library, providing additional functionality for common programming tasks.
While we have already shown a Guava test example in Chapter 8, this section presents three additional tests that illustrate different testing scenarios.
Example 1: Ints.contains
Guava implements a class called Ints
that offers static methods for working with integer values. One key
method is:
public static boolean contains(int[] array, int target)
This method checks whether an array of integers (array
)
contains a specific 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));
}
The test method verifies various scenarios using predefined test data. The uppercase identifiers are constants defined in the test class, called IntsTest, as shown below:
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
Our next example examines the reverse
method in the
Ints
class, 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});
}
The test uses a helper method, also called testReverse
,
that accepts two arrays of integers as parameters, comparing the
input
array against its expectedOutput
. This
helper method’s implementation is shown below:
private static void testReverse(int[] input, int[] expectedOutput) {
input = Arrays.copyOf(input, input.length);
Ints.reverse(input);
assertTrue(Arrays.equals(expectedOutput, input));
}
First, the method creates a copy of the input
array to
preserve the original array’s contents. Next, the method under test
(Ints.reverse
) is called. Finally, an assert
statement verifies whether the result matches the
expectedOutput
.
Example 3: Files.copy
Guava also provides a Files
class that contains utility
methods for working with files. One such method is
copy(File from, File to)
, which copies all bytes from one
file to another. Here’s an interesting test case for this method:
public void testCopyFile() throws IOException {
File i18nFile = getTestFile("i18n.txt"); // setup
File temp = createTempFile();
Files.copy(i18nFile, temp); // method under test
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
.
The copy
method is then called to copy the content from
one of the files (i18nFile
) to the other file
(temp
).
Finally, there is an assert
statement, which 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
.
The Files.toString
function used in the
assertEquals
reads the content of a file and returns it as
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 for Spring, a framework for web development in Java. PetClinic implements a simple system for controlling a veterinary clinic. The system stores information about both the animals admitted to 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 for testing the update of 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. Next, 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
whether it now ends with an X
.
This is an integration test, since it retrieves and saves data from the application’s database. Furthermore, it uses two interesting services from Spring.
First, the object associated with the owners
attribute—with methods for retrieving and saving data from pet owners—is
automatically injected by Spring, which provides a dependency
injection service. To enable this, the declaration of this
attribute in the ClinicServiceTests
class has an
@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
the semantics of transactions in its execution. In the case of
integration tests, this annotation indicates that we want to
automatically roll back any changes at the end of each transaction. Here
is a comment from the test explaining this:
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 has its own tests, which, of course, are run by JUnit itself. The first versions of these tests were implemented by Kent Beck and Erich Gamma, the creators of JUnit and, to a large extent, the pioneers of the concept of automated unit testing.
The JUnit tests—version 4—are in a directory called test, which is located next to the main directory 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 (see the commit). It works as described below.
First, in the same directory as SuiteTest.java
, there
are other files that implement test cases. Below is one of them (the
comments were added by us):
// 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 implements a simple TestCase
, which uses the
old JUnit convention for implementing tests. According to this
convention: (1) test cases should be a subclass of
TestCase
; (2) each test method 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. The validity of this
property of OneTestCase
is tested by the following method
in 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: since JUnit is a testing framework, its test methods run tests and verify if the result is as expected. Thus, in this case, JUnit has a dual role: it is both the test framework and the System Under Test (SUT).
As shown in the previous code, 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.
Finally, four assert
statements are used to ensure
that:
- The test suite contains a single test case (subclass of
TestCase
). - None of the tests failed.
- No error or exception occurred.
- The test as a whole was successful.
1.5 Vue.js
Vue.js is a JavaScript framework for implementing Single-Page Applications. The framework allows developers to create components that have data, operations, and also an HTML presentation, i.e., the visual part of the component that will be displayed in the browser.
Here is a Vue.js test that is 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 a HTML view
(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, reverses their order, and concatenates them back
together.
The filters are used in the component’s template in the following way (which reminds us 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 will be equal to IH
.
1.6 PowerToys
PowerToys is a set of Windows utilities. For example, one of the utilities allows you to pin 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, the test checks whether the Save
button of the
utility is available (enabled). Then it simulates a click on
the button. After having done that, the button should no longer be
enabled, since we have just made a save and therefore 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 clarify 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.
Check out the other articles on our site.