【问题标题】:organizing unittests in java/junit for testing classes with common api在 java/junit 中组织单元测试以使用通用 api 测试类
【发布时间】:2013-07-16 15:46:38
【问题描述】:

我正在实现一些基本的排序算法(出于学习的目的),并想为它们编写单元测试。所有排序程序都有以下通用api

...
public static void sort(Comparable[] a);
...
public static boolean isSorted(Comparable[] a);
...
public static boolean isSorted(Comparable[] a),int from ,int to;
...

所以,我编写了以下测试来测试 SelectionSort 中的 isSorted() 方法

public class SelectionSortTests {
        String[] a ;    

    @After
    public void tearDown() throws Exception {
            a = null;
    }

    @Test
    public void arraySortedSingleElement(){
        a = new String[]{"A"};
        Assert.assertTrue(SelectionSort.isSorted(a));
    }

    @Test
    public void arraySortedDistinctElements(){
        a = new String[]{"A","B","C","D"};
        Assert.assertTrue(SelectionSort.isSorted(a));
    }
    @Test
    public void arrayNotSorted(){
        a = new String[]{"A","B","C","B"};
        Assert.assertFalse(SelectionSort.isSorted(a));
    }
...
}

现在我觉得如果我要为说 InsertionSort、ShellSort 等编写测试,它们看起来会一样..只有被测试类的名称会改变..

那么,我应该如何组织测试呢?套件是答案还是我可以使用反射做得更好 - 可以编写一个驱动程序,我可以向其中添加要测试的类的名称列表,并且驱动程序调用通过将类名传递给它来运行公共单元测试。 .

我意识到这是一种常见的情况..想知道如何在没有唾沫或玻璃纸的情况下处理这种情况

更新: 感谢@BevinQ 和@Matthew Farwell,我尝试使用参数化单元测试来解决这个问题。 使用反射调用静态方法.. 似乎工作:) 虽然我认为它仍然可以重构以避免重复代码

@RunWith(Parameterized.class)
public class ParameterizedSortTests {
    private Class classToTest;
    private Method methodToTest;

    public ParameterizedSortTests(String packageName,String classToTest) {
        super();
        try {
            this.classToTest = Class.forName(packageName+"."+classToTest);
        } catch (ClassNotFoundException e) {
            System.out.println("failed to get class!!");
            e.printStackTrace();
        }

    }

    //method return collection of class names to be tested
    @Parameterized.Parameters
    public static  List<Object[]> classesToTest(){
        return Arrays.asList(new Object[][]{ 
                {"elemsorts","SelectionSort"} ,
                {"elemsorts","InsertionSort"} 
        });
    }


    public void setMethod(String method,Class...args){
        try {
            this.methodToTest = this.classToTest.getMethod(method, args);
        } catch (SecurityException e) {

            e.printStackTrace();
        } catch (NoSuchMethodException e) {

            e.printStackTrace();
        }
    }

    @Test
    public void arrayIsSorted(){
        setMethod("isSorted",Comparable[].class);
        String[] a = new String[]{"A","B","C","D"};
        Boolean arraySorted = null;
        try {
            arraySorted = (Boolean)this.methodToTest.invoke(null, new Object[]{a});
            System.out.println(this.methodToTest+"returned :"+arraySorted);
        } catch (IllegalArgumentException e) {

            e.printStackTrace();
        } catch (IllegalAccessException e) {

            e.printStackTrace();
        } catch (InvocationTargetException e) {

            e.printStackTrace();
        }

        Assert.assertTrue(arraySorted);
    }

    @Test
    public void arrayIsNotSorted(){
        setMethod("isSorted",Comparable[].class);
        String[] a = new String[]{"A","B","C","B"};
        Boolean arraySorted = null;
        try {
            arraySorted = (Boolean)this.methodToTest.invoke(null, new Object[]{a});
            System.out.println(this.methodToTest+"returned :"+arraySorted);
        } catch (IllegalArgumentException e) {

            e.printStackTrace();
        } catch (IllegalAccessException e) {

            e.printStackTrace();
        } catch (InvocationTargetException e) {

            e.printStackTrace();
        }
        //System.out.println("arraySorted="+arraySorted);
        Assert.assertFalse(arraySorted);
    }   

}

【问题讨论】:

  • 困难在于使您的方法静态化。如果您要使它们不是静态的并实现一个接口。你会发现生活轻松多了。如果你想要一些结构,你将不得不使用反射来调用方法。

标签: java junit organization


【解决方案1】:

接口

public abstract class AbstractSortTests {
    String[] a ;    

    @After
    public void tearDown() throws Exception {
        a = null;
    }

   protected abstract Sorter getSorter();

    @Test
    public void arraySortedSingleElement(){
        a = new String[]{"A"};
        Assert.assertTrue(getSorter().isSorted(a));
    }

    @Test
    public void arraySortedDistinctElements(){
        a = new String[]{"A","B","C","D"};
        Assert.assertTrue(getSorter.isSorted(a));
    }
...
}

public class SelectionSortTests extends AbstractSortTests {

    protected Sorter getSorter(){
        return SelectionSort.getInstance();
    }

}

public class QuickSortTests extends AbstractSortTests {

    protected Sorter getSorter(){
        return QuickSort.getInstance();
    }

}

使用反射有点麻烦,但仍然可行。我没有测试过这段代码,所以可能有 有几个错误,但过去曾使用过这种方法。在 99% 的情况下,使用接口是首选方法。

public abstract class AbstractSortTests {
    String[] a ;    

    @After
    public void tearDown() throws Exception {
        a = null;
    }

   protected abstract Sorter getSorter();

    @Test
    public void arraySortedSingleElement() throws Exception{
        a = new String[]{"A"};
        Assert.assertTrue(executeMethod(getSorterClass(), "isSorted", a);
    }

    @Test
    public void arraySortedDistinctElements() throws Exception{
        a = new String[]{"A","B","C","D"};
        Assert.assertTrue(executeMethod(getSorterClass(), "isSorted", a);
    }

    private void executeMethod(Class<?> sortClass, String methodName, String[] values) throws Exception{
        return sortClass.getDeclaredMethod(methodName, new Class[]{String[].class}).invoke(null, new Object[]{values});
    }
...
}

public class SelectionSortTests extends AbstractSortTests {

    protected Class<?> getSorterClass(){
        return SelectionSort.class;
    }

}

【讨论】:

  • 这里的重点是引入Sorter接口。
【解决方案2】:

正如@BevynQ 所说,如果您将方法设置为非静态方法并实现接口(下面称为Sorter),那么您的生活会轻松很多。您可以轻松使用Parameterized。这是一个如何使用它的非常简单的示例,(未经测试,未经编译)

@RunWith(Parameterized.class)
public class SorterTest {
  @Parameters
  public static Iterable<Object[]> data() {
    return Arrays.asList(new Object[][] {
      { new SelectionSort() },
      { new BubbleSort() }
    });
  }

  private final Sorter sorter

  public SorterTest(Sorter sorter) {
    this.sorter = sorter;
  }

  @Test
  public void arraySortedSingleElement(){
    String[] a = new String[]{"A"};
    Assert.assertTrue(sorter.isSorted(a));
  }

  @Test
  public void arraySortedDistinctElements(){
    String[] a = new String[]{"A","B","C","D"};
    Assert.assertTrue(sorter.isSorted(a));
  }

  @Test
  public void arrayNotSorted(){
    String[] a = new String[]{"A","B","C","B"};
    Assert.assertFalse(sorter.isSorted(a));
  }
}

【讨论】:

    【解决方案3】:

    为什么不这样呢?

    @Test
    public void arraySortedDistinctElements(){
        a = new String[]{"A","B","C","D"};
        Assert.assertTrue(SelectionSort.isSorted(a));
        Assert.assertTrue(InsertionSort.isSorted(a));
        Assert.assertTrue(QuickSort.isSorted(a));
    }
    

    我不认为你有超过 10 种不同的分类要测试。所以应该不错。

    否则,您可以在 Array 中声明所有排序类并使用 Class 属性加载。

    【讨论】:

    • 那会起作用..但是每次我编写一个新的排序类时我都会编辑所有的测试方法。理想情况下,要测试的新类的名称应该添加到测试中的一个位置程序
    • 然后你可以声明一个类数组并使用类属性来加载它们。您有属性可以在 JUnit 测试用例初始化期间定义一次加载[不记得注释名称]
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-03-15
    • 1970-01-01
    • 2012-12-25
    • 1970-01-01
    • 2011-07-06
    • 2013-02-04
    • 2015-04-25
    相关资源
    最近更新 更多