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

时间:2018-02-02 09:25:03

标签: java junit enums

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

问题:是我努力以这样的方式编写测试类:

  1. 针对所有测试用例统一测试排序算法,即使我们将来再添加更多测试用例
  2. 统一测试Sort类中的所有排序算法,即使我们回来并在将来添加更多算法
  3. 尝试

    [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,所以我想知道如何最好地解决这个问题。此外,还存在自动枚举排序算法的问题。

    尽管我喜欢解决这个子问题,但我也愿意接受一种完全不同的方法,只要我们能够解决我在本问题开头所概述的主要问题。

1 个答案:

答案 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()来创建数组的副本,这样就不会修改原始数组。