CS61B(3): Testing

This is the lecture note of CS61B - Lecture 3.

In this lecture, we will discuss an important and realistic issue -- Testing.

🐹 Let's think about an important question - how do you know that your code works correctly?

  • Run to see if the code works as your expect.
  • Pass Autograder (in this course).
  • Pass tests written by ourselves.

The last one is the most important one. In the real world, programmers believe their code works correctly because of tests they write themselves.

Ad Hoc Testing vs. JUnit

Let's try to write a method that sorts arrays of Strings and promise its correctness.

With the old way, we will write the sort method, and use Autograder to verify its correctness. But with the new way which will be taught in this lecture, we will write sort method, as well as our own test for sort method.

Ad Hoc Testing

We will start by writing testSort first, i.e. writing tests takes precedence over implementing function!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import java.util.Arrays;

/** Tests the Sort class */
public class TestSort {

/** Tests the Sort.sort method */
public static void testSort() {
String[] input = {"i", "have", "an", "egg"};
String[] expected = {"an", "egg", "have", "i"};
Sort.sort(input);

for (int i = 0; i < input.length; i++) {
if (!input[i].equals(expected[i])) {
System.out.println("Mismatch in position " + i +
", expected: " + expected[i] + ", but got: " + input[i]);
return;
}
}
}

public static void main(String[] args) {
testSort();
}
}

While the single test above isn't a ton of work, writing a suite of such Ad Hoc tests would be very tedious, as it would entail writing a bunch of different loops and print statements. In the next section, we'll see how the JUnit library saves us a lot of work.

JUnit

Next, we will do testing with JUnit , and write tests and the sort method simultaneously.

We will implement Selection Sort in sort method. The idea of selection sort is as follows:

The following code is the final code. If you are curious about the process, please watch lecture videos.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import org.junit.Test;
import static org.junit.Assert.*;

/** Tests the Sort class */
public class TestSort {

/** If you want to have your tests timeout after a certain amount of time
* (to prevent infinite loops), you can declare your test like the following: */
@Test(timeout = 1000)
/** Tests the .findSmallest method. */
public void testFindSmallest() {
String[] input = {"i", "have", "an", "egg"};
int expected = 2;
int actual = Sort.findSmallest(input, 0);
assertEquals(expected, actual);

String[] input2 = {"there", "are", "many", "pigs"};
int expected2 = 2;
int actual2 = Sort.findSmallest(input2, 2);
assertEquals(expected2, actual2);
}

@Test
/** Tests the .swap method. */
public void testSwap() {
String[] input = {"i", "have", "an", "egg"};
String[] expected = {"an", "have", "i", "egg"};
Sort.swap(input, 0, 2);
org.junit.Assert.assertArrayEquals(expected, input);

String[] input2 = {"there", "are", "many", "pigs"};
String[] expected2 = {"there", "pigs", "many", "are"};
Sort.swap(input2, 1, 3);
assertArrayEquals(expected2, input2);
}

@Test
/** Tests the .sort method */
public void testSort() {
String[] input = {"i", "have", "an", "egg"};
String[] expected = {"an", "egg", "have", "i"};
Sort.sort(input);

assertArrayEquals(expected, input);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public class Sort {

/** Sorts strings destructively. */
public static void sort(String[] x) {
sort(x, 0);
}

// OVERLOAD
/** Sorts x starting at position start. */
private static void sort(String[] x, int start) {
if (start == x.length - 1) {
return;
}

// Find the smallest item
int smallestId = findSmallest(x, start);
// Swap
swap(x, start, smallestId);
// Selection sort the rest ... using recursion!
sort(x, start + 1);
}

/** Returns the index of the smallest String in x, starting at start. */
public static int findSmallest(String[] x, int start) {
int smallestId = start;
for (int i = start; i < x.length; i++) {
int cmp = x[i].compareTo(x[smallestId]);
// from the internet, if x[i] < x[smallestId], cmp will be -1
if (cmp < 0) {
smallestId = i;
}
}
return smallestId;
}

/** Swaps item at position a with item at position b. */
public static void swap(String[] x, int a, int b) {
String temp = x[a];
x[a] = x[b];
x[b] = temp;
}
}

Unit Testing

Unit Testing is a great way to rigorously test each method of your code, and ultimately to ensure that you have a working project.

The ā€œUnitā€ part of Unit Testing comes from the idea that you can break your program down into units, or the smallest testable part of an application. Therefore, Unit Testing enforces good code structure (each method should only do ā€œOne Thingā€), and allows you to consider all of the edge cases for each method and test for them individually.