java - 重构 JUnit 测试以扩展多个方法和测试用例

标签 java junit enums

作为算法和单元测试的个人练习,我在 Sort 类中实现了不同的排序算法,并使用 JUnit Jupiter 对其进行了测试。
这个想法是设置一些测试用例,然后针对它们测试每个实现。

问题: 是我正在努力以这样一种方式编写测试类:

  1. 针对所有测试用例统一测试排序算法,即使我们将来返回并添加更多测试用例,并且
  2. 统一测试 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/

相关文章:

java - 如何更改网络服务 url 端点?

c# - 使用枚举项调用方法

java - 如何模拟模拟对象的方法调用?

java - 有没有办法在 Junit 测试中使用自定义 TestNG 报告器?

c# - 为属性分配随机枚举值

c - C 中的 void display_board(enum cell_contents board[][BOARDWIDTH])

java - spring security认证添加子域

java - 如何按字典顺序对二维数组进行排序?

java - 如何将数据从一个 Activity 传递到当前可见/Activity Activity ?这是一个很好的共同模式吗?

java - NetBeans 中的 GWT JUnit 测试