junit测试参考官方文档:

https://docs.spring.io/spring-boot/docs/2.0.4.RELEASE/reference/html/boot-features-testing.html

1. 对springboot框架的项目进行测试,需要引入测试包

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

然后在Test类上,创建springboot的引入

@SpringBootTest
@RunWith(SpringRunner.class)

在新版本中用junit4:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes=MySpringApplication.class)

如果是web项目,对controller进行测试,则要引入webServletContext,不然无法获取request和response

@WebAppConfiguration

因Test类是独立运行,要对多个类进行测试,可能会建立多个类的测试类,这时就可以将统一类注解放在一个共通类。让使用类继承即可。(注意不能用接口)

常用测试配置:

  • @Ignore  (忽略某项测试)

  • @FixMethodOrder(MethodSorters.NAME_ASCENDING)   注意:要定义在当前类上,不能定义到启动父类上,在junit5版本用TestMethodOrder代替

  • @TestConfiguration (仅用于测试的configuration)

  • @MockBean(用于容器启动,将某个类作为随机结果返回)

  • Mockito
  • @Mock & @InjectMocks (对于非容器启动类的测试)
  • @Spy
  • @ActiveProfiles (用于定义要使用的application文件)
  • @WebMvcTest(****Controller.class)   (用于单纯测试Controller类)

  • @MybatisTest  (mapper接口测试)

  • Suit(集合测试,用于将多个test用例合为一体测试)

  • @Rule
  • TestCase 和 TestSuit (注解的原身)

  • @Parameterized.Parameters

     

@TestConfiguration

当我们需要引入配置来测试某个类时,就可以创建一个测试用的TestConfiguration,当然为了不和真正的使用有关联,我一般建立在test下,这样就不会有瓜葛。

为了测试MyHttpRequestMvcInterceptor,创建一个测试配置类:

@TestConfiguration
public class MvcInterceptorAssistance extends WebMvcConfigurationSupport {

    /**
     * 在WebMvc配置中设立RequestCounter
     * @param registry
     */
    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MyHttpRequestMvcInterceptor());
        super.addInterceptors(registry);
    }
}

假设这是我的TestController:

@RestController
public class TestController {

    @GetMapping("/test")
    public String test(){
        return "testResultOk";
    }
}

配置结构:

@RunWith(SpringJUnit4ClassRunner.class)
@Import(MvcInterceptorAssistance.class)
@AutoConfigureMockMvc
@SpringBootTest(classes = SpringMainTest.class,webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT,properties = {"grpc.port=11809"}) public class SpringBootBaseTest {

测试Test1-testRestTemplate:


    @Autowired
    private TestRestTemplate testRestTemplate;

    @LocalServerPort
    private int port;

    @Test
    public void test2() throws Exception {
         testRestTemplate.getForEntity("http://localhost:"+port + "/test",String.class);
    }

测试Test2 - MockMvc:

  @Autowired
    private MockMvc mvc;


    @Test
    public void test() throws Exception {
        
mvc.perform(MockMvcRequestBuilders.get("/test").accept(MediaType.APPLICATION_JSON))
                .andExpect(MockMvcResultMatchers.status().isOk());

    ※ 对于Post的json格式,需要注意填写accept,还需要填写contentType,否则会出现类型不匹配。

 

 

@MockBean:

mockbean用于在@springboottest中将指定的类,不作为IOC托管中的bean来使用。即比如我们要测试@Controller的类,controller调用了多个service,其中一个service可能牵扯调用外部或者DB,那么我们就可以在测试中

将此类指定为@MockBean.

@RunWith(SpringRunner.class)
@SpringBootTest
public class MyControllerTest{
@MockBean
private TestService testService;

@Autowired
private MyController myController;

@Test
public void getUser(){
   Mockito.when(testService.remoteGet(Mockito.any(User.class))).thenReturn(user);
   User user = myController.getUser(1234);
}


    ※ @MockBean 用于IOC bean模式,这样@MockBean标记的对象,会抢占原来的bean在IOC中的位置。

 

@Mock & @InjectMocks

当我们有一个spring项目,但是我们又不想通过容器启动(@SpringBootTest),这时候就可以使用单独对bean测试的方式。

测试类使用@InjectMocks , 因测试的类内部会有@Autowired关联的类,关联类要使用@Mock。

@Mock的对象默认是不具备任何方法能力的。也就是类似于空对象,需要在test使用时来指定。

 

@RunWith(SpringRunner.class)
@ActiveProfiles("test")
public class AccountServiceTest {

    @InjectMocks
    private AccountServiceImpl accountService;

    @Mock
    private AccountDao accountDao;


    /**
     * service使用Mock模式调用
     */
    @Test
    public void testService(){
        AccountDto accountDto = new AccountDto();
        accountDto.setId("1234");
        accountDto.setName("test1234");
        Mockito.when(userDao.insert(Mockito.any(Account.class))).thenReturn(1);
        accountService.insert(accountDto);
    }

※ @InjectMocks 要使用具体实现类,不能使用接口定义。可用于spring不启动时的测试。

@Spy

spy的作用就是可以将我们new的对象传入到@MockInject的属性变量中。使用在代替@Bean创建的对象

例如:UserService内部会使用@Autowired RestTemplate restTemplate;这个时候就可以使用@Spy RestTemplate restTemplate = new RestTemplate();来代替。

 

Mockito:

      VOID方法:

Mockito.doNothing().when(userService).addUser(Mockito.any(UserDto.class));

    有返回值测试:

Mockito.when(userDao.insert(Mockito.any(Account.class))).thenReturn(1);
Mock的内容还可判断当前Inject传入的内容是否满足预期
Mockito.doNothing().when(myRedis).hset(Mockito.eq("USER"), Mockito.eq("admin123"), Mockito.eq(JSONObject.toJSONString(userInfo))).thenReturn(true);

 因没有otherwise和反向匹配,当对预期值判断时可使用(双组合):

        Mockito.when(myRedis.hdel(Mockito.anyString(), Mockito.anyString())).thenThrow(ValueMatcherException.class);
        Mockito.when(myRedis.hdel(Mockito.eq("USER"), Mockito.eq("admin123"))).thenReturn(1L);

或者实现Answer接口,自己处理应该的返回值。

class  MyAnswer implements Answer<java.lang.Long> {

    private UserDto userDto;

    public MyAnswer(UserDto userDto){
        this.userDto = userDto;
    }

    @Override
    public java.lang.Long answer(InvocationOnMock invocation) throws Throwable {
        if (StringUtils.equals(invocation.getArgument(0), "User")
            && StringUtils.equals(invocation.getArgument(1), userDto.getUserCode())){
            return 1L;
        }
        throw new IllegalArgumentException();
    }
}

 Mockito.verify 仅测试某个mockbean是否有被执行,执行的次数,并不能判断内部的值。 

@ActiveProfiles (用于定义要使用的application文件)

 当我们属性文件定义 application-test.properties 内部定义了测试要使用的配置信息,那么我们就可以使用@ActiveProfiles("test")类似于 spring.active.profiles=test.

 

@WebMvcTest(****Controller.class)   

用于单纯测试Controller类,通过@MockMvc 测试,其它的用@MockBean标记。

※注意:其实WebMvcTest还是会去查找应用中标注@SpringBootApplication的类,也就是如果应用的启动类中标记了ScanMapper,依然会加载mapperbean的相关对象,尽管当前的测试类跟

这个没有半毛钱关系。因此,需要在测试类内部新创建一个@SpringBootApplication的类,在这个类中取消mapperScan,这样就可以了。

@RunWith(SpringRunner.class)
//使用@WebMvcTest只实例化Web层,而不是整个上下文。在具有多个Controller的应用程序中,
@WebMvcTest(TestController.class)
public class TestControllerTest {

@SpringBootApplication
@TestConfiguration
public static classTestControllerTest Config{
}
@Autowired private MockMvc mockMvc; @MockBean private MyService myService; @Test public void testUser() throws Exception { Mockito.when(myService.getUser("123")).thenReturn(null); this.mockMvc.perform(post("/user")) .contentType("application/json")) .andDo(print()) .andExpect(status().isOk()) .andExpect(null)); } }

 但是如果遇到统一配置 WebMvcConfigurer 拦截器内部依赖的bean,则会出现bean实例错误。需要让ctroller绕开。

@RunWith(SpringRunner.class)
@WebMvcTest(MyController.class)
public class MyControllerTest {

    @Autowired
    private MyController myController;

    private MockMvc mockMvc;

    /** 通过覆盖 **/
    @Before
    public void setup() {
        mockMvc = MockMvcBuilders.standaloneSetup(myController).build();
    }

    /**
     *  拦截器内部的bean
     */
    @MockBean
    private MyTestBean myTestBean;

    // ...
}

 


@MybatisTest:

当我们想要测试Mapper层。就可以引入mybatis的test。

 <dependency> 
     <groupId>org.mybatis.spring.boot</groupId> 
     <artifactId>mybatis-spring-boot-starter-test</artifactId> 
     <version>1.3.2</version> 
     <scope>test</scope> 
 </dependency>
@RunWith(SpringRunner.class)
@MybatisTest
//添加以下设置,使用真实数据库
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
// 默认会回滚,可以通过以下设置调整不回滚
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public class UserMapperTest {
    @Autowired
    private UserMapper userMapper;

 

Suit集合测试:

@RunWith(Suite.class)
@Suite.SuiteClasses({AAATest.class,BBBTest.class})
public class suitTest{}

因集合测试不同于单个测试会根据加载的class顺序执行测试用例,且不会中途退出应用启动,因此要注意测试用例之间不能对某个公有变量值有操作。

 不同的测试内容分属不同的集合,较好。

TestCase 和 TestSuit (注解的原身)

我们使用中的@Test,其实也可以是继承TestCase类,方法public,void 以test开头。

public class Test1 extends TestCase {

    public void testApplication() {
        ****
    }
}

@Suit用的是继承TestSuit类。内部方法名为suit,static,public返回Test。

public class SuitTest2 extends TestSuite {

    public static Test suit(){
        TestSuite suite = new TestSuite();
       // suite.addTestSuite(TestSuit3.class); //可添加子suit
        Test testApplication = TestSuite.createTest(Test2.class, "test1"); //添加Test
        suite.addTest(testApplication);
        return suite;
    }
}


/**
 * 测试Main类的方法
*/
 public static void main(String[] args) {
        TestResult result = new TestResult();
        SuitTest2.suit().run(result);
        result.errorCount();
    }
  • @Rule

     用于设定某项规则,然后处理test。这里rule规则只是给我们预留了对应的处理接口,具体的实现逻辑需要自行代码处理。

       

            MethodRule:

                      方法执行Rule。

                      比如,某个Test要多次运行,参数又不一致,如果写到一个Test中,看着很凌乱,且代码量大大增加。这里我们可以自己写个类实现MethodRule接口,处理对应的逻辑代码。

                   这里写了个 ParamsMethodRule

/**
 * Description:参数定义Rule
 *
 * @author Denny.Zhao
 * @since 2021/11/22
 */
public class ParamsMethodRule implements MethodRule {
    private final String testName;
    private final String[] strArray;
    public String params;

    public ParamsMethodRule(String[] strArray, String testName) {
        this.strArray = strArray;
        this.testName = testName;
    }

    public String getParams() {
        return params;
    }

    @Override
    public Statement apply(Statement base, FrameworkMethod method, Object target) {
        return new Statement() {
            @Override
            public void evaluate() throws Throwable {
                if (method.getName().equals(testName)){
                    int i=0;
                    do{
                        params = strArray[i];
                        base.evaluate();
                    }while (++i < strArray.length);
                }else{
                       base.evaluate();
                }

            }
        };
    }
}
View Code

 

 

使用的地方引入Rule规则:

@Rule
    public ParamsMethodRule rule = new ParamsMethodRule(new String[]{
            "httpclient.springclient.stats.pending",
            "httpclient.springclient.stats.available",
            "httpclient.springclient.stats.leased",
            "httpclient.springclient.stats.total",
            "httpclient.springclient.stats.max"
        }, "httpClientSpringClientTest");

 

              在@Test方法中通过以下的方式可以获取参数;     

String param = rule.getParams();

   

对于重复性试验,可使用springTest中的@Repeat,需要添加以下Rule。

@Rule
public final SpringMethodRule springMethodRule = new SpringMethodRule();
  • @Parameterized.Parameters

          用于参数化入参项,重复跑数据用。

1. 定义参数内容

    @Parameterized.Parameters
    public static Collection getName(){
        return Lists.list("DescribeUser",

2. 构造化类

@RunWith(Parameterized.class)
@Slf4j
public class MyTest {

    private String userName;


    public SkiffAuthMgrControllerTest(String userName){
        this.userName= userName;
    }

这样即可重复跑一个case数据了。

 

 

 

 

 

 

 

 

※注意:

JMX关闭:

       由于junit默认是关闭对jmx的访问的,因此如果项目中有@Autowired MBeanServer,会获取为空,如果要打开,则参见:

具体见官方文档:https://docs.spring.io/spring-boot/docs/2.0.4.RELEASE/reference/html/boot-features-testing.html  43.3.6 Using JMX部分

@RunWith(SpringRunner.class)
@SpringBootTest(properties = "spring.jmx.enabled=true")
@DirtiesContext
public class SampleJmxTests {

    @Autowired
    private MBeanServer mBeanServer;

    @Test
    public void exampleTest() {
        // ...
    }

}

 

posted on 2024-05-10 13:40  zhaoqiang1980  阅读(37)  评论(0编辑  收藏  举报