【问题标题】:How do I reuse JUnit test methods for different testing contexts?如何为不同的测试上下文重用 JUnit 测试方法?
【发布时间】:2020-01-23 13:30:48
【问题描述】:

到目前为止,我一直在开发测试以支持我的项目中的某个应用程序。现在我已经启动并运行了一组测试,我需要为另一个类似的应用程序提供支持。这两个应用程序的结果在我的系统中应该是相同的,因为它们都产生相同的操作,但来自不同的上下文。问题是,我正在寻找一种方法来重用我为第一个测试所做的测试,而无需为另一个测试用例复制他们的代码。

我目前在 Java EE 8 下使用 JUnit 4。

我的情况是这样的。给定一个类似下面的测试:

class TestExample {
    Context context;

    @Before
    public void setUp() {
        context = new ContextA();
    }

    @After
    public void terminate() {
        context.dispose();
    }

    @Test
    public void test1() {
        context....
    }
}

abstract class Context {
    @Override
    public void dispose() {
        ...
    }
}

class ContextA extends Context {
}

class ContextB extends Context {
}

我希望能够为 ContextA 做一次测试,然后为 ContextB 做一次测试。

---o---

所以在一段时间后回到这个问题之后,我从这个线程中收集了三个可能的解决方案:

  1. 参数化
  2. 测试继承
  3. 上下文构成

但是,在对真实的测试用例进行了全部尝试之后,我发现它们都不是完美的解决方案。它们确实解决了我在这个问题上提出的问题,但是当一个真实案例出现并且它们之间的上下文开始略有不同时,这些选项就会开始变得复杂和丑陋。

最后,我选择了最简单的解决方案:为每个不同的上下文复制测试源代码。它可以工作,但这样做的问题是,随着时间的推移,维护测试变得越来越难,因为对单个测试的更改必须复制到它的所有副本。

【问题讨论】:

标签: java unit-testing design-patterns junit


【解决方案1】:

如何为不同的测试上下文重用 JUnit 测试方法?

通常,您会

单元测试的一个核心点是:当您稍后对生产代码进行更改时,它们可以帮助您快速识别错误。含义:单元测试的主要要求是您能够快速阅读和理解它们。任何使这变得更加困难的事情,例如避免代码重复的“复杂”解决方案都是有问题的。

从这个角度来看,测试代码的“规则”有时与生产代码的“规则”不同。在生产代码中,您希望(几乎)不惜一切代价避免代码重复。

然而,对于测试代码,拥有两个不同的测试类来做非常相似的事情……两次是完全可以的。也许,只是也许您将通用代码提取到一些实用程序类中的辅助方法(例如执行特定的复杂断言)中。

话虽如此,一个潜在的解决方案:如果您真的想在多个“输入”上运行完全相同的测试,那么我建议您查看 parameterized 测试示例。您可以简单地拥有多个不同的 context 对象,并为每个对象运行所有测试,而不是在 @Before 代码中硬编码您的 context 实例。

【讨论】:

    【解决方案2】:

    一种不同的方法可能是改进您的设计

    在您的情况下,两者都向我们投射相同的基类。您可以通过将Context 转换为普通类(删除abstract 关键字)并将其移至通用基础项目来应用优先组合而不是继承 方法。这样您就可以在一个点上测试Context 的行为。

    您的专用类ContextAContextB 将获得Context 的实例作为构造函数参数注入,以便您可以在他们自己的测试中模拟它。

    interface Context {
       public void dispose();
    }
    
    class ContextImpl implements Context {
        @Override
        public void dispose() {
            ...
        }
    }
    
    class ContextA implements Context {
        private final Context common;
        ContextA(@Inject Context common){
           this.common = common;
         @Override
        public void dispose() {
            common.dispose();
        }
    }
    
    class ContextB implements Context {
        private final Context common;
        ContextB(@Inject Context common){
           this.common = common;
         @Override
        public void dispose() {
            common.dispose();
        }
    }
    

    然后您验证(连同 ContextAContextB 的特殊行为)是否调用了传递的 Context 实例上的预期方法。

    是的,您仍然有重复的测试,但它们非常简单,只要Context 中的方法签名不变,它们就永远不会改变。


    有时最好反过来做,将专门的实现注入到持有共同行为的类中......

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-05-07
      • 2011-08-13
      • 1970-01-01
      • 2014-01-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-12-30
      相关资源
      最近更新 更多