spring-boot-单元测试

pom依赖

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

        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.3.1</version>
        </dependency>

快捷键

ctrl + shift + t

service层 测试


@RunWith(SpringRunner.class)
@SpringBootTest
public class LearnServiceTest {

    @Autowired
    private LearnService learnService;

    @Test
    public void getLearn() {
        LearnResource learnResource = learnService.selectByKey(1001L);
        Assert.assertThat(learnResource.getAuthor(), is("嘟嘟MD独立博客"));
    }


    /**
     * 这样测试完数据就会回滚了,不会造成垃圾数据
     * <p>
     *
     * @Transactional :单元个测试的时候如果不想造成垃圾数据,可以开启事物功能,记在方法或者类头部添加
     * <p>
     * @Rollback(false): @Rollback 表示事务执行完回滚,支持传入一个参数value,默认true 即回滚,false不回滚。
     */
    @Test
    @Transactional
//    @Rollback(false)
    public void add() {
        LearnResource bean = new LearnResource();
        bean.setAuthor("测试回滚");
        bean.setTitle("回滚用例");
        bean.setUrl("http://tengj.top");
        learnService.save(bean);
    }


}

controller 测试

@RunWith(SpringRunner.class)
@SpringBootTest
public class LearnControllerTest {

    @Autowired
    private WebApplicationContext wac;

    private MockMvc mvc;

    private MockHttpSession session;


    @Before
    public void setupMockMvc() {
        //初始化MockMvc对象
        mvc = MockMvcBuilders.webAppContextSetup(wac).build();

        //构建session
        session = new MockHttpSession();
        User user = new User("root", "root");
        //拦截器那边会判断用户是否登录,所以这里注入一个用户
        session.setAttribute("user", user);
    }

    /**
     * 新增教程测试用例
     * <p>
     * post 请求
     *
     * @throws Exception
     */
    @Test
    public void addLearn() throws Exception {
        // 手动写 json
        //String json = "{\"author\":\"HAHAHAA\",\"title\":\"Spring\",\"url\":\"http://tengj.top/\"}";

        // 前端传递的 json 格式,对象 转 json
        LearnResource learnResource = new LearnResource();
        learnResource.setAuthor("HAHAHAAs");
        learnResource.setTitle("Spring");
        learnResource.setUrl("http://tengj.top/");

        Gson gson = new Gson();
        String json = gson.toJson(learnResource);


        //mockMvc.perform执行一个请求
        //MockMvcRequestBuilders构造一个请求
        mvc.perform(MockMvcRequestBuilders.post("/learn/add")
                //发送的数据格式
                .accept(MediaType.APPLICATION_JSON_UTF8)
                //传json参数 通过 @RequestBody注解 接受的参数
                .content(json.getBytes())
                // 注入一个session
                .session(session)
        )
                //andExpect添加执行完成后的断言
                .andExpect(MockMvcResultMatchers.status().isOk())
                //andDo添加一个结果处理器,表示要对结果做点什么事情
                .andDo(MockMvcResultHandlers.print());//输出整个响应结果信息
    }

    /**
     * 获取教程测试用例
     * <p>
     * get 请求
     *
     * @throws Exception
     */
    @Test
    public void qryLearn() throws Exception {
        mvc.perform(MockMvcRequestBuilders.get("/learn/resource/1001")
                .contentType(MediaType.APPLICATION_JSON_UTF8)
                .accept(MediaType.APPLICATION_JSON_UTF8)
                .session(session)
        )
                .andExpect(MockMvcResultMatchers.status().isOk())
                //jsonPath用来获取author字段比对是否为嘟嘟MD独立博客,不是就测试不通过
                .andExpect(MockMvcResultMatchers.jsonPath("$.author").value("嘟嘟MD独立博客"))
                .andExpect(MockMvcResultMatchers.jsonPath("$.title").value("Spring Boot干货系列"))
                .andDo(MockMvcResultHandlers.print());
    }

     /**
     * get 请求
     * @RequestParam 注解接收参数 ,也可以用于无注解修饰的对象接受参数,如果字段名一样,则自动进行赋值
     * 
     * @throws Exception
     * 
     */
    @Test
    public void qryLearn() throws Exception {
        mvc.perform(MockMvcRequestBuilders.get("/learn/queryLean")
                .contentType(MediaType.APPLICATION_JSON_UTF8)
                .accept(MediaType.APPLICATION_JSON_UTF8)
                .session(session)
                .param("id", "1001")
        )
                .andExpect(MockMvcResultMatchers.status().isOk())
                //jsonPath用来获取author字段比对是否为嘟嘟MD独立博客,不是就测试不通过
                .andExpect(MockMvcResultMatchers.jsonPath("$.author").value("嘟嘟MD独立博客"))
                .andExpect(MockMvcResultMatchers.jsonPath("$.title").value("Spring Boot干货系列"))
                .andDo(MockMvcResultHandlers.print());
    }

 /**
     * get 请求
     * 对象接收参数,但无注解修饰,字段一样,则赋值
     *
     * @throws Exception
     */
    @Test
    public void qryLearn() throws Exception {
        LearnResource learnResource = new LearnResource();
        learnResource.setId(999L);
        learnResource.setAuthor("zhang");
        learnResource.setTitle("zhang");
        //learnResource.setUrl("http://www.baidu.com");

        Map<String, Object> stringObjectMap = objectToMap(learnResource);
        String requestParams = stringJoinAt(stringObjectMap);

        mvc.perform(MockMvcRequestBuilders.get("/learn/queryLean" + requestParams)
                .contentType(MediaType.APPLICATION_JSON_UTF8)
                .accept(MediaType.APPLICATION_JSON_UTF8)
                .session(session)
        )
                .andExpect(MockMvcResultMatchers.status().isOk())
                //jsonPath用来获取author字段比对是否为嘟嘟MD独立博客,不是就测试不通过
                .andExpect(MockMvcResultMatchers.jsonPath("$.author").value("官方SpriongBoot例子"))
                .andExpect(MockMvcResultMatchers.jsonPath("$.title").value("官方SpriongBoot例子"))
                .andDo(MockMvcResultHandlers.print());
    }


    /**
     * object 转 map ,字段值为 null 的,则添加
     *
     * @param obj
     * @return
     */
    public static Map<String, Object> objectToMap(Object obj) {
        Map<String, Object> map = new HashMap<>();
        Class<?> clazz = obj.getClass();
        System.out.println(clazz);
        for (Field field : clazz.getDeclaredFields()) {
            field.setAccessible(true);
            String fieldName = field.getName();
            Object value = null;
            try {
                value = field.get(obj);
                if (Objects.isNull(value)) {
                    continue;
                }
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
            map.put(fieldName, value);
        }
        return map;
    }


    /**
     * map 转为 & 符号进行拼接的 字符串
     *
     * @param map
     * @return
     */
    public static String stringJoinAt(Map<String, Object> map) {
        StringBuffer content = new StringBuffer();
        // 按照key做首字母升序排列
        List<String> keys = new ArrayList<String>(map.keySet());
        Collections.sort(keys, String.CASE_INSENSITIVE_ORDER);
        for (int i = 0; i < keys.size(); i++) {
            String key = keys.get(i).toString();
            String value = map.get(key).toString();
            // 空串不参与签名
            if (StringUtils.isBlank(value)) {
                continue;
            }
            content.append((i == 0 ? "" : "&") + key + "=" + value);

        }
        String signSrc = content.toString();
        if (signSrc.startsWith("&")) {
            signSrc = signSrc.replaceFirst("&", "");
        }

        return "?" + signSrc;
    }


    /**
     * 修改教程测试用例
     *
     * @throws Exception
     */
    @Test
    public void updateLearn() throws Exception {
        String json = "{\"author\":\"测试修改\",\"id\":1031,\"title\":\"Spring Boot干货系列\",\"url\":\"http://tengj.top/\"}";
        mvc.perform(MockMvcRequestBuilders.post("/learn/update")
                .accept(MediaType.APPLICATION_JSON_UTF8)
                .content(json.getBytes())//传json参数
                .session(session)
        )
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andDo(MockMvcResultHandlers.print());
    }

    /**
     * 删除教程测试用例
     *
     * @throws Exception
     */
    @Test
    public void deleteLearn() throws Exception {
        String json = "[1031]";
        mvc.perform(MockMvcRequestBuilders.post("/learn/delete")
                .accept(MediaType.APPLICATION_JSON_UTF8)
                .content(json.getBytes())//传json参数数组形式
                .session(session)
        )
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andDo(MockMvcResultHandlers.print());
    }

}

单元测试事物没有回滚

  • 查看MySQL当前默认的存储引擎:
mysql> show variables like '%storage_engine%';
  • user表用了什么引擎
mysql> show create table user;
  • 将user表修为InnoDB存储引擎
mysql> ALTER TABLE user ENGINE=INNODB;

以下情况,事务也不回滚
使用RANDOM_PORT或DEFINED_PORT这种安排隐式提供了一个真正的servlet环境, 在这种情况下,在服务器上启动的任何事务都不会回滚。
@SpringBootTest(classes = Application.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)

@LocalServerPort
private int port;

断言 Assert.assertThat使用

例如:CoreMatchers.equalTo

assertThat( [value], [matcher statement] );

//value 是接下来想要测试的变量值;
//matcher statement 是使用 Hamcrest 匹配符来表达的对前面变量所期望的值的声明
//如果 value 值与 matcher statement 所表达的期望值相符,则测试成功,否则测试失败。
// 想判断某个字符串 s 是否含有子字符串 "developer" 或 "Works" 中间的一个
assertThat(s, anyOf(containsString("developer"), containsString("Works")));
// 匹配符 anyOf 表示任何一个条件满足则成立,类似于逻辑或 "||", 匹配符 containsString 表示是否含有参数子 


// 联合匹配符not和equalTo表示“不等于”
assertThat( something, not( equalTo( "developer" ) ) ); 
// 联合匹配符not和containsString表示“不包含子字符串”
assertThat( something, not( containsString( "Works" ) ) ); 
// 联合匹配符anyOf和containsString表示“包含任何一个子字符串”
assertThat(something, anyOf(containsString("developer"), containsString("Works")));
 

字符相关匹配符
/**
* equalTo匹配符断言被测的testedValue等于expectedValue,
* equalTo可以断言数值之间,字符串之间和对象之间是否相等,相当于Object的equals方法
*/
assertThat(testedValue, equalTo(expectedValue));

/**equalToIgnoringCase匹配符断言被测的字符串testedString
*在忽略大小写的情况下等于expectedString
*/
assertThat(testedString, equalToIgnoringCase(expectedString));

/**equalToIgnoringWhiteSpace匹配符断言被测的字符串testedString
*在忽略头尾的任意个空格的情况下等于expectedString,
*注意:字符串中的空格不能被忽略
*/
assertThat(testedString, equalToIgnoringWhiteSpace(expectedString);

/**containsString匹配符断言被测的字符串testedString包含子字符串subString**/
assertThat(testedString, containsString(subString) );

/**endsWith匹配符断言被测的字符串testedString以子字符串suffix结尾*/
assertThat(testedString, endsWith(suffix));

/**startsWith匹配符断言被测的字符串testedString以子字符串prefix开始*/
assertThat(testedString, startsWith(prefix));

一般匹配符
/**nullValue()匹配符断言被测object的值为null*/
assertThat(object,nullValue());

/**notNullValue()匹配符断言被测object的值不为null*/
assertThat(object,notNullValue());

/**is匹配符断言被测的object等于后面给出匹配表达式*/
assertThat(testedString, is(equalTo(expectedValue)));

/**is匹配符简写应用之一,is(equalTo(x))的简写,断言testedValue等于expectedValue*/
assertThat(testedValue, is(expectedValue));

/**is匹配符简写应用之二,is(instanceOf(SomeClass.class))的简写,
*断言testedObject为Cheddar的实例
*/
assertThat(testedObject, is(Cheddar.class));

/**not匹配符和is匹配符正好相反,断言被测的object不等于后面给出的object*/
assertThat(testedString, not(expectedString));

/**allOf匹配符断言符合所有条件,相当于“与”(&&)*/
assertThat(testedNumber, allOf( greaterThan(8), lessThan(16) ) );

/**anyOf匹配符断言符合条件之一,相当于“或”(||)*/
assertThat(testedNumber, anyOf( greaterThan(16), lessThan(8) ) );

数值相关匹配符
/**closeTo匹配符断言被测的浮点型数testedDouble在20.0¡À0.5范围之内*/
assertThat(testedDouble, closeTo( 20.0, 0.5 ));

/**greaterThan匹配符断言被测的数值testedNumber大于16.0*/
assertThat(testedNumber, greaterThan(16.0));

/** lessThan匹配符断言被测的数值testedNumber小于16.0*/
assertThat(testedNumber, lessThan (16.0));

/** greaterThanOrEqualTo匹配符断言被测的数值testedNumber大于等于16.0*/
assertThat(testedNumber, greaterThanOrEqualTo (16.0));

/** lessThanOrEqualTo匹配符断言被测的testedNumber小于等于16.0*/
assertThat(testedNumber, lessThanOrEqualTo (16.0));

集合相关匹配符
/**hasEntry匹配符断言被测的Map对象mapObject含有一个键值为"key"对应元素值为"value"的Entry项*/
assertThat(mapObject, hasEntry("key", "value" ) );

/**hasItem匹配符表明被测的迭代对象iterableObject含有元素element项则测试通过*/
assertThat(iterableObject, hasItem (element));

/** hasKey匹配符断言被测的Map对象mapObject含有键值“key”*/
assertThat(mapObject, hasKey ("key"));

/** hasValue匹配符断言被测的Map对象mapObject含有元素值value*/
assertThat(mapObject, hasValue(value));

Junit基本注解介绍

@BeforeClass 在所有测试方法前执行一次,一般在其中写上整体初始化的代码

@AfterClass 在所有测试方法后执行一次,一般在其中写上销毁和释放资源的代码

@Before 在每个测试方法前执行,一般用来初始化方法(比如我们在测试别的方法时,类中与其他测试方法共享的值已经被改变,为了保证测试结果的有效性,我们会在@Before注解的方法中重置数据)

@After 在每个测试方法后执行,在方法执行完成后要做的事情

@Test(timeout = 1000) 测试方法执行超过1000毫秒后算超时,测试将失败

@Test(expected = Exception.class) 测试方法期望得到的异常类,如果方法执行没有抛出指定的异常,则测试失败

@Ignore(“not ready yet”) 执行测试时将忽略掉此方法,如果用于修饰类,则忽略整个类

@Test 编写一般测试用例

Test注解主要包含有expected属性,可指定所希望抛出的异常类型;也即如果抛出了指定的异常,则该测试被认为成功,否则为失败

@Test(expected = RuntimeException.class)
    public void testException() {
        ArrayList<Object> objects = new ArrayList<>();
        Assert.assertThat(objects, notNullValue());
        throw new RuntimeException("查询失败");
    }

打包测试

测试套件,包含有一系列的需要测试的类;
我们用一个类,把所有的测试类整理进去,然后直接运行这个类,所有的测试类都会执行

@RunWith(Suite.class)
@Suite.SuiteClasses(LearnServiceTest.class)
public class SuitsTest {
}

MockMvc

@RunWith 在JUnit中有很多个Runner,他们负责调用你的测试代码,每一个Runner都有各自的特殊功能,你要根据需要选择不同的Runner来运行你的测试代码。

assertThat一般与CoreMatchers一同使用

参考:
原文: http://tengj.top/2017/12/28/springboot12/  
作者: 嘟嘟MD

作者:痴乙
来源:CSDN
原文:https://blog.csdn.net/fxbin123/article/details/80617754

posted @ 2018-12-07 13:16  张建斌  阅读(226)  评论(0编辑  收藏