Java 最佳实践-在单元测试中设置没有设置器的字段

Java 最佳实践-在单元测试中设置没有设置器的字段,java,unit-testing,reflection,mockito,getter-setter,Java,Unit Testing,Reflection,Mockito,Getter Setter,假设您有以下要测试的类: public class SomeService { public String someMethod(SomeEntity someEntity) { return someEntity.getSomeProperty(); } } SomeEntity如下所示: public class SomeEntity { private String someProperty; public getSomeProperty() { ret

假设您有以下要测试的类:

public class SomeService {
  public String someMethod(SomeEntity someEntity) {
    return someEntity.getSomeProperty();
  }
}
SomeEntity如下所示:

public class SomeEntity {
  private String someProperty;

  public getSomeProperty() {
    return this.someProperty;
  }
}
 public class TestClass {
      private SomeService someService;

        @Test
          public void testSomeProperty() {
            SomeEntity someEntity = new SomeEntity();
            ReflectionTestUtils.setField(someEntity, "someProperty", "someValue");

            String result = someService.someMethod(someEntity);

            assertThat(result).isEqualTo("someValue");
          }
}
public class TestClass {
  private SomeService someService;

  @Test
  public void testSomeProperty() {
   SomeEntityWithSetters someEntity = new SomeEntityTestWithSetters();
    someEntity.setSomeProperty("someValue");

    String result = someService.someMethod(someEntity);

    assertThat(result).isEqualTo("someValue");
  }


  public class SomeEntityWithSetters extends SomeEntity {
   public setSomeProperty(String someProperty) {
     this.someProperty = someProperty;
   } 
  }
}
public class TestClass {
  private SomeService someService;

  @Test
  public void testSomeProperty() {
    SomeEntity someEntity = mock(SomeEntity.class);
    when(someEntity.getSomeProperty()).thenReturn("someValue");

    String result = someService.someMethod(someEntity);

    assertThat(result).isEqualTo("someValue");
  }
}
您要执行的断言可以是以下内容:

String result = someService.someMethod(someEntity);

assertThat(result).isEqualTo("someValue");
你怎样才能使这个测试有效

1) 在SomeEntity类中为“someProperty”添加一个setter。我认为这不是一个好的解决方案,因为您不会更改生产代码以使测试正常工作

2) 使用ReflectionUtils设置此字段的值。测试将如下所示:

public class SomeEntity {
  private String someProperty;

  public getSomeProperty() {
    return this.someProperty;
  }
}
 public class TestClass {
      private SomeService someService;

        @Test
          public void testSomeProperty() {
            SomeEntity someEntity = new SomeEntity();
            ReflectionTestUtils.setField(someEntity, "someProperty", "someValue");

            String result = someService.someMethod(someEntity);

            assertThat(result).isEqualTo("someValue");
          }
}
public class TestClass {
  private SomeService someService;

  @Test
  public void testSomeProperty() {
   SomeEntityWithSetters someEntity = new SomeEntityTestWithSetters();
    someEntity.setSomeProperty("someValue");

    String result = someService.someMethod(someEntity);

    assertThat(result).isEqualTo("someValue");
  }


  public class SomeEntityWithSetters extends SomeEntity {
   public setSomeProperty(String someProperty) {
     this.someProperty = someProperty;
   } 
  }
}
public class TestClass {
  private SomeService someService;

  @Test
  public void testSomeProperty() {
    SomeEntity someEntity = mock(SomeEntity.class);
    when(someEntity.getSomeProperty()).thenReturn("someValue");

    String result = someService.someMethod(someEntity);

    assertThat(result).isEqualTo("someValue");
  }
}
3) 在测试类中创建一个内部类,该类扩展SomeEntity类并添加此字段的setter。但是,要使其工作,您还需要更改SomeEntity类,因为该字段应变为“受保护”而不是“私有”。测试类可能如下所示:

public class SomeEntity {
  private String someProperty;

  public getSomeProperty() {
    return this.someProperty;
  }
}
 public class TestClass {
      private SomeService someService;

        @Test
          public void testSomeProperty() {
            SomeEntity someEntity = new SomeEntity();
            ReflectionTestUtils.setField(someEntity, "someProperty", "someValue");

            String result = someService.someMethod(someEntity);

            assertThat(result).isEqualTo("someValue");
          }
}
public class TestClass {
  private SomeService someService;

  @Test
  public void testSomeProperty() {
   SomeEntityWithSetters someEntity = new SomeEntityTestWithSetters();
    someEntity.setSomeProperty("someValue");

    String result = someService.someMethod(someEntity);

    assertThat(result).isEqualTo("someValue");
  }


  public class SomeEntityWithSetters extends SomeEntity {
   public setSomeProperty(String someProperty) {
     this.someProperty = someProperty;
   } 
  }
}
public class TestClass {
  private SomeService someService;

  @Test
  public void testSomeProperty() {
    SomeEntity someEntity = mock(SomeEntity.class);
    when(someEntity.getSomeProperty()).thenReturn("someValue");

    String result = someService.someMethod(someEntity);

    assertThat(result).isEqualTo("someValue");
  }
}
4) 您可以使用Mockito来模拟某个实体。如果您只需要在类中模拟一个属性,这似乎很好,但是如果您需要模拟10个这样的属性,该怎么办呢。测试可能如下所示:

public class SomeEntity {
  private String someProperty;

  public getSomeProperty() {
    return this.someProperty;
  }
}
 public class TestClass {
      private SomeService someService;

        @Test
          public void testSomeProperty() {
            SomeEntity someEntity = new SomeEntity();
            ReflectionTestUtils.setField(someEntity, "someProperty", "someValue");

            String result = someService.someMethod(someEntity);

            assertThat(result).isEqualTo("someValue");
          }
}
public class TestClass {
  private SomeService someService;

  @Test
  public void testSomeProperty() {
   SomeEntityWithSetters someEntity = new SomeEntityTestWithSetters();
    someEntity.setSomeProperty("someValue");

    String result = someService.someMethod(someEntity);

    assertThat(result).isEqualTo("someValue");
  }


  public class SomeEntityWithSetters extends SomeEntity {
   public setSomeProperty(String someProperty) {
     this.someProperty = someProperty;
   } 
  }
}
public class TestClass {
  private SomeService someService;

  @Test
  public void testSomeProperty() {
    SomeEntity someEntity = mock(SomeEntity.class);
    when(someEntity.getSomeProperty()).thenReturn("someValue");

    String result = someService.someMethod(someEntity);

    assertThat(result).isEqualTo("someValue");
  }
}

通过对SomeService.someMethod()的junit测试

备选案文1。不应使用此选项,因为无需更改用于编写junit的实体

备选案文2。可以使用

备选案文3。同样是A3,不需要为junit扩展。当类无法扩展时,情况如何


备选案文4。是的,一个不错的选择。mockito的使用也是出于同样的原因。

您可以添加一个具有默认(包专用)作用域的setter。

什么是特定于可测试的
某些服务的行为/契约?根据你的骨架代码,真的没有。它要么在错误的输入上抛出一个NPE,要么返回一个可能为空或不为空的字符串,这取决于Hibernate的魔力。不确定您实际上可以测试什么。

我以前也经历过很多次同样的困境,一个快速的解决方案是让您想要模拟包的字段受到保护,或者提供一个受保护的setter。当然,两者都会改变生产代码

可选地,可以考虑依赖注入框架,例如。下面是他们给出的一个例子:

@Module
class DripCoffeeModule {
  @Provides Heater provideHeater(Executor executor) {
    return new CpuHeater(executor);
  }
}
此JUnit测试使用来自Mockito的模拟对象覆盖DripCoffeeModule对加热器的绑定。模拟被注射到咖啡机和测试中


可以使用反射设置该值。它不需要对生产代码进行任何更改


ReflectionTestUtils.setField(YourClass.class,“fieldName”,fieldValue)

如果类ClassToTest没有任何构造函数或setter。。如何在prod中填充属性。。我猜junit实际上是在测试某种服务方法,而不是Classtotest。如果是这种情况,您可以模拟ClasstoTest。ClasstoTest的测试将单独进行,不应使用模拟。这种情况下,属性由Hibernate设置,它是一个数据库实体,Hibernate不需要设置程序来完成。好的,在这种情况下,Hibernate通过反射设置字段。是的,但这不是我在测试中关心的问题,我已经将ClassToTest重命名为SomeEntity,因为ClassToTest实际上不是我正在测试的类。这只是一个简单的示例,someMethod()可能会做更多的事情,但这只是一个示例,如果someMethod调用了某个应该被模拟的实体。通过这种方式,将测试someMethod。对吗?在我看来,如果someEntity是这个类的依赖项,而不是您正在使用的对象,那么它确实应该被嘲笑。服务、存储库、映射器。。。像这样的事情确实需要模拟。是的,事实上,如果一个服务a与许多服务(如b、c、d)耦合,那么对于一个服务的测试,一个可以模拟所有的依赖关系。BCD的测试将由他们各自的JUnit负责。这也会改变生产代码,如果测试驱动的开发对您的编码方式有很大影响,我不想这样做。这通常会改进代码。但有时它违反了信息隐藏,使信息对测试可见。您不能拥有完美的测试覆盖率和完美的信息隐藏。