朱皮特的博客 自由的飞翔

springboot极简教程011-单元测试

2018-10-28
朱皮特
阅读量:

创建单元测试

在IDEA里,如果要对某个类创建单元测试,只需要在代码区域激活选中该类的文件,然后按下快捷键:CTRL + SHIFT + T,则会弹出一个菜单,选择后根据向导就可以自动创建单元测试类:

  • Testing library: 默认JUnit4
  • Class name: 默认在原类添加Test后缀
  • Destination package: 和原类相同
  • Generate: 可以勾选@Before和@After,用来在测试开始前和结束后做一些操作。
  • Member: 显示原类的所有成员函数,基本上全勾选上。

点击确定后,会自动在test\java下生成测试类,例如CourseServiceImplTest:

package com.example.dao.service;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import static org.junit.Assert.*;

public class CourseServiceImplTest {

    @Before
    public void setUp() throws Exception {
    }

    @After
    public void tearDown() throws Exception {
    }

    @Test
    public void deleteByIds() {
    }

    @Test
    public void deleteByIds1() {
    }

    @Test
    public void queryList() {
    }

    @Test
    public void queryList1() {
    }
}

测试功能类

如果仅仅是测试类的某些功能,又不需要涉及到网络业务相关的,可以在单元测试类里直接使用该类并调用该类的功能函数,然后对结果进行Assert验证,如果运行单元测试时验证不通过,会在日志中输出不通过的源码位置,进而就可以知道是哪个函数功能测试没有通过了。

可以看一个例子:

import com.example.dao.model.Course;
import com.example.dao.service.CourseService;
import org.hamcrest.CoreMatchers;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;


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

    @Autowired
    private CourseService courseService;

    @Test
    public void getCourse() {
        // 测试插入,插入成功的话返回 1
        int n = courseService.save(new Course(1040L, "Spring boot Demo", "https://www.spring.com"));
        Assert.assertThat(n, CoreMatchers.equalTo(1));

        // 测试查询
        Course course = courseService.selectByKey(1001);
        Assert.assertThat(course.getAuthor(), CoreMatchers.equalTo("spring"));

        // 测试删除
        //courseService.delete();
    }
}

该测试单元主要是对服务类CourseService进行测试,主要测试了插入和查询操作,通过对返回结果的验证来判断功能是否OK,这里使用了断言函数Assert.assertThat来进行验证,该断言的常见用法整理到最后了,可以参考。

测试网络业务相关

MockMvc

上面对单纯功能的测试比较简单,但是如果涉及到网络请求的业务测试,搭建一套网络环境就比较麻烦,如何更简单地进行测试呢?这时候就要用到MockMvc了,可以不用启动工程就可以测试业务逻辑。

MockMvc实现了对Http请求的模拟,能够直接使用网络的形式,转换到Controller的调用,这样可以使得测试速度快、不依赖网络环境,而且提供了一套验证的工具,这样可以使得请求的验证统一而且很方便。

package com.example;

import com.example.dao.model.User;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

@RunWith(SpringRunner.class)
@SpringBootTest
public class CourseControllerTest {
    protected Logger logger = LoggerFactory.getLogger(getClass());
    @Autowired
    private WebApplicationContext webContext;
    private MockMvc mvc = null;
    private MockHttpSession session = null;

    @Before
    public void setupMockMvc() {
        logger.info("@Before...");
        mvc = MockMvcBuilders.webAppContextSetup(webContext).build();
        session = new MockHttpSession();
        User user = new User("root", "root");
        session.setAttribute("user", user);
    }

    @After
    public void after() {
        logger.info("@After...");
    }


    /**
     * 新增教程测试用例
     *
     * @throws Exception
     */
    @Test
    public void addCourse() throws Exception {
        String json = "{\"author\":\"HAHAHAA\",\"title\":\"Spring\",\"url\":\"http://www.spring.com\"}";
        mvc.perform(MockMvcRequestBuilders.post("/course/add")
                .accept(MediaType.APPLICATION_JSON_UTF8)
                .content(json.getBytes()) //传json参数
                .session(session)
        )
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andDo(MockMvcResultHandlers.print());
    }

    /**
     * 获取教程测试用例
     *
     * @throws Exception
     */
    @Test
    public void queryCourse() throws Exception {
        mvc.perform(MockMvcRequestBuilders.get("/course/resource/1001")
                .contentType(MediaType.APPLICATION_JSON_UTF8)
                .accept(MediaType.APPLICATION_JSON_UTF8)
                .session(session)
        )
                .andExpect(MockMvcResultMatchers.status().isOk())
//                .andExpect(MockMvcResultMatchers.jsonPath("$.author").value("course author name"))
//                .andExpect(MockMvcResultMatchers.jsonPath("$.title").value("course tile name"))
                .andDo(MockMvcResultHandlers.print());
    }

    /**
     * 修改教程测试用例
     *
     * @throws Exception
     */
    @Test
    public void updateCourse() throws Exception {
        String json = "{\"author\":\"测试修改\",\"id\":1031,\"title\":\"Spring教程\",\"url\":\"http://www.spring.com\"}";
        mvc.perform(MockMvcRequestBuilders.post("/course/update")
                .accept(MediaType.APPLICATION_JSON_UTF8)
                .contentType(MediaType.APPLICATION_JSON_UTF8)
                .content(json.getBytes())//传json参数
                .session(session)
        )
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andDo(MockMvcResultHandlers.print());
    }

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

postman

用户在开发或者调试网络程序或者是网页B/S模式的程序的时候是需要一些方法来跟踪网页请求的,用户可以使用一些网络的监视工具比如著名的Firebug等网页调试工具。今天给大家介绍的这款网页调试工具不仅可以调试简单的css、html、脚本等简单的网页基本信息,它还可以发送几乎所有类型的HTTP请求!Postman在发送网络HTTP请求方面可以说是Chrome插件类产品中的代表产品之一。

插件安装

chrome网上应用商店安装,貌似已经搜索不到了。

软件安装

https://www.getpostman.com/apps,下载安装。Postman提供了独立的安装包,不再依赖于Chrome浏览器了。同时支持MAC、Windows和Linux,推荐使用这种方式安装。

测试回滚

有时不希望测试时对数据库里产生垃圾数据,那么可以在测试时回滚下数据,这需要数据库支持回滚,例如MySQL的存储引擎为InnoDB时是支持回滚的,如果不是可以修改一下,具体可以参考:springboot极简教程010-数据库准备

然后在测试函数上添加注解:@Transactional@Rollback(true)即可,例如:

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

    @Autowired
    private CourseService courseService;

    @Test
    @Transactional
    @Rollback(true)
    public void getCourse() {
        // 测试插入,插入成功的话返回 1
        int n = courseService.save(new Course(1040L, "Spring boot Demo", "https://www.spring.com"));
        Assert.assertThat(n, CoreMatchers.equalTo(1));

        // 测试查询
        Course course = courseService.selectByKey(1001);
        Assert.assertThat(course.getAuthor(), CoreMatchers.equalTo("spring"));

        // 测试删除
        //courseService.delete();
    }
}

Assert.assertThat

// 相当于Object的equals方法
Assert.assertThat(a, CoreMatchers.equalTo(b));

// 对象不相等
assertThat(s, not(equalTo("developer")));

// 忽略大小写比较两个字符串是否相等
assertThat(testedString, equalToIgnoringCase(expectedString));

// 忽略头尾任意个空白字符比较两个字符串是否相等(字符串中的空白字符不忽略)
assertThat(testedString, equalToIgnoringWhiteSpace(expectedString);

// 字符串中是否包含某个子串
assertThat(s, not(containsString("Works"))); 

assertThat(s, anyOf(containsString("developer"), containsString("Works"))); 

// 字符串以某个前缀开头
assertThat(s, startsWith(prefix));

// 字符串以某个后缀结尾
assertThat(s, endsWith(suffix));

// 判断对象是否为null
assertThat(object, nullValue());

// 判断对象是否不为null
assertThat(object, notNullValue());

// is表达式
assertThat(testedString, is(equalTo(expectedValue)));

assertThat(testedValue, is(expectedValue));

// 断言对象是某类的实例
assertThat(testedObject, is(Cheddar.class));

// 
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));

Comments

Content