【问题标题】:Unit testing the obvious单元测试显而易见
【发布时间】:2016-08-30 00:22:48
【问题描述】:

在尝试为功能处理样板代码而非业务逻辑的类编写测试用例时。我开始怀疑单元测试是否真的值得这门课。但是话又说回来,在使用 TDD 时,建议我们为添加的任何逻辑编写测试。

以下面的类为例,只是使用 DI 注入依赖项并获取配置参数来设置应用程序的运行。除了单元测试是否正确注入了依赖项,或者是否在运行完成时调用了destroy(这将是对java CDI框架的单元测试而不是我自己的代码),我还能单元测试什么?

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.ejb.Singleton;
import javax.ejb.Startup;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.inject.Inject;

@Singleton
@Startup
@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public class PipsAlprConnectionRunner {
    @Inject
    private PipsAlprConfiguration config;

    @Inject
    private PipsAlprConnector connector;

    @Inject
    private Scheduler scheduler;

    @Inject
    @PipsAlprAdapterService
    private ServiceStatus status;

    private Timer connectorTimer = null;

    @PostConstruct
    public void initialized() {
        status.started();
        connectorTimer = scheduler.schedule(connector, 0,
                1000 * config.getPollPeriodSeconds());
        status.operational();
    }

    @PreDestroy
    public void destroy() {
        connectorTimer.cancel();
        connector.shutdown();
        status.stopped();
    }
}

在上面的类中,我想不出任何使用 TDD 的测试场景,所以只是想出了代码,现在我想知道我到底可以在这里进行什么单元测试。

【问题讨论】:

    标签: unit-testing jakarta-ee junit dependency-injection tdd


    【解决方案1】:

    好吧,可以证明这个类做了一些事情。它改变一个状态,它启动一个计时器。您可以通过 Mockito 将这些对象的模拟注入到类中,然后确保 initialized() 和 destroy() 都执行您希望它们对这些模拟执行的操作。

    @RunWith(MockitoJUnitRunner.class)
    public class PipsAlprConnectionRunner {
    
        @Mock
        private PipsAlprConfiguration config;
    
        @Mock
        private PipsAlprConnector connector;
    
        @Mock
        private Scheduler scheduler;
    
        @Mock
        private ServiceStatus status;     
    
        @InjectMocks
        private PipsAlprConnectionRunner pipsAlprConnectionRunner ;
    
        @Test
        public void initialized_should_set_status_started() {
             pipsAlprConnectionRunner.initialized();
             Mockito.verify(status).started();
        }
    
        // etc.
    }
    

    如果您想为每个“故障点”创建一个方法或每个方法/测试一个方法,这几乎是个人品味的问题。

    就我个人而言,我会说目标是 100% 的覆盖率,因此即使是主要委托的非常简单的类也应该被覆盖。如果有人改变了什么会发生什么?测试确保此类更改不会破坏现有功能。

    【讨论】:

    • 所以我相信您定义的 PipsAlprConnectionRunner 类实际上是一个与实际 PipsAlprConnectionRunner 不同的测试类?并且所有对象都将通过 DI 插入模拟实现?检查依赖项是否实际注入的单元测试怎么样?但我相信这样的测试是无用的,因为在单元测试运行期间不会发生 CDI 注入
    • 不,PipsAlprConnectionRunner 实际上是您的PipsAlprConnectionRunner。 Mockito 将创建一个并将模拟的依赖项注入其中,这就是 @InjectMocks 的魔力就我个人而言,我不会自己测试 DI,因为这将有效地测试框架(好吧,我们可以为这样的想法提出论据我们正在测试是否存在正确的注释)。我会在稍后阶段简单地进行一些集成测试,无论如何这将依赖于 DI,从而自动测试。
    • 我明白了,谢谢!此外,由于 started() 和 destroy() 方法改变了 PipsAlprConnectionRunner 的内部状态,唯一测试它的方法是通过行为验证然后状态验证?但这只能通过模拟对象来完成,如果我们不使用模拟框架怎么办?或者,在模拟对象出现之前,人们如何对状态难以验证的对象进行单元测试?
    • 假对象。您可以简单地将您的类扩展为一个没有让您询问“是否调用此方法”的行为?当然,你必须为每节课写一个,等等,这样就很麻烦了。但幸运的是,我们现在有了模拟,不必担心 ;-)
    • 我经常看到的一种模式是使用策略模式为具有虚拟值的假对象的接口子类化,并将其注入到单元测试所需但尚未实现的类中。我相信这就是你的意思?通常我已经看到这种模式返回用于测试的固定值。但是我们也可以利用这种模式来测试行为验证吗?
    猜你喜欢
    • 2017-12-21
    • 2016-10-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-07
    • 1970-01-01
    相关资源
    最近更新 更多