【问题标题】:Setting up a test system with "real data"用“真实数据”搭建测试系统
【发布时间】:2012-10-19 12:28:00
【问题描述】:

所以现在我正在使用 JUnit 4 并在 @BeforeClass 方法中设置了重置用户架构或准备示例数据所需的一切。 现在,并不是我不喜欢这种方法,而是我发现它非常令人沮丧,原因如下:

  • 我正在使用 Parameterized 注释对不同的输入数据运行相同的测试。参数化不适用于 @BeforeClass,因为 @BeforeClass 使用静态方法。

这意味着如果我想保留@BeforeClass 逻辑,我必须复制测试。我不能使用@After 和@Before,因为这些都会在每次测试之后发生,这将是一个开销。

我想我可以重构这个单元测试,因为我将编写一个处理测试的抽象类,并为我想尝试的每个组参数编写一个子类,这样我就可以只编写一次测试代码。

我希望您可以建议一个更简洁的选项,起点如下:使用@Parameterized,每个参数组只需要运行一次“数据库”方法。

编辑:

这是我的班级没有 BeforeClass 的示例

RunWith(LabelledParameterized.class)
public class TestCreateCampaign extends AbstractTestSubscriberCampaign {

    public TestCreateCampaign(String label, String apiKey, String userKey,
            int customerId) {
        super(label, apiKey, userKey, customerId);
    }

    @Before
    public void setUp() throws Exception {
        super.setUp();
    }

    @After
    public void tearDown() throws Exception {
        super.tearDown();
    }

    @Parameters
    public static Collection<Object[]> generatedData() {
        return DataProvider.generatedCorrectSubscriberData();
    }

    @Test
    public void testCreateEmailCampaignBothTriggered() {

        // TEST

    }

    @Test
    public void testCreateTextCampaignTriggered() {

        // TEST

    }

    @Test
    public void testCreateTextCampaignTest() {

        // TEST

    }

    // Other Tests

}

【问题讨论】:

  • 那么目前,您是否使用Enclosed 和多个参数化内部类?每个都在*类中使用静态设置方法?每个参数组一次?请澄清。
  • 当你说参数化在 BeforeClass 上不起作用时,你是什么意思?您的意思是该方法没有运行,还是以错误的顺序运行?你能发布一些示例代码吗?
  • @MatthewFarwell 我相信他的意思是 BeforeClass 只运行一次,而不是每组参数化值运行一次。之前为每个测试运行一次。我相信他想要每个参数化的值集一次。

标签: java testing junit junit4


【解决方案1】:

这取决于您要如何设置课程,但您可以为此使用ClassRule。这与TestRule 的作用相同,但它为每个类而不是每个测试运行一次。这可以与Parameterized和TestRule结合,例如:

@RunWith(Parameterized.class)
public class TestCreateCampaign {
  @ClassRule
  public static ExternalResource beforeAfterClass = new ExternalResource() {

    @Override
    protected void before() throws Throwable {
      System.out.println("before each class");
    }

    @Override
    protected void after() {
      System.out.println("after each class");
    }
  };

  @Rule
  public ExternalResource beforeAfter = new ExternalResource() {
    @Override
    protected void before() throws Throwable {
      System.out.println("before each test");
    }

    @Override
    protected void after() {
      System.out.println("after each test");
    }
  };

  @Parameters(name = "{index}: fib({0})={1}")
  public static Iterable<Object[]> data() {
    return Arrays.asList(new Object[][] { { 3, 0 }, { 4, 1 } });
  }

  private int fInput;
  private int fExpected;

  public TestCreateCampaign(int input, int expected) {
    fInput = input;
    fExpected = expected;
  }

  @Test
  public void test1() {
    System.out.println("test1 fInput=" + fInput);
  }
}

这会产生以下输出:

before each class
before each test
test1 3
after each test
before each test
test1 4
after each test
after each class

这似乎是您正在寻找的。为了减少重复的数量,当然可以在单独的 java 类中定义 beforeAfterClass 和 beforeAfter。

这些在 JUnit 4.9+ 中可用。

【讨论】:

  • OP 正在寻找的是两次运行“课前”和“课后”方法,每组参数化值运行一次。
  • 正如 John B 所说,我需要基于参数运行它。
  • 那我就不明白用例了。 @Parameters 需要是静态的并且在一个单独的类中,所以如果你想拥有一组参数,你需要有一个测试类。你的问题是你想参数化测试类,所以你有一个测试类,你可以传递一组参数,并且该测试类本身被参数化?
【解决方案2】:

从参数化测试类的构造函数调用你的设置方法怎么样?

编辑:

好的,我不知道有什么会自动执行此操作,但我认为您可以编写 Rule 来执行此操作。您可以从扩展ExternalResource 的头开始实现Rule。这是我认为它会做的事情。

  1. 构造函数将采用测试类的一个实例和一个ExternalResource 实例。
  2. 在构造函数中,它会找到包含@Test 注释的方法列表并获取计数。它将迭代计数设置为 0。
  3. before 方法中,它会增加迭代计数,如果在增加后为1(或之前为0),它将在传递的ExternalResource 上调用before 方法。
  4. after 方法中,它将检查迭代计数是否等于测试次数,如果是,则在传递的ExternalResource 上调用after 方法。

您可能需要使用不同的回调类/接口和ExternalResource,因为beforeafter 方法是protected。如果你真的想要很酷,你可以在你的规则中定义你自己的 BeforeParametersAfterParameter 注释,它会在传递的实例中查找这些方法。

如果您开发了这个,请将其发布或提交给 JUnit 以供包含。

这是我想出的,没有我想要的那么好:

@RunWith(Parameterized.class)
public class TestExample {

private interface BeforeAfter {
    void before();

    void after();
}

public static class Resource extends ExternalResource {

    private final int count;
    private final BeforeAfter ba;
    private int iteration = 0;

    Resource(Object instance, BeforeAfter ba) {
        int localCount = 0;
        for (Method method : instance.getClass().getMethods()) {
            if (method.getAnnotation(Test.class) != null) {
                localCount++;
            }
        }
        this.count = localCount;
        this.ba = ba;
    }

    @Override
    protected void before() throws Throwable {
        if (iteration == 0) {
            ba.before();
        }

        iteration++;
    }

    @Override
    protected void after() {
        if (iteration == count) {
            ba.after();

            iteration = 0;
        }
    }
}

@Parameters
public static Iterable<Object[]> data() {
    return Arrays.asList(new Object[][] { { 3, 0 }, { 4, 1 } });
}

@Rule
public static Resource resource = new Resource(new TestExample(0, 0), new BeforeAfter() {

    @Override
    public void before() {
        System.out.println("setup");
    }

    @Override
    public void after() {
        System.out.println("cleanup");

    }
});

private int fInput;
private int fExpected;

public TestExample(int input, int expected) {

    // System.out.println("Constructor invoked" + fInput);
    fInput = input;
    fExpected = expected;
}

@Test
public void test1() {
    System.out.println("test1 fInput=" + fInput);
}

@Test
public void test2() {
    System.out.println("test2 fInput=" + fInput);
}
}

导致:

setup
test1 fInput=3
test2 fInput=3
cleanup
setup
test1 fInput=4
test2 fInput=4
cleanup

【讨论】:

  • 添加了您要求澄清的内容。
  • 是的,但是从构造函数调用是否由于某种原因不起作用?
  • 所以,我知道这将是一个线索,但是......你可以有一个等于测试数量的计数值(在构造函数中设置)。在 After 中,将该值递减,如果为 0,则调用 AfterClass。
  • 是的,对不起,我的目标是在 JUNIT4 中默认使用参数化的东西,AfterClass 也是如此。
  • 我尝试了你的建议,但是在每个测试方法之后都会调用构造函数。不是在测试每个参数之后。
【解决方案3】:

请参阅How to load DBUnit test data once per case with Spring Test,了解每次测试运行仅初始化一次测试数据的方法。

【讨论】:

    最近更新 更多