单元测试JSR-330注入的对象
单元测试JSR-330注入的对象
我之前使用过Spring DI,并且我认为其中一个好处是我可以在不涉及Spring的情况下测试我的Spring bean类(为了简洁起见,省略了导入):
public class Foo {
private String field;
public void setField(String field) { this.field = field; }
public String getField() { return field; }
}
public class TestFoo {
@Test
public void test_field_is_set() {
Foo foo = new Foo();
foo.setField("Bar");
assertEquals("Bar", foo.getField());
}
}
现在我正在尝试使用JSR-330,这意味着不需要显式编写setter方法。
到目前为止,我使用的是Hk2,纯粹是因为有关Jersey与Hk2相关的一些传闻,以及它使得与其他JSR-330实现共存变得困难。
public class Foo {
@Inject
private String field;
}
我几乎期望会发生一些魔法,即@Inject注解会导致setter方法可用,但事实并非如此:
Foo foo = new Foo();
foo.setField("Bar"); // 类型Foo没有定义setField(String)方法
- 如何(便捷地)测试这种使用注解的类而不调用框架?
- 如果无法实现上述目标,如何以一种可移植的方式调用框架(即不将测试代码紧密耦合到Hk2、Guice等)?
- 如果还无法实现上述目标,有什么典型而干净的方法来测试这种使用注解的类?
问题的出现原因:在进行单元测试时,如果需要测试JSR-330注入的对象,由于这些对象的字段是私有的,无法直接访问和设置字段的值,因此需要找到一种方法来解决这个问题。
解决方法:最简单的方法是将字段的访问权限改为包级私有(package-private),然后在测试中直接设置它们的值。这种方法只适用于测试代码和被测试代码位于同一个包中的情况。这种方法的优点是避免了使用反射,因此在重构时是安全的。
整理成文章:
在进行单元测试时,我们常常会碰到一些问题,特别是在测试JSR-330注入的对象时。由于这些对象的字段是私有的,我们无法直接访问和设置字段的值,因此需要找到一种解决方法。
最简单的方法是将字段的访问权限改为包级私有(package-private),然后在测试中直接设置它们的值。这样,我们可以在测试中创建一个对象实例,并直接设置它的字段值。具体代码如下:
public class Foo { String field; } // 在测试中 Foo foo = new Foo(); foo.field = "bar";
这种方法的优点是避免了使用反射,因此在重构时是安全的。同时,这种方法也适用于测试代码和被测试代码位于同一个包中的情况。
通过将字段的访问权限改为包级私有,我们可以在单元测试中对JSR-330注入的对象进行测试,并直接设置字段的值。这样,我们就能够更方便地进行单元测试,并确保代码的质量和稳定性。
JSR-330注入对象的单元测试问题产生的原因是,JSR-330容器和Spring通常使用直接字段反射而不是setter方法来注入字段。因此,如果不想使用任何DI框架,可以自己编写必要的反射代码来进行单元测试,但这似乎有点过度,只是为了避免测试依赖;毕竟,使用JSR-330的目的是将代码编写为接口,并不是为了避免使用JVM以避免耦合。
解决这种类型的类的测试问题的常见方法是为您喜欢的任何容器设置一个测试上下文,并在该上下文中运行单元测试。如果使用Spring,您可以在src/test/
目录(或等效目录)中放置一个applicationContext-test.xml
文件或TestConfig
类;如果使用Guice,您可以编写一个模块来连接模拟对象或测试数据集。
问题的出现原因是因为许多框架依赖于对私有/受保护字段的访问。Hibernate、JPA、几个JSR-330的实现,包括Spring本身,都是如此。
Spring的spring-test包提供了一个ReflectionTestUtils类,其中包含用于访问这些字段的静态方法。
使用这个类,可以这样测试问题中的类:
import static org.springframework.test.util.ReflectionTestUtils.*; ... public void testUsingSpringReflectionTestUtils() { Foo foo = new Foo(); setField(foo, "field", "Bar"); assertEquals("Bar", foo.getField()); }
为了使其工作,您需要在测试类路径中添加spring-test和spring-core,但这并不会为您的生产代码增加对Spring的依赖。
关于相同原理的其他实现的替代方法,欢迎提供意见。然而,我认为没有必要自己实现,因为Spring已经有一个很好的实现。