作为算法和单元测试的个人练习,我在 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 s 并尝试将测试用例封装在枚举中:
// 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}
),
...
我仍然需要为 Sort
类中的每个方法编写一个 @ParameterizedTest
,但它变得更易于管理。
子问题: 然而,当我继续将选择排序的测试转换成这个习语时,我发现它的测试数组已经从之前的插入排序参数化测试中排序了,因为它们都在幕后的枚举中共享相同的数组。
我无法将 @BeforeAll
或 @BeforeEach
合并到这种结构中,所以我想知道如何最好地解决这个问题。此外,还存在自动枚举排序算法的问题。
尽管我很喜欢解决这个子问题,但我也愿意接受一种完全不同的方法,只要我们能够解决我在这个问题开头概述的主要问题。
最佳答案
首先,我建议为排序逻辑创建一个接口(interface):
public interface ISortingAlgorithm {
int[] sort(int[] input);
}
然后,您的每个排序算法都是一个实现此接口(interface)的类。
然后,我将创建一个参数化的抽象测试类,其中包含不同的测试用例(它们并不特定于排序算法的实现)。
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() 创建数组的副本,这样您就不会修改原始数组。
关于java - 重构 JUnit 测试以扩展多个方法和测试用例,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48579508/