使用Spring时实例化对象,用于测试与生产

我是否正确理解在使用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中:

          

产出:(对遗失的空间表示歉意)

 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组件时,我们通常使用@RunWith(SpringJUnit4ClassRunner.class)并创建类@ContextConfiguration 。 通过创建类@ContextConfiguration您可以为配置创建内部静态类,并在其中完全控制。 在那里,你可以定义所有你需要的bean和@Autowired它在你的测试中,以及可以是模拟或常规对象的依赖项,具体取决于测试用例。

组件扫描生产代码:如果测试需要更多组件,您可以添加@ComponentScan但我们尝试只扫描它需要的软件包(这是在您使用@Component注释时,但在您的情况下,您可以将XML添加到@ContextConfiguration ) 。 当你不需要模拟并且你有一个复杂的设置需要生产时,这是一个很好的选择。 这对于集成测试很有用,在集成测试中,您要测试组件在要测试的function片中如何相互交互。

混合方法:这是通常的情况,当你有许多豆需要生产,但一两个需要嘲笑。 然后你可以@ComponentScan生成代码,但添加一个内部静态类,它是@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()); } } 

通常,当您想要创建unit testing时,您需要记住:

  1. 你需要测试真实对象的代码,这意味着你想要unit testing的类需要是一个真实的实例,使用new运算符是不理想的,因为你可能在对象中有一些依赖,并且使用构造函数并不总是更好的方式。 但你可以使用这样的东西。

     @Before public void init(){ room = new Room(Mockito.mock(BottleCounter.class)); //If you have a constructor that receive the dependencies } 
  2. 需要模拟其他对象(也称为依赖项)的所有成员变量,任何has-a关系都需要用Mock对象替换,并且所有对这个模拟对象的方法的调用都应该使用Mockito.when进行Mockito.when

如果你使用

 @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:spring-config.xml") 

您将调用您的真正的bean并且不会进行unit testing,它将更像集成测试。 在您从我的观点中写入问题的示例中,测试应该按以下方式进行:

 @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()); } } 

我想,这不是在Spring中测试Junit的正确方法,因为您在RoomTest.java中使用new关键字创建Room对象。

您可以使用相同的配置文件(即Beans.xml文件)在Junit测试用例期间创建bean。

Spring提供@RunWith@ContextConfiguration来执行上述任务。 点击此处查看详细说明。

在我看来, Dependency Injectio应该使您的代码更少依赖于容器,而不是传统的Java EE开发。

构成应用程序的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测试运行器,使用测试特定的上下文来运行测试

 @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:test-context.xml") 

让Spring实例化你的bean,但是定制你的测试特定的上下文,以便它排除你在测试中不需要的所有bean,或者模拟你不想测试的东西(例如你的BottleCounter )但是可以不排除

            

而另一个注意事项,在生产中,你最有可能最终得到带有注释的bean,这些bean在春季被基于扫描注释类的类路径而被拾取,而不是在xml中声明它们。 在这个设置中,您仍然可以借助context:exclude-filter来模拟您的bean context:exclude-filter ,类似于

        

更多关于你的困境

在我看来,你已经设置了困境的上下文。 当你说我理解在使用Spring时,你应该使用Spring配置xml来实例化你的对象以进行生产,并在测试时直接实例化对象 。 只有一个答案,是的,你错了,因为这根本不和Spring有关。

您的困境有效的背景是当您推断集成与unit testing时。 特别是,如果您定义unit testing正在测试一个单独的组件,其他所有内容(包括对其他bean的依赖关系)被模拟或删除。 因此,如果您的目的是根据此定义编写unit testing,那么您的代码是完全正常的,甚至可以通过直接实例化对象,没有框架能够自动注入其依赖项。 根据这个定义,弹簧测试是集成测试,这就是@Koitoer在他的回答中提到的,当他说你将称之为真正的bean而不是unit testing时,它更像是集成测试

在实践中,人们通常不关心这种区别。 Spring将其测试作为unit testing。 常见的情况是@Nenad Bozic称之为混合approch,你不想模拟一些对象,例如连接到数据库等,并根据你的一些评论,这就是你需要的。