关于java:Java单元测试浅析JUnitMockito

42次阅读

共计 10814 个字符,预计需要花费 28 分钟才能阅读完成。

作者:京东物流 秦彪

1. 什么是单元测试

(1)单元测试环节:

测试过程依照阶段划分分为:单元测试、集成测试、零碎测试、验收测试等。相干含意如下:

1)       单元测试:针对计算机程序模块进行输入正确性测验工作。

2)       集成测试:在单元测试根底上,整合各个模块组成子系统,进行集成测试。

3)       零碎测试:将整个交付所波及的合作内容都纳入其中思考,蕴含计算机硬件、软件、接口、操作等等一系列作为一个整体,测验是否满足软件或需要阐明。

4)       验收测试:在交付或者公布之前对所做的工作进行测试测验。

单元测试是阶段性测试的首要环节,也是白盒测试的一种,该内容的编写与实际能够前置在研发实现,研发在编写业务代码的时候就须要生成对应代码的单元测试。单元测试的发起人是程序设计者,受益人也是编写程序的人,所以对于程序员,十分有必要造成自我约束力,实现根本的单元测试用例编写。

(2)单元测试特色:

由上可知,单元测试其实是针对软件中最小的测试单元来进行验证的。这里的单元就是指相干的性能子集,比方一个办法、一个类等。值得注意的是作为最低级别的测试流动,单元测试验证的对象仅限于以后测试内容,与程序其它局部内容相隔离,总结起来单元测试有以下特色:

1)       次要性能是证实编写的代码内容与冀望输入统一。

2)       最小最低级的测试内容,由程序员本身发动,保障程序根本组件失常。

3)       单元测试尽量不要区分类与办法,主张以过程性的办法为测试单位,简略实用高效为指标。

4)       不要偏离主题,专一于测试一小块的代码,保障根底性能。

5)       剥离与内部接口、存储之间的依赖,使单元测试可控。

6)       任何工夫任何程序执行单元测试都须要是胜利的。

2. 为什么要单元测试

(1)单元测试意义:

程序代码都是由根本单元一直组合成简单的零碎,底层根本单元都无奈保障输入输出正确性,层级递增时,问题就会一直放大,直到整个零碎解体无奈应用。所以单元测试的意义就在于保障基本功能是失常可用且稳固的。而对于接口、数据源等起因造成的不稳固因素,是外在起因,不在单元测试思考范畴之内。

(2)应用 main 办法进行测试:

@PostMapping(value="/save")
public Map<String,Object> save(@RequestBody Student stu) {studentService.save(stu);
    Map<String,Object> params = new HashMap<>();
    params.put("code",200);
    params.put("message","保留胜利");
    return params;
}

如果要对下面的 Controller 进行测试,能够编写如下的代码示例,应用 main 办法进行测试的时候,先启动整个工程利用,而后编写 main 办法如下进行拜访,在单步调试代码。

public static void main(String[] args) {HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        String json = "{"name":" 张三 ","className":" 三年级一班 ","age":"20","sex":" 男 "}";
        HttpEntity<String> httpEntity = new HttpEntity<>(json, headers);
        String url = "http://localhost:9092/student/save";
        MainMethodTest test = new MainMethodTest();
        ResponseEntity<Map> responseEntity = test.getRestTemplate().postForEntity(url, httpEntity, Map.class);
        System.out.println(responseEntity.getBody());
    }

(3)应用 main 办法进行测试的毛病:

1)       通过编写大量的 main 办法针对每个内容做打印输出到控制台干燥繁琐,不具备优雅性。

2)       测试方法不能一起运行,后果须要程序员本人判断正确性。

3)       对立且重复性工作应该交给工具去实现。

3. 单元测试框架 -JUnit

3.1 JUnit 简介

JUnit 官网:https://junit.org/。JUnit 是一个用于编写可反复测试的简略框架。它是用于单元测试框架的 xUnit 体系结构的一个实例。

JUnit 的特点:

(1)针对于 Java 语言特定设计的单元测试框架,应用十分宽泛。

(2)特定畛域的规范测试框架。

(3)可能在多种 IDE 开发平台应用,蕴含 Idea、Eclipse 中进行集成。

(4)可能不便由 Maven 引入应用。

(5)能够不便的编写单元测试代码,查看测试后果等。

JUnit 的重要概念:

名称 性能作用
Assert 断言办法汇合
TestCase 示意一个测试案例
TestSuite 蕴含一组 TestCase,形成一组测试
TestResult 收集测试后果

JUnit 的一些注意事项及标准:

(1)测试方法必须应用 @Test 润饰

(2)测试方法必须应用 public void 进行润饰,不能带参数

(3)测试代码的包应该和被测试代码包构造保持一致

(4)测试单元中的每个办法必须能够独立测试,办法间不能有任何依赖

(5)测试类个别应用 Test 作为类名的后缀

(6)测试方法使个别用 test 作为办法名的前缀

JUnit 失败后果阐明:

(1)Failure:测试后果和预期后果不统一导致,示意测试不通过

(2)error:由异样代码引起,它能够产生于测试代码自身的谬误,也能够是被测代码的 Bug

3.2 JUnit 内容

(1)断言的 API

断言办法 断言形容
assertNull(String message, Object object) 查看对象是否为空,不为空报错
assertNotNull(String message, Object object) 查看对象是否不为空,为空报错
assertEquals(String message, Object expected, Object actual) 查看对象值是否相等,不相等报错
assertTrue(String message, boolean condition) 查看条件是否为真,不为真报错
assertFalse(String message, boolean condition) 查看条件是否为假,为真报错
assertSame(String message, Object expected, Object actual) 查看对象援用是否相等,不相等报错
assertNotSame(String message, Object unexpected, Object actual) 查看对象援用是否不等,相等报错
assertArrayEquals(String message, Object[] expecteds, Object[] actuals) 查看数组值是否相等,遍历比拟,不相等报错
assertArrayEquals(String message, Object[] expecteds, Object[] actuals) 查看数组值是否相等,遍历比拟,不相等报错
assertThat(String reason, T actual, Matcher<? super T> matcher) 查看对象是否满足给定规定,不满足报错

(2)JUnit 罕用注解:

1)@Test: 定义一个测试方法 @Test(excepted=xx.class): xx.class 示意异样类,示意测试的办法抛出此异样时,认为是失常的测试通过的 @Test(timeout = 毫秒数) : 测试方法执行工夫是否合乎预期。

2)@BeforeClass:在所有的办法执行前被执行,static 办法全局只会执行一次,而且第一个运行。

3)@AfterClass:在所有的办法执行之后进行执行,static 办法全局只会执行一次,最初一个运行。

4)@Before:在每一个测试方法被运行前执行一次。

5)@After:在每一个测试方法运行后被执行一次。

6)@Ignore:所润饰的测试方法会被测试运行器疏忽。

7)@RunWith:能够更改测试执行器应用 junit 测试执行器。

3.3 JUnit 应用

3.3.1 Controller 层单元测试

(1)Springboot 中应用 maven 引入 Junit 非常简单, 应用如下依赖即可引入:

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

(2)下面应用 main 办法案例能够应用如下的 Junit 代码实现:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = MainApplication.class)
public class StudentControllerTest {
    
    // 注入 Spring 容器
    @Autowired
    private WebApplicationContext applicationContext;
    // 模仿 Http 申请
    private MockMvc mockMvc;

    @Before
    public void setupMockMvc(){
        // 初始化 MockMvc 对象
        mockMvc = MockMvcBuilders.webAppContextSetup(applicationContext).build();}
    
    /**
     * 新增学生测试用例
     * @throws Exception
     */
    @Test
    public void addStudent() throws Exception{String json="{"name":" 张三 ","className":" 三年级一班 ","age":"20","sex":" 男 "}";
        mockMvc.perform(MockMvcRequestBuilders.post("/student/save")    // 结构一个 post 申请
                    // 发送端和接收端数据格式
                    .contentType(MediaType.APPLICATION_JSON_UTF8)
                    .accept(MediaType.APPLICATION_JSON_UTF8)
                    .content(json.getBytes())
            )
           // 断言校验返回的 code 编码
           .andExpect(MockMvcResultMatchers.status().isOk())
           // 增加处理器打印返回后果
           .andDo(MockMvcResultHandlers.print());
    }
}

只须要在类或者指定办法上右键执行即可,能够间接充当 postman 工作拜访指定 url,且不须要写申请代码,这些都由工具主动实现。

(3)案例中相干组件介绍

本案例中结构 mockMVC 对象时,也能够应用如下形式:

@Autowired
private StudentController studentController;
@Before
public void setupMockMvc(){
   // 初始化 MockMvc 对象
   mockMvc = MockMvcBuilders.standaloneSetup(studentController).build();}

其中 MockMVC 是 Spring 测试框架提供的用于 REST 申请的工具,是对 Http 申请的模仿,无需启动整个模块就能够对 Controller 层进行调用,速度快且不依赖网络环境。

应用 MockMVC 的根本步骤如下:

1) mockMvc.perform 执行申请

2) MockMvcRequestBuilders.post 或 get 结构申请

3) MockHttpServletRequestBuilder.param 或 content 增加申请参数

4) MockMvcRequestBuilders.contentType 增加申请类型

5) MockMvcRequestBuilders.accept 增加响应类型

6) ResultActions.andExpect 增加后果断言

7) ResultActions.andDo 增加返回后果后置解决

8) ResultActions.andReturn 执行实现后返回相应后果

3.3.2 Service 层单元测试

能够编写如下代码对 Service 层查询方法进行单测:

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

    @Autowired
    private StudentService studentService;

    @Test
    public void getOne() throws Exception {Student stu = studentService.selectByKey(5);
         Assert.assertThat(stu.getName(),CoreMatchers.is("张三"));
    }
}

执行后果:

3.3.3 Dao 层单元测试

能够编写如下代码对 Dao 层保留办法进行单测:

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

    @Autowired
    private StudentMapper studentMapper;

    @Test
    @Rollback(value = true)
    @Transactional
    public void insertOne() throws Exception {Student student = new Student();
         student.setName("李四");
         student.setMajor("计算机学院");
         student.setAge(25);
         student.setSex('男');
         int count = studentMapper.insert(student);
         Assert.assertEquals(1, count);
    }
}

其中 @Rollback(value = true) 能够执行单元测试之后回滚所新增的数据,放弃数据库不产生脏数据。

3.3.4 异样测试

(1)在 service 层定义一个异常情况:

public void computeScore() {int a = 10, b = 0;}

(2)在 service 的测试类中定义单元测试办法:

@Test(expected = ArithmeticException.class)
    public void computeScoreTest() {studentService.computeScore();
    }

(3)执行单元测试也会通过,起因是 @Test 注解中的定义了异样

3.3.5 测试套件测多个类

(1)新建一个空的单元测试类

(2)利用注解 @RunWith(Suite.class) 和 @SuiteClasses 表明要一起单元测试的类

@RunWith(Suite.class)
@Suite.SuiteClasses({StudentServiceTest.class, StudentDaoTest.class})
public class AllTest {}

运行后果:

3.3.6 idea 中查看单元测试覆盖率

(1)单测覆盖率

测试覆盖率是掂量测试过程工作自身的有效性,晋升测试效率和缩小程序 bug,晋升产品可靠性与稳定性的指标。

统计单元测试覆盖率的意义:

1)能够洞察整个代码中的根底组件性能的所有盲点,发现相干问题。

2)进步代码品质,通常覆盖率低示意代码品质也不会太高,因为单测不通过原本就映射出思考到各种状况不够充沛。

3)从覆盖率的达标上能够进步代码的设计能力。

(2)在 idea 中查看单元测试覆盖率很简略,只需依照图中示例的图标运行,或者在单元测试办法或类上右键 Run ‘xxx’ with Coverage 即可。执行后果是一个表格,列出了类、办法、行数、分支笼罩状况。

(3)在代码中会标识出笼罩状况,绿色的是已笼罩的,红色的是未笼罩的。

(4)如果想要导出单元测试的覆盖率后果,能够应用如下图所示的形式,勾选 Open generated HTML in browser

导出后果:

3.3.7 JUnit 插件主动生成单测代码

(1)装置插件,重启 idea 失效

(2)配置插件

(3)应用插件

在须要生成单测代码的类上右键 generate…,如下图所示。

生成后果:

4. 单元测试工具 -Mockito

4.1 Mockito 简介

在单元测试过程中主张不要依赖特定的接口与数据起源,此时就波及到对相干数据的模仿,比方 Http 和 JDBC 的返回后果等,能够应用虚构对象即 Mock 对象进行模仿,使得单元测试不在耦合。

Mock 过程的应用前提:

(1)理论对象时很难被结构进去的

(2)理论对象的特定行为很难被触发

(3)理论对象可能以后还不存在,比方依赖的接口还没有开发实现等等。

Mockito 官网:https://site.mockito.org。Mockito 和 JUnit 一样是专门针对 Java 语言的 mock 数据框架,它与同类的 EasyMock 和 jMock 性能十分类似,然而该工具更加简略易用。

Mockito 的特点:

(1)能够模仿类不仅仅是接口

(2)通过注解形式简略易懂

(3)反对程序验证

(4)具备参数匹配器

4.2 Mockito 应用

maven 引入 spring-boot-starter-test 会主动将 mockito 引入到工程中。

4.2.1 应用案例

(1)在之前的代码中在定义一个 BookService 接口, 含意是借书接口,暂且不做实现

public interface BookService {Book orderBook(String name);
}

(2)在之前的 StudentService 类中新增一个 orderBook 办法,含意是学生预约书籍办法,其中实现内容调用上述的 BookService 的 orderBook 办法。

public Book orderBook(String name) {return bookService.orderBook(name);
}

(3)编写单元测试办法,测试 StudentService 的 orderBook 办法

@Test
public void orderBookTest() {Book expectBook = new Book(1L, "钢铁是怎么炼成的", "书架 A01");
    Mockito.when(bookService.orderBook(any(String.class))).thenReturn(expectBook);
    Book book = studentService.orderBook("");
    System.out.println(book);
    Assert.assertTrue("预约书籍不符", expectBook.equals(book));
}

(4)执行后果:

(5)后果解析

上述内容并没有实现 BookService 接口的 orderBook(String name) 办法。然而应用 mockito 进行模仿数据之后,却通过了单元测试,起因就在于 Mockito 替换了原本要在 StudentService 的 orderBook 办法中获取的对象,此处就模仿了该对象很难获取或以后无奈获取到,用模仿数据进行代替。

4.2.2 相干语法

罕用 API:

上述案例中用到了 mockito 的 when、any、theWhen 等语法。接下来介绍下都有哪些罕用的 API:

1)       mock:模仿一个须要的对象

2)       when:个别配合 thenXXX 一起应用,示意当执行什么操作之后怎么。

3)       any:  返回一个特定对象的缺省值,上例中标识能够填写任何 String 类型的数据。

4)       theReturn: 在执行特定操作后返回指定后果。

5)       spy:发明一个监控对象。

6)       verify:验证特定的行为。

7)       doReturn:返回后果。

8)       doThrow:抛出特定异样。

9)       doAnswer:做一个自定义响应。

10)     times:操作执行次数。

11)     atLeastOnce:操作至多要执行一次。

12)     atLeast:操作至多执行指定的次数。

13)     atMost:操作至少执行指定的次数。

14)     atMostOnce:操作至少执行一次。

15)     doNothing:不做任何的解决。

16)     doReturn:返回一个后果。

17)     doThrow:抛出一个指定异样。

18)     doAnswer:指定一个特定操作。

19)     doCallRealMethod:用于监控对象返回一个实在后果。

4.2.3 应用要点

(1)打桩

Mockito 中有 Stub,所谓存根或者叫打桩的概念,下面案例中的 Mockito.when(bookService.orderBook(any(String.class))).thenReturn(expectBook); 就是打桩的含意,先定义好如果依照既定的形式调用了什么,后果就输入什么。而后在应用 Book book = studentService.orderBook(“”); 即依照指定存根输入指定后果。

    @Test
    public void verifyTest() {List mockedList = mock(List.class);

        mockedList.add("one");

        verify(mockedList).add("one");            // 验证通过,因为后面定义了这个桩
        verify(mockedList).add("two");            // 验证失败,因为后面没有定义了这个桩
    }

(2)参数匹配

上例 StudentService 的 orderBook 办法中的 any(String.class) 即为参数匹配器,能够匹配任何此处定义的 String 类型的数据。

(3)次数验证

    @Test
    public void timesTest() {List mockedList = mock(List.class);
        when(mockedList.get(anyInt())).thenReturn(1000);
        System.out.println(mockedList.get(1));
        System.out.println(mockedList.get(1));
        System.out.println(mockedList.get(1));
        System.out.println(mockedList.get(2));

        // 验证通过:get(1) 被调用 3 次
        verify(mockedList, times(3)).get(1);
        // 验证通过:get(1) 至多被调用 1 次
        verify(mockedList, atLeastOnce()).get(1);
        // 验证通过:get(1) 至多被调用 3 次
        verify(mockedList, atLeast(3)).get(1);
    }

(4)程序验证

    @Test
    public void orderBookTest1() {String json = "{"id":12,"location":" 书架 A12","name":" 三国演义 "}";
        String json1 = "{"id":21,"location":" 书架 A21","name":" 水浒传 "}";
        String json2 = "{"id":22,"location":" 书架 A22","name":" 红楼梦 "}";
        String json3 = "{"id":23,"location":" 书架 A23","name":" 西游记 "}";
        when(bookService.orderBook("")).thenReturn(JSON.parseObject(json, Book.class));
        Book book = bookService.orderBook("");
        Assert.assertTrue("预约书籍有误", "三国演义".equals(book.getName()));

        when(bookService.orderBook("")).thenReturn(JSON.parseObject(json1, Book.class)).
                thenReturn(JSON.parseObject(json2, Book.class)).
                thenReturn(JSON.parseObject(json3, Book.class));
        Book book1 = bookService.orderBook("");
        Book book2 = bookService.orderBook("");
        Book book3 = bookService.orderBook("");
        Book book4 = bookService.orderBook("");
        Book book5 = bookService.orderBook("");
        // 全副验证通过,按程序最初打桩打了 3 次,大于 3 次依照最初对象输入
        Assert.assertTrue("预约书籍有误", "水浒传".equals(book1.getName()));
        Assert.assertTrue("预约书籍有误", "红楼梦".equals(book2.getName()));
        Assert.assertTrue("预约书籍有误", "西游记".equals(book3.getName()));
        Assert.assertTrue("预约书籍有误", "西游记".equals(book4.getName()));
        Assert.assertTrue("预约书籍有误", "西游记".equals(book5.getName()));
}

(5)异样验证

    @Test(expected = RuntimeException.class)
    public void exceptionTest() {List mockedList = mock(List.class);
        doThrow(new RuntimeException()).when(mockedList).add(1);
        // 验证通过
        mockedList.add(1);
    }

正文完
 0