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

Java 重构JUnit测试以扩展到多个方法和测试用例,java,junit,enums,Java,Junit,Enums,作为算法和单元测试方面的个人练习,我正在Sort类中实现不同的排序算法,并使用JUnit Jupiter进行测试。 其思想是建立一些测试用例,然后针对它们测试每个实现 问题是:我正在努力编写测试类,以便它: 对所有测试用例统一测试排序算法,即使我们将来回来添加更多测试用例,并且 统一测试Sort类中的所有排序算法,即使我们将来回来添加更多算法 尝试 [1]在我开始担心这些扩展之前,我首先手动设置所有内容: // Sort class public class Sort { public

作为算法和单元测试方面的个人练习,我正在
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 () { ... }
    
    }
    
    但随着我继续实现更多排序算法和测试用例,这种手动方法很快变得单调乏味。这些都不是自动的,我很可能会出现复制和粘贴错误,这些错误会导致测试结果无效(例如,仍然在新的
    @test void mergeSortCase1()
    上调用
    Sort.selectionSort(testCase1);

    [2]然后我学习了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())
        );
    }
    
    我还可以通过向enum添加另一个元素来添加新案例,这样我们就不必接触实际的测试代码:

    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
    类中的每个方法编写一个
    @parameterizetest
    ,但是它变得更易于管理

    子问题:然而,当我继续将选择排序的测试转换成这个习惯用法时,我发现它的测试数组已经从先前的插入排序参数化测试中排序,因为它们在幕后的枚举中共享相同的数组。
    我无法将
    @BeforeAll
    @beforeach
    合并到这种结构中,因此我想知道如何最好地解决这个问题。此外,还存在自动枚举排序算法的问题


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

    首先,我建议为排序逻辑创建一个接口:

    public interface ISortingAlgorithm {
      int[] sort(int[] input);
    }
    
    然后,每个排序算法都是实现该接口的类

    之后,我将创建一个参数化的抽象测试类,该类包含不同的测试用例(它们不是特定于排序算法的实现)

    然后,您可以为每个算法创建一个子类,您只需要在其中创建一个算法实例。 (也许,您甚至不需要抽象类,也可以参数化所使用的实现。)

    关于您的子问题:
    根据您的实现,您可能正在修改原始输入阵列。我建议使用System.arraycopy()创建阵列的副本,这样您就不会修改原始阵列。

    子问题:谢谢!我通过让测试阵列访问器返回阵列的克隆来实现它。主要问题:[a]在本例中,您将如何提供测试用例并通过@ValueSource将它们提供给测试人员?[B]您能否澄清您所说的“.参数化所使用的实现…”是什么意思?您的意思是我们也可以将不同的排序算法作为另一个参数提供给测试人员?最后(稍微无关)[C]当在一个类中的多个实现与一个接口的几个子界面之间进行选择时,我们应该考虑什么样的设计选择?[A]对不起,我猜想 @ ValueSurCE 在这种情况下不起作用。<代码> @枚举源或<代码> >源代码< /代码>应该工作()[B]这在理论上是可能的,但我个人更喜欢使用测试类继承的拟议解决方案。[C]我不会将同一规范的不同实现放在一个类中。另请参见:strategy pattern哇,strategy pattern非常清晰,现在帮助我更好地理解了您的答案。我将切换到它,然后重新讨论与JUnit相关的问题。在研究它的过程中,我还遇到了动态测试和测试工厂。Do你认为在这种情况下,他们会是一个更合适的选择?
    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();
      }
    }