Book cover

Web Version | Dark Mode | Cite

Software Engineering: A Modern Approach

Marco Tulio Valente

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:

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.