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负责。这也会改变生产代码,如果测试驱动的开发对您的编码方式有很大影响,我不想这样做。这通常会改进代码。但有时它违反了信息隐藏,使信息对测试可见。您不能拥有完美的测试覆盖率和完美的信息隐藏。