使用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:

<?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>

输出:(我为丢失的空间道歉)

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("anananan", r.generatePoem());
    }
}

以这种方式实例化测试对象是否正确?

内部静态类配置:在测试Spring组件时,我们通常使用@RunWith(SpringJUnit4ClassRunner.class),并将类设为@ContextConfiguration。通过创建类@ContextConfiguration,您可以创建一个用于配置的内部静态类,并且在其中您可以完全控制。在那里,您可以将所有需要的内容定义为测试中的bean和@Autowired it,以及依赖项,这些依赖项可以是mock或常规对象,具体取决于测试用例。

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

混合方法:当你有很多豆子需要像生产一样生产,但有一两个需要模拟时,这是常见的情况。然后,您可以@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("anananan", r.generatePoem());
    }
}

通常,当您想要创建单元测试时,您需要记住:

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

    @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 进行模拟

如果您使用

@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("anananan", room.generatePoem());
   }
}

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

您可以在Junit测试用例中使用相同的配置文件,即Beans.xml文件来创建bean。

Spring提供了@RunWith@ContextConfiguration来执行上述任务。查看此处了解更多详细解释。

在我的操作中,Dependency Injectio应该使您的代码比传统的Java EE开发更少地依赖容器。

组成应用程序的POJO应该在JUnit或TestNG测试中是可测试的,对象只需使用新操作符实例化,而不需要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("anananan", room.generatePoem());
   }
}

第一个答案

您应该使用Spring测试运行器运行测试,使用特定于测试的上下文

@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来实例化生产对象,并在测试时直接实例化对象。答案只有一个,是的,你错了,因为这与Spring根本无关。

当您对集成与单元测试进行推理时,您的困境才是有效的。特别是,如果您定义单元测试是测试一个单独的组件,而其他所有东西(包括对其他bean的依赖关系)都会被嘲笑或存根化。因此,如果您的意图是根据这个定义编写单元测试,那么您的代码是完全可以的,即使是通过直接实例化对象,也没有任何框架能够自动注入其依赖项。根据这个定义,春季测试是集成测试,这就是@Koitoer在回答中提到的,当他说你会调用你真正的bean,这不会是单元测试,它更像是集成测试

在实践中,人们通常并不关心这种区别。Spring将其测试称为单元测试。常见的情况是@Nenad Bozic所说的混合方法,在这种方法中,你只想模拟几个对象,例如与数据库的连接等,根据你的一些评论,这就是你需要的。

相关内容

  • 没有找到相关文章

最新更新