是否可以创建类的实例进行测试而无需调用构造函数?

时间:2019-03-01 17:54:14

标签: java unit-testing junit mockito

是否可以在不模拟或调用其构造函数的情况下用复杂的构造函数实例化一个类?这将很有用,因为每次向服务添加新的依赖项时,单元测试类都不需要更改。

模拟服务通过创建类的新实现(具有空方法重写)解决了问题,但这不是实现的实例。这是一个问题,因为每当调用模拟服务中的方法时,都必须告知Mockito调用真实方法。取而代之的是,该服务的实际实现将是首选。

例如:

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<h1>IT 411</h1>
<h2>Displaying a gallery of images</h2>
<hr />
<p>Click anywhere on this page to make the image move using mousemove</P>
<p id="clickstatus"></p>
</div>

<div id="gallery">
  <img id="moveimage" class="image" src="images/gnu.jpg" height="200px" width="250px" />
</div>
<!-- 1. -->
<div id="coordinates"></div>

在单元测试类中,是否可以在无需调用其构造函数的情况下实例化实现?

class ComplexService {
    private Service service1;
    private Service service2;
    private Service service3;
    private Service service4;
    private Service service5;

    ComplexConstructor(Service service1, Service service2, Service service3, Service service4, Service service5) {
        this.service1 = service1;
        this.service2 = service2;
        this.service3 = service3;
        this.service4 = service4;
        this.service5 = service5;
    }

    boolean methodToTest() {
        return service1.get();
    }
}

修改

有可能使用反射,我希望JUnit或Mockito中有一种内置的方式来实现相同的目的。这是使用反射的方法。

public class ComplexConostructorTest {
    private ComplexConstructor complexConstructor;
    private Service serviceMock;

    @Before
    public void init() {
        /*
         Somehow create implementation of complexConstructor
         without calling constructor

         . . .
          */

        // Mock dependency
        ReflectionTestUtils.setField(complexConstructor, 
                "service1", 
                serviceMock = Mockito.mock(Service.class));
    }

    @Test
    public void service1Test() {
        when(serviceMock.get())
                .thenReturn(true);

        assertTrue(complexConstructor.methodToTest());
    }
}

5 个答案:

答案 0 :(得分:0)

据我所知,这是不可能的,但是您可以简单地向类中添加一个更简单的构造方法并将其用于测试。另一方面,如果测试以与应用程序中不同的状态测试对象,则我不确定这种测试的效果如何。

答案 1 :(得分:0)

您可以在类中添加无参数构造函数,并为所有字段创建设置器。

class ComplexService {
    private Service service1;
    private Service service2;
    private Service service3;
    private Service service4;
    private Service service5;

    public ComplexService(){
      super();
    }
    ...

    public void setService1(Service service1){
      this.service1 = service1;
    }
    //for other fields too
    ...
}

在测试中,您将其命名为:

myService = new ComplexService()
myService.setService1(service1);
...

答案 2 :(得分:0)

您可以,但是您可能不想:

ComplexConstructor partialMock =
    Mockito.mock(ComplexConstructor.class, CALLS_REAL_METHODS);

"partial mock"实例将不会调用其构造函数或字段初始化器,但是对被测系统的所有调用都将调用该类的实际行为。 (从技术上讲,出于Mockito的目的,该类也将覆盖其equalshashCode,并且该类将是ComplexConstructor的生成子类,而不是ComplexConstructor本身。)

通过这种方式,您可以与构造函数隔离,但是由于您要抑制被测类行为的任意子集,因此准确确定要测试的内容困难得多。 >以便使系统确信,因为测试通过了。那应该是您测试的主要目标,而使用部分模拟可能很难实现这一目标。正是出于这个原因,同事或合作者可能会正确地观察到您不应嘲笑被测系统

尽管我个人认为您不需要更改单元测试以在需要时提供模拟是错误或意外的,但是您可以创建一个与提供ComplexConstructor测试实例的测试分开的工厂,或者考虑使用依赖项自动向您的被测系统提供模拟的注入框架。

答案 3 :(得分:0)

您可以在测试代码中创建辅助方法,例如,一些具有描述性名称的工厂方法可以构造对象。例如,make_default_ComplexService等等,正是您的测试所需要的。然后,测试可以使用这些方法,并且如果构造函数发生更改,则在许多情况下,您仅需要更新辅助方法,而不必更新所有测试。这种方法具有足够的通用性,也可以将测试与剧烈的变化区分开来,例如将“带参数的构造函数”方法转换为“带有大量设置器的无参数构造函数”方法。

这种方法将减少测试的维护工作,您仍将使用原始构造函数(因为它是由工厂调用的),而不是一些伪造的构造函数,并且测试代码甚至比直接构造函数的调用更具可读性如果正确选择了工厂方法的名称。

答案 4 :(得分:0)

看起来您正在混淆多个术语和概念。让我帮您更好地了解它们。

  

这很有用,因为每次向服务添加新的依赖项时,单元测试类都不需要更改。

您的类具有许多依赖关系,这些依赖关系是通过构造函数提供的。如果您正在编写单元测试,则您的目标是来测试此依赖类,则应模拟所有依赖项。这就是为什么它称为单元测试。这意味着,对于您班级的每个新依赖项,应该通过添加新的模拟及其模拟行为来更新测试。

  

必须告知Mockito调用real方法。取而代之的是,该服务的实际实现将是首选。

请考虑集成测试,在这种情况下,您只能模拟一定数量的依赖关系,而其他依赖关系将按预期或“真实”的方式运行,直到您当然对它们进行模拟为止。但是,如果您只是想避免支持测试,那不是正确的方法。

请不要尝试通过反思来破坏测试中的测试类。这可能会导致错误的测试结果,浪费时间和总体上令人失望:)诸如PowerMock和JMockit之类的模拟库提供了任何种类的hack,例如您尝试实现的hack,它们通常过于强大。

  

强大的力量带来更大的责任感