Java 使用 Spring 时实例化对象,用于测试与生产
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/29203218/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Instantiating objects when using Spring, for testing vs production
提问by dwjohnston
Am correct in understanding that when using Spring, you should use the Spring configuration xml to instantiate your objects for production, and directly instantiate objects when testing?
是否正确理解在使用 Spring 时,您应该使用 Spring 配置 xml 来实例化您的对象以进行生产,并在测试时直接实例化对象?
Eg.
例如。
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
房间.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
瓶子计数器.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:
<?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>
Outputs: (my apologies for the missing 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!
Now for testing this:
现在测试这个:
BottleCounterTest.java:
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));
}
}
Pretty straight forward.
很直接。
RoomTest.java:
房间测试.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());
}
}
Am I correct in instantiating my test objects this way?
我以这种方式实例化我的测试对象是否正确?
采纳答案by Nenad Bozic
Inner static class configuration:When testing Spring components we usually use @RunWith(SpringJUnit4ClassRunner.class)
and make our class @ContextConfiguration
. By making class @ContextConfiguration
you can create an inner static class for configuration and in it you have full control. There you define all you need as beans and @Autowired
it in your test, along with dependencies which can be mocks or regular objects, depending on test case.
内部静态类配置:在测试 Spring 组件时,我们通常使用@RunWith(SpringJUnit4ClassRunner.class)
并创建我们的类@ContextConfiguration
。通过创建类,@ContextConfiguration
您可以为配置创建一个内部静态类,并在其中拥有完全控制权。在那里你将所有你需要的定义为 bean 和@Autowired
它在你的测试中,以及可以是模拟或常规对象的依赖项,具体取决于测试用例。
Component scanning production code:If there are more components needed for test you can add @ComponentScan
but we try to make it scan only packages it needs (this is when you use @Component
annotation but in your case you can add XML to @ContextConfiguration
). This is a good choice when you do not need mocks and you have a complicated setup which needs to be production like. This is good for integration tests where you want to test how components interact with each other in functional slices you want to test.
组件扫描生产代码:如果测试需要更多组件,您可以添加,@ComponentScan
但我们尝试使其仅扫描它需要的包(这是您使用@Component
注释时,但在您的情况下,您可以将 XML 添加到@ContextConfiguration
)。当您不需要模拟并且您有一个需要像生产一样的复杂设置时,这是一个不错的选择。这对于您想要测试组件如何在您想要测试的功能切片中相互交互的集成测试很有用。
Hybrid approach:This is the usual case when you have many beans which need to be production like but one or two need to be mocks. Then you can @ComponentScan
production code but add an inner static class which is @Configuration
and there define beans with annotation @Primary
which will override production code configuration for that bean in case of tests. This is good since you do not need to write long @Configuration
with all defined beans, you scan what you need and override what should be mocked.
混合方法:当您有许多 bean 需要生产时,这是通常的情况,但一两个需要模拟。然后,您可以@ComponentScan
生产代码,但添加一个内部静态类,该类@Configuration
定义带有注释的 bean,该类@Primary
将在测试时覆盖该 bean 的生产代码配置。这很好,因为您不需要@Configuration
用所有定义的 bean编写很长的代码,您可以扫描您需要的内容并覆盖应该模拟的内容。
In your case I would go with first approach like this:
在你的情况下,我会采用这样的第一种方法:
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());
}
}
回答by Waheed
I guess, this is not the right way of testing Junit in Spring as you are creating Room object using new keyword in your RoomTest.java .
我想,这不是在 Spring 中测试 Junit 的正确方法,因为您正在 RoomTest.java 中使用 new 关键字创建 Room 对象。
You can use your same configuration file i.e Beans.xml file to create bean during Junit test cases.
您可以使用相同的配置文件,即 Beans.xml 文件在 Junit 测试用例期间创建 bean。
Spring provides @RunWith
and @ContextConfiguration
to perform above task. Check herefor more detail explaination.
Spring 提供@RunWith
并@ContextConfiguration
执行上述任务。查看此处了解更多详细说明。
回答by Koitoer
In general when you want to create the unit testing, you need to have in mind :
一般来说,当你想创建单元测试时,你需要记住:
You need to test the code for the real object, it means the class that you want to unit test need to be a real instance, it is not ideal using new operator as you probably have some dependencies in the object and using constructor is not always the better way. But you could use something like this.
@Before public void init(){ room = new Room(Mockito.mock(BottleCounter.class)); //If you have a constructor that receive the dependencies }
All the member variables that are other object (aka. as dependencies) need to be mocked, any has-a relationship need to be replace with a Mock object and all the calls to the methods of this mocked object should be mocked as well using
Mockito.when
您需要测试真实对象的代码,这意味着您要进行单元测试的类需要是一个真实的实例,使用 new 运算符并不理想,因为您可能在对象中有一些依赖项,并且并不总是使用构造函数更好的方法。但是你可以使用这样的东西。
@Before public void init(){ room = new Room(Mockito.mock(BottleCounter.class)); //If you have a constructor that receive the dependencies }
所有属于其他对象(也称为依赖项)的成员变量都需要被模拟,任何具有关系的关系都需要用模拟对象替换,并且对这个模拟对象的方法的所有调用也应该被模拟使用
Mockito.when
If you use
如果你使用
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring-config.xml")
You will call your real beans and that wont be a unit testing, it will be more like integration testing. In the example that you write in your question from my point of view the test should be done as:
您将调用真正的 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());
}
}
回答by jfcorugedo
In my oppinion Dependency Injectio
should make your code less dependent on the container than it would be with traditional Java EE development.
在我Dependency Injectio
看来,与传统的 Java EE 开发相比,应该使您的代码对容器的依赖更少。
The POJOs that make up your application should be testable in JUnit or TestNG tests, with objects simply instantiated using the new operator, without Spring or any other container.
组成应用程序的 POJO 应该可以在 JUnit 或 TestNG 测试中进行测试,对象只需使用 new 运算符实例化,无需 Spring 或任何其他容器。
For instance:
例如:
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());
}
}
回答by Master Slave
First an answer
先回答一下
You should run your test with a Spring test runner, using a test specific context
您应该使用特定于测试的上下文使用 Spring 测试运行器运行您的测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:test-context.xml")
Let Spring instantiate your bean, but tailor your test specific context so that it excludes all the beans that you don't need inside a test, or mock-away the stuff that you don't want to test (e.g. your BottleCounter
) but can't exclude
让 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>
and another note, in production, you'll most likely end up with annotated beans that are being picked up by spring based on scanning the classpath for annotated classes as oppose to declaring them all in xml. In this setup, you can still mock your beans with a help of context:exclude-filter
, something like
另一个注意事项,在生产中,您很可能最终会基于扫描带注释类的类路径而不是在 xml 中全部声明它们而得到 spring 正在拾取的带注释的 bean。在此设置中,您仍然可以借助 来模拟您的 bean context:exclude-filter
,例如
<!--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>
More about your dilemma
更多关于你的困境
In my view you have setup the context for a dilemma wrong. When you say am I correct in understanding that when using Spring, you should use the Spring configuration xml to instantiate your objects for production, and directly instantiate objects when testing. There can be only one answer, yes, you're wrong, because this is not related to Spring at all.
在我看来,您已经为困境设置了错误的上下文。当您说我的理解是否正确时,在使用 Spring 时,您应该使用 Spring 配置 xml 来实例化您的对象以进行生产,并在测试时直接实例化对象。只能有一个答案,是的,你错了,因为这与 Spring 完全没有关系。
The context where your dilemma is valid is when you reason about integration vs unit testing. In particular, if you define that unit test is testing an individual component with everything else (including dependencies to other beans) being mocked or stubbed away. So if your intent is to write the unit test according to this definiton, your code is perfectly OK, even desirable cause by instantiating the object directly, no framework will be able to automagically inject its dependencies. According to this definition spring tests are integration tests, and that is what @Koitoer mentions in his answer when he says You will call your real beans and that wont be a unit testing, it will be more like integration testing
当您对集成与单元测试进行推理时,您的困境有效的上下文是。特别是,如果您定义单元测试正在测试单个组件,而其他所有内容(包括对其他 bean 的依赖)都被模拟或存根。因此,如果您的意图是根据此定义编写单元测试,那么您的代码完全可以,即使是通过直接实例化对象的理想原因,也没有框架能够自动注入其依赖项。根据这个定义,spring 测试是集成测试,这就是@Koitoer 在他的回答中提到的,当他说你将调用真正的 bean 并且这不会是单元测试,它更像是集成测试
In practice, people are usually not concerned about the distinction. Spring refers to its test as unit tests. The common case is what @Nenad Bozic calls a hybrid approch, where you would like ot mock out just a few object, e.g. connection to a DB or the like, and based on some of your comments this is what you need.
在实践中,人们通常不关心区别。Spring 将其测试称为单元测试。常见的情况是@Nenad Bozic 所谓的混合方法,在这种情况下,您不希望模拟几个对象,例如与数据库的连接等,并且根据您的一些评论,这就是您所需要的。