使用 Spring 时实例化对象,用于测试与生产
Instantiating objects when using Spring, for testing vs production
在使用Spring时,您应该使用Spring配置xml来实例化您的对象进行生产,而在测试时直接实例化对象的理解是否正确?
例如。
MyMain.java
package org.world.hello;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyMain {
private Room room;
public static void speak(String str)
{
System.out.println(str);
}
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
Room room = (Room) context.getBean("myRoom");
speak(room.generatePoem());
}
}
Room.java
package org.world.hello;
public class Room {
private BottleCounter bottleCounter;
private int numBottles;
public String generatePoem()
{
String str = "";
for (int i = numBottles; i>=0; i--)
{
str = str + bottleCounter.countBottle(i) + "\n";
}
return str;
}
public BottleCounter getBottleCounter() {
return bottleCounter;
}
public void setBottleCounter(BottleCounter bottleCounter) {
this.bottleCounter = bottleCounter;
}
public int getNumBottles() {
return numBottles;
}
public void setNumBottles(int numBottles) {
this.numBottles = numBottles;
}
}
BottleCounter.java
package org.world.hello;
public class BottleCounter {
public String countBottle(int i)
{
return i + " bottles of beer on the wall" + i + " bottles of beer!";
}
}
Beans.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<bean id="myRoom" class="org.world.hello.Room">
<property name="bottleCounter">
<bean id = "myBottleCounter" class = "org.world.hello.BottleCounter"/>
</property>
<property name = "numBottles" value = "10"></property>
</bean>
</beans>
输出:(我为丢失的内容道歉 space)
10 bottles of beer on the wall10 bottles of beer!
9 bottles of beer on the wall9 bottles of beer!
8 bottles of beer on the wall8 bottles of beer!
7 bottles of beer on the wall7 bottles of beer!
6 bottles of beer on the wall6 bottles of beer!
5 bottles of beer on the wall5 bottles of beer!
4 bottles of beer on the wall4 bottles of beer!
3 bottles of beer on the wall3 bottles of beer!
2 bottles of beer on the wall2 bottles of beer!
1 bottles of beer on the wall1 bottles of beer!
0 bottles of beer on the wall0 bottles of beer!
现在进行测试:
BottleCounterTest.java:
package org.world.hello;
import static org.junit.Assert.*;
import org.junit.Test;
public class BottleCounterTest {
@Test
public void testOneBottle() {
BottleCounter b = new BottleCounter();
assertEquals("1 bottles of beer on the wall1 bottles of beer!", b.countBottle(1));
}
}
非常直接。
RoomTest.java:
package org.world.hello;
import static org.junit.Assert.*;
import org.mockito.Mockito;
import org.junit.Test;
public class RoomTest {
@Test
public void testThreeBottlesAreSeperatedByNewLines()
{
Room r = new Room();
BottleCounter b = Mockito.mock(BottleCounter.class);
Mockito.when(b.countBottle(Mockito.anyInt())).thenReturn("a");
r.setBottleCounter(b);
r.setNumBottles(3);
assertEquals("a\na\na\na\n", r.generatePoem());
}
}
我以这种方式实例化我的测试对象是否正确?
我想,这不是在 Spring 中测试 Junit 的正确方法,因为您在 RoomTest.java 中使用 new 关键字创建 Room 对象。
您可以使用相同的配置文件,即 Beans.xml 文件在 Junit 测试用例期间创建 bean。
Spring 提供 @RunWith
和 @ContextConfiguration
来执行上述任务。检查 here 以获得更多详细说明。
一般来说,当你想要创建单元测试时,你需要记住:
您需要测试真实对象的代码,这意味着您要进行单元测试的class需要是一个真实的实例,使用new运算符并不理想您可能在对象中有一些依赖关系,使用构造函数并不总是更好的方法。但是你可以使用这样的东西。
@Before
public void init(){
room = new Room(Mockito.mock(BottleCounter.class)); //If you have a constructor that receive the dependencies
}
所有其他对象的成员变量(也称为依赖项)都需要被模拟,任何具有关系的关系都需要用 Mock 对象替换,并且所有对方法的调用这个模拟对象也应该使用 Mockito.when
来模拟
如果你使用
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring-config.xml")
您将调用真正的 bean,这不会是单元测试,它更像是集成测试。从我的角度来看,在您在问题中写的示例中,测试应按以下方式进行:
@RunWith(MockitoJUnitRunner.class)
public class RoomTest {
@InjectMocks
public Room room; //This will instantiate the real object for you
//So you wont need new operator anymore.
@Mock //You wont need this in your class example
private AnyDependecyClass anyDependency;
@Test
public void testThreeBottlesAreSeperatedByNewLines(){
BottleCounter b = Mockito.mock(BottleCounter.class);
Mockito.when(b.countBottle(Mockito.anyInt())).thenReturn("a");
room.setBottleCounter(b);
room.setNumBottles(3);
assertEquals("a\na\na\na\n", room.generatePoem());
}
}
内静态class配置:
在测试 Spring 组件时,我们通常使用 @RunWith(SpringJUnit4ClassRunner.class)
并使我们的 class @ContextConfiguration
。通过使 class @ContextConfiguration
您可以为配置创建一个内部静态 class 并且您可以在其中完全控制。在那里,您将所有需要的东西定义为 beans 并在测试中 @Autowired
它,以及可以是模拟或常规对象的依赖项,具体取决于测试用例。
组件扫描生产代码:
如果测试需要更多组件,您可以添加 @ComponentScan
但我们尝试让它只扫描它需要的包(这是当您使用 @Component
注释但在您的情况下您可以添加 XML 到 @ContextConfiguration
)。当您不需要模拟并且您有一个需要像生产一样的复杂设置时,这是一个不错的选择。这对于集成测试很有用,您可以在其中测试组件如何在您要测试的功能片中相互交互。
混合方法: 当您有许多需要像生产一样但一两个需要模拟的 bean 时,这是通常的情况。然后你可以 @ComponentScan
生产代码但是添加一个内部静态 class 即 @Configuration
并且在那里定义带有注释 @Primary
的bean这将覆盖该bean的生产代码配置以防万一测试。这很好,因为您不需要为所有已定义的 bean 编写长 @Configuration
,您扫描需要的内容并覆盖应该模拟的内容。
在你的情况下,我会采用这样的第一种方法:
package org.world.hello;
import static org.junit.Assert.*;
import org.mockito.Mockito;
import org.junit.Test;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class RoomTest {
@Configuration
//@ImportResource(value = {"path/to/resource.xml"}) if you need to load additional xml configuration
static class TestConfig {
@Bean
public BottleCounter bottleCounter() {
return Mockito.mock(BottleCounter.class);
}
@Bean
public Room room(BottleCounter bottleCounter) {
Room room = new Room();
room.setBottleCounter(bottleCounter);
//r.setNumBottles(3); if you need 3 in each test
return room;
}
}
@Autowired
private Room room; //room defined in configuration with mocked bottlecounter
@Test
public void testThreeBottlesAreSeperatedByNewLines()
{
Mockito.when(b.countBottle(Mockito.anyInt())).thenReturn("a");
r.setNumBottles(3);
assertEquals("a\na\na\na\n", r.generatePoem());
}
}
在我看来,与传统的 Java EE 开发相比,Dependency Injectio
应该使您的代码对容器的依赖更少。
构成您的应用程序的 POJO 应该可以在 JUnit 或 TestNG 测试中进行测试,对象只需使用 new 运算符实例化,无需 Spring 或任何其他容器。
例如:
import static org.mockito.Mockito.*;
@RunWith(MockitoJUnitRunner.class)
public class RoomTest {
@Rule
public MockitoRule rule = MockitoJUnit.rule();
@Mock //You wont need this in your class example
private BottleCounter nameOfBottleCounterAttributeInsideRoom;
@InjectMocks
public Room room;
@Test
public void testThreeBottlesAreSeperatedByNewLines(){
when(b.countBottle(anyInt())).thenReturn("a");
room.setBottleCounter(b);
room.setNumBottles(3);
assertEquals("a\na\na\na\n", room.generatePoem());
}
}
先回答
您应该 运行 您的测试使用 Spring 测试 运行ner,使用测试特定上下文
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:test-context.xml")
让 Spring 实例化您的 bean,但定制您的测试特定上下文,以便它排除测试中不需要的所有 bean,或者模拟掉您不需要的东西测试(例如你的BottleCounter
)但不能排除
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<!--Mock BottleCounter -->
<bean id="myBottleCounter" name="myBottleCounter" class="org.mockito.Mockito" factory-method="mock">
<constructor-arg value="org.world.hello.BottleCounter"/>
</bean>
<bean id="myRoom" class="org.world.hello.Room">
<property name="bottleCounter" ref="myBottleCounter"></property>
<property name = "numBottles" value = "10"></property>
</bean>
</beans>
和另一个注意事项,在生产中,您很可能最终会得到带注释的 bean,这些 bean 被 spring 基于扫描类路径中带注释的 类 而不是声明它们全部在 xml。在此设置中,您仍然可以在 context:exclude-filter
的帮助下模拟您的 bean,例如
<!--Mock BottleCounter -->
<bean id="myBottleCounter" name="myBottleCounter" class="org.mockito.Mockito" factory-method="mock">
<constructor-arg value="org.world.hello.BottleCounter"/>
</bean>
<context:component-scan base-package="org.world.hello">
<context:exclude-filter type="regex" expression="org\.world\.hello\.Bottle*"/>
</context:component-scan>
更多关于你的困境
在我看来,您为困境设置的上下文是错误的。当你说 时,我的理解是否正确,当使用 Spring 时,你应该使用 Spring 配置 xml 来实例化你的对象以进行生产,并且testing时直接实例化对象。只能有一个答案,是的,你错了,因为这和Spring根本没有关系。
当您推理集成与单元测试时,您的困境是有效的。特别是,如果您定义单元测试正在测试单个组件,而其他所有内容(包括对其他 bean 的依赖项)都被模拟或存根。因此,如果您的意图是根据此定义编写单元测试,那么您的代码是完全可以的,即使是直接实例化对象的理想原因,任何框架都无法自动注入其依赖项。根据这个定义,spring 测试是集成测试,这就是 @Koitoer 在他的回答中提到的,他说 你会调用你真正的 beans,那不会是单元测试,它会更多比如集成测试
实际上,人们通常不关心区别。 Spring 将其测试称为单元测试。常见的情况是@Nenad Bozic 所说的混合方法,您希望只模拟几个对象,例如连接到数据库等,根据您的一些评论,这就是您所需要的。
在使用Spring时,您应该使用Spring配置xml来实例化您的对象进行生产,而在测试时直接实例化对象的理解是否正确?
例如。
MyMain.java
package org.world.hello;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyMain {
private Room room;
public static void speak(String str)
{
System.out.println(str);
}
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
Room room = (Room) context.getBean("myRoom");
speak(room.generatePoem());
}
}
Room.java
package org.world.hello;
public class Room {
private BottleCounter bottleCounter;
private int numBottles;
public String generatePoem()
{
String str = "";
for (int i = numBottles; i>=0; i--)
{
str = str + bottleCounter.countBottle(i) + "\n";
}
return str;
}
public BottleCounter getBottleCounter() {
return bottleCounter;
}
public void setBottleCounter(BottleCounter bottleCounter) {
this.bottleCounter = bottleCounter;
}
public int getNumBottles() {
return numBottles;
}
public void setNumBottles(int numBottles) {
this.numBottles = numBottles;
}
}
BottleCounter.java
package org.world.hello;
public class BottleCounter {
public String countBottle(int i)
{
return i + " bottles of beer on the wall" + i + " bottles of beer!";
}
}
Beans.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<bean id="myRoom" class="org.world.hello.Room">
<property name="bottleCounter">
<bean id = "myBottleCounter" class = "org.world.hello.BottleCounter"/>
</property>
<property name = "numBottles" value = "10"></property>
</bean>
</beans>
输出:(我为丢失的内容道歉 space)
10 bottles of beer on the wall10 bottles of beer!
9 bottles of beer on the wall9 bottles of beer!
8 bottles of beer on the wall8 bottles of beer!
7 bottles of beer on the wall7 bottles of beer!
6 bottles of beer on the wall6 bottles of beer!
5 bottles of beer on the wall5 bottles of beer!
4 bottles of beer on the wall4 bottles of beer!
3 bottles of beer on the wall3 bottles of beer!
2 bottles of beer on the wall2 bottles of beer!
1 bottles of beer on the wall1 bottles of beer!
0 bottles of beer on the wall0 bottles of beer!
现在进行测试:
BottleCounterTest.java:
package org.world.hello;
import static org.junit.Assert.*;
import org.junit.Test;
public class BottleCounterTest {
@Test
public void testOneBottle() {
BottleCounter b = new BottleCounter();
assertEquals("1 bottles of beer on the wall1 bottles of beer!", b.countBottle(1));
}
}
非常直接。
RoomTest.java:
package org.world.hello;
import static org.junit.Assert.*;
import org.mockito.Mockito;
import org.junit.Test;
public class RoomTest {
@Test
public void testThreeBottlesAreSeperatedByNewLines()
{
Room r = new Room();
BottleCounter b = Mockito.mock(BottleCounter.class);
Mockito.when(b.countBottle(Mockito.anyInt())).thenReturn("a");
r.setBottleCounter(b);
r.setNumBottles(3);
assertEquals("a\na\na\na\n", r.generatePoem());
}
}
我以这种方式实例化我的测试对象是否正确?
我想,这不是在 Spring 中测试 Junit 的正确方法,因为您在 RoomTest.java 中使用 new 关键字创建 Room 对象。
您可以使用相同的配置文件,即 Beans.xml 文件在 Junit 测试用例期间创建 bean。
Spring 提供 @RunWith
和 @ContextConfiguration
来执行上述任务。检查 here 以获得更多详细说明。
一般来说,当你想要创建单元测试时,你需要记住:
您需要测试真实对象的代码,这意味着您要进行单元测试的class需要是一个真实的实例,使用new运算符并不理想您可能在对象中有一些依赖关系,使用构造函数并不总是更好的方法。但是你可以使用这样的东西。
@Before public void init(){ room = new Room(Mockito.mock(BottleCounter.class)); //If you have a constructor that receive the dependencies }
所有其他对象的成员变量(也称为依赖项)都需要被模拟,任何具有关系的关系都需要用 Mock 对象替换,并且所有对方法的调用这个模拟对象也应该使用
Mockito.when
来模拟
如果你使用
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring-config.xml")
您将调用真正的 bean,这不会是单元测试,它更像是集成测试。从我的角度来看,在您在问题中写的示例中,测试应按以下方式进行:
@RunWith(MockitoJUnitRunner.class)
public class RoomTest {
@InjectMocks
public Room room; //This will instantiate the real object for you
//So you wont need new operator anymore.
@Mock //You wont need this in your class example
private AnyDependecyClass anyDependency;
@Test
public void testThreeBottlesAreSeperatedByNewLines(){
BottleCounter b = Mockito.mock(BottleCounter.class);
Mockito.when(b.countBottle(Mockito.anyInt())).thenReturn("a");
room.setBottleCounter(b);
room.setNumBottles(3);
assertEquals("a\na\na\na\n", room.generatePoem());
}
}
内静态class配置:
在测试 Spring 组件时,我们通常使用 @RunWith(SpringJUnit4ClassRunner.class)
并使我们的 class @ContextConfiguration
。通过使 class @ContextConfiguration
您可以为配置创建一个内部静态 class 并且您可以在其中完全控制。在那里,您将所有需要的东西定义为 beans 并在测试中 @Autowired
它,以及可以是模拟或常规对象的依赖项,具体取决于测试用例。
组件扫描生产代码:
如果测试需要更多组件,您可以添加 @ComponentScan
但我们尝试让它只扫描它需要的包(这是当您使用 @Component
注释但在您的情况下您可以添加 XML 到 @ContextConfiguration
)。当您不需要模拟并且您有一个需要像生产一样的复杂设置时,这是一个不错的选择。这对于集成测试很有用,您可以在其中测试组件如何在您要测试的功能片中相互交互。
混合方法: 当您有许多需要像生产一样但一两个需要模拟的 bean 时,这是通常的情况。然后你可以 @ComponentScan
生产代码但是添加一个内部静态 class 即 @Configuration
并且在那里定义带有注释 @Primary
的bean这将覆盖该bean的生产代码配置以防万一测试。这很好,因为您不需要为所有已定义的 bean 编写长 @Configuration
,您扫描需要的内容并覆盖应该模拟的内容。
在你的情况下,我会采用这样的第一种方法:
package org.world.hello;
import static org.junit.Assert.*;
import org.mockito.Mockito;
import org.junit.Test;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class RoomTest {
@Configuration
//@ImportResource(value = {"path/to/resource.xml"}) if you need to load additional xml configuration
static class TestConfig {
@Bean
public BottleCounter bottleCounter() {
return Mockito.mock(BottleCounter.class);
}
@Bean
public Room room(BottleCounter bottleCounter) {
Room room = new Room();
room.setBottleCounter(bottleCounter);
//r.setNumBottles(3); if you need 3 in each test
return room;
}
}
@Autowired
private Room room; //room defined in configuration with mocked bottlecounter
@Test
public void testThreeBottlesAreSeperatedByNewLines()
{
Mockito.when(b.countBottle(Mockito.anyInt())).thenReturn("a");
r.setNumBottles(3);
assertEquals("a\na\na\na\n", r.generatePoem());
}
}
在我看来,与传统的 Java EE 开发相比,Dependency Injectio
应该使您的代码对容器的依赖更少。
构成您的应用程序的 POJO 应该可以在 JUnit 或 TestNG 测试中进行测试,对象只需使用 new 运算符实例化,无需 Spring 或任何其他容器。
例如:
import static org.mockito.Mockito.*;
@RunWith(MockitoJUnitRunner.class)
public class RoomTest {
@Rule
public MockitoRule rule = MockitoJUnit.rule();
@Mock //You wont need this in your class example
private BottleCounter nameOfBottleCounterAttributeInsideRoom;
@InjectMocks
public Room room;
@Test
public void testThreeBottlesAreSeperatedByNewLines(){
when(b.countBottle(anyInt())).thenReturn("a");
room.setBottleCounter(b);
room.setNumBottles(3);
assertEquals("a\na\na\na\n", room.generatePoem());
}
}
先回答
您应该 运行 您的测试使用 Spring 测试 运行ner,使用测试特定上下文
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:test-context.xml")
让 Spring 实例化您的 bean,但定制您的测试特定上下文,以便它排除测试中不需要的所有 bean,或者模拟掉您不需要的东西测试(例如你的BottleCounter
)但不能排除
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<!--Mock BottleCounter -->
<bean id="myBottleCounter" name="myBottleCounter" class="org.mockito.Mockito" factory-method="mock">
<constructor-arg value="org.world.hello.BottleCounter"/>
</bean>
<bean id="myRoom" class="org.world.hello.Room">
<property name="bottleCounter" ref="myBottleCounter"></property>
<property name = "numBottles" value = "10"></property>
</bean>
</beans>
和另一个注意事项,在生产中,您很可能最终会得到带注释的 bean,这些 bean 被 spring 基于扫描类路径中带注释的 类 而不是声明它们全部在 xml。在此设置中,您仍然可以在 context:exclude-filter
的帮助下模拟您的 bean,例如
<!--Mock BottleCounter -->
<bean id="myBottleCounter" name="myBottleCounter" class="org.mockito.Mockito" factory-method="mock">
<constructor-arg value="org.world.hello.BottleCounter"/>
</bean>
<context:component-scan base-package="org.world.hello">
<context:exclude-filter type="regex" expression="org\.world\.hello\.Bottle*"/>
</context:component-scan>
更多关于你的困境
在我看来,您为困境设置的上下文是错误的。当你说 时,我的理解是否正确,当使用 Spring 时,你应该使用 Spring 配置 xml 来实例化你的对象以进行生产,并且testing时直接实例化对象。只能有一个答案,是的,你错了,因为这和Spring根本没有关系。
当您推理集成与单元测试时,您的困境是有效的。特别是,如果您定义单元测试正在测试单个组件,而其他所有内容(包括对其他 bean 的依赖项)都被模拟或存根。因此,如果您的意图是根据此定义编写单元测试,那么您的代码是完全可以的,即使是直接实例化对象的理想原因,任何框架都无法自动注入其依赖项。根据这个定义,spring 测试是集成测试,这就是 @Koitoer 在他的回答中提到的,他说 你会调用你真正的 beans,那不会是单元测试,它会更多比如集成测试
实际上,人们通常不关心区别。 Spring 将其测试称为单元测试。常见的情况是@Nenad Bozic 所说的混合方法,您希望只模拟几个对象,例如连接到数据库等,根据您的一些评论,这就是您所需要的。