作为算法和单元测试的个人练习,我在Sort
类中实现了不同的排序算法,并使用JUnit Jupiter进行测试。
我们的想法是设置一些测试用例,然后针对它们测试每个实现。
问题:是我努力以这样的方式编写测试类:
Sort
类中的所有排序算法,即使我们回来并在将来添加更多算法。尝试
[1] 在我开始担心这些扩展之前,我首先手动设置所有内容:
// Sort class
public class Sort {
public static int[] insertionSort (int[] array) { ... }
public static int[] selectionSort (int[] array) { ... }
}
// JUnit
class SortTest {
private static int[] expectedCase1;
private static int[] expectedCase2;
private int[] testCase1;
private int[] testCase2;
@BeforeAll static void setExpectedArrays () {
// Initialise expected results
expectedCase1 = new int[]{1};
...
}
@BeforeEach void setTestArrays () {
// Initialise/reset test arrays
}
@Test void insertionSortCase1 () {
assertArrayEquals(
expectedCase1,
Sort.insertionSort(testCase1)
);
}
@Test void insertionSortCase2 () { ... }
@Test void selectionSortCase1 () { ... }
@Test void selectionSortCase2 () { ... }
}
但随着我继续实施更多排序算法和测试用例,这种手动方法很快变得乏味。这些都不是自动的,我很可能会制作复制粘贴错误,无法使测试结果无效(例如仍然在新的Sort.selectionSort(testCase1);
上调用@Test void mergeSortCase1 ()
。
[2] 然后我了解了@ParameterizedTest并尝试将测试用例封装在枚举中:
// Inside the test class:
private enum ArrayCase {
SINGLETON(
new int[]{2}, // expected array
new int[]{2} // test array
),
REVERSE_SORTED(
new int[]{-1,3,6,7,9},
new int[]{9,7,6,3,-1}
),
...
private final int[] sortedArray;
private final int[] testArray;
// Constructor
ArrayCase (int[] sortedArray, int[] testArray) {
this.sortedArray = sortedArray;
this.testArray = testArray;
}
// Accessor
public int[] expectedArray () { ... }
public int[] testArray () { ... }
}
然后实际测试减少到:
@ParameterizedTest
@EnumSource(ArrayCase.class)
void insertionSort (ArrayCase arrayCase) {
assertArrayEquals(
arrayCase.expectedArray(),
Sort.insertionSort(arrayCase.testArray())
);
}
我还可以通过在枚举中添加另一个元素来添加新案例,这样我们就不必触及实际的测试代码了:
MOSTLY_DUPLICATES(
new int[]{-1,-1,0,0,0,2,2,2,3,4,4,4,7,7,7,9,9},
new int[]{4,9,4,-1,9,0,2,2,2,7,-1,0,3,4,7,0,7}
),
...
我仍然需要为@ParameterizedTest
类中的每个方法编写一个Sort
,但它变得更易于管理。
子问题:然而,当我继续将选择排序的测试转换为这个习惯用法时,我发现它的测试数组已经从之前的参数化测试中排序,因为插入排序是它们都在场景后面的枚举中共享相同的数组
我无法在这种结构中加入@BeforeAll
或@BeforeEach
,所以我想知道如何最好地解决这个问题。此外,还存在自动枚举排序算法的问题。
尽管我喜欢解决这个子问题,但我也愿意接受一种完全不同的方法,只要我们能够解决我在本问题开头所概述的主要问题。
答案 0 :(得分:1)
首先,我建议为排序逻辑创建一个接口:
public interface ISortingAlgorithm {
int[] sort(int[] input);
}
然后,每个排序算法都是实现此接口的类。
之后,我将创建一个参数化的抽象测试类,它包含不同的测试用例(不是特定于排序算法的实现)。
public abstract class AbstractSortingTest {
private ISortingAlgorithm algorithm;
@BeforeAll
public void setUp() {
this.algorithm = createAlgorithm();
}
protected abstract ISortingAlgorithm createAlgorithm();
@ParameterizedTest
@ValueSource(...)
public void testSorting1(int[] input, int[] expectedResult) {
assertEquals(expectedResult, this.algorithm.sort(input));
}
}
然后,您可以为每个算法创建一个子类,您只需要在其中创建算法实例。 (可能你甚至不需要抽象类,也可以参数化使用过的实现。)
public class InsertionSortTest extends AbstractSortingTest {
@Override
protected ISortingAlgorithm createAlgorithm() {
return new InsertionSort();
}
}
关于您的子问题: 根据您的实现,您可能正在修改原始输入数组。我建议使用System.arraycopy()来创建数组的副本,这样就不会修改原始数组。