2019独角兽企业重金招聘Python工程师标准>>>
API 的单元测试 在API测试中时非常重要的一部分,因为单元测试能确保API组件能正常运行。 在这篇文章中我们将学习如何使用JUnit覆盖String Boot Rest API测试。String Boot 是用于创建应用程序的开源框架,也用于创建我们的API。
单元测试API有许多不同的变种和技术。我更喜欢一下组合:Spring Boot, JUnit, MockMvc and Mockito, 因为他们都是开源的并且支持Java,是我的首选语言。
首先,我们必须有 Intellij IDEA 作为开发的IDE, JDK8 作为Java开发。这些都是我个人的喜欢的 但是你也可以使用 Eclipse, NetBeans 甚至是简单的文本编辑器。
现在,我们来建立一个项目,你也可以参与这里的源码 。我们将会测试Controller 和 repository 类。简单的说,我们有4个Controller (ArrivalController, DepartureController, UsersController, FlightsController) 和 4个repository (ArrivalRepository, DepartureRepository, UsersRepository, FlightsRepository). 我们将会为每一个Controller (测试 JSON Object 的大小, 请求的最终状态和对JSON object 里面单个元素的断言) 和 每一个 repository (表里插入2个新的元素并确保返回的结果是相等的) 编写单元测试。
第一步 - 创建API 测试项目
1. 安装 IntelliJ IDEA
2. 确保你的 JDK 已经安装 (版本>=1.8.XXX).
Now we will create a new project.
现在我们将创建一个新的项目
3. 打开 IntelliJ 并且点击 “Create New Project”
4. 选择 Gradle, Java 和 JDK 版本
5. 项目的名字
6. 选择项目的存放位置
如果你的所有操作都正确,你现在在编辑中应该可以看到一个空 Java 项目:
第二步 - 添加依赖
现在项目创建好了,我们必须添加依赖。你可以使用这些依赖,因为都是开放的。
具体操作,双击你的build.gradle 文件 增加下面的配置内容到配置文件:
group 'blazemeter' version '1.0-SNAPSHOT' buildscript { repositories { jcenter() mavenCentral() maven { url "http://repo.spring.io/libs-snapshot" } } dependencies { classpath("org.springframework.boot:spring-boot-gradle-plugin:1.5.2.RELEASE") } } apply plugin: 'java' apply plugin: 'idea' apply plugin: 'io.spring.dependency-management' apply plugin: 'org.springframework.boot' sourceSets { main.java.srcDir "src/main/java" main.resources.srcDir "src/main/resources" test.java.srcDir "src/test/java" test.resources.srcDir "src/test/resources" } jar { baseName = 'blaze-demo-api' version = '1.0' } bootRepackage { mainClass = 'com.demo.BlazeMeterApi' } dependencyManagement { imports { mavenBom 'io.spring.platform:platform-bom:Brussels-SR2' } } repositories { mavenCentral() jcenter() maven { url "http://repo.spring.io/libs-snapshot" } } sourceCompatibility = 1.8 targetCompatibility = 1.8 dependencies { compile group: 'org.springframework', name: 'spring-core' compile group: 'org.springframework.boot', name: 'spring-boot-starter-jdbc' compile group: 'org.springframework.boot', name: 'spring-boot-starter-web' compile group: 'org.springframework.boot', name: 'spring-boot-starter-actuator' compile group: 'org.springframework.boot', name: 'spring-boot-starter-security' compile group: 'org.springframework.boot', name: 'spring-boot-starter-data-jpa' compile group: 'org.springframework.security.oauth', name: 'spring-security-oauth2' compile group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-hibernate4' compile group: 'mysql', name: 'mysql-connector-java' compile group: 'io.rest-assured', name: 'rest-assured', version: '3.0.3' compile group: 'io.rest-assured', name: 'json-schema-validator', version: '3.0.3' testCompile group: 'org.springframework.boot', name: 'spring-boot-starter-test' testCompile group: 'org.springframework.security', name: 'spring-security-test' testCompile group: 'junit', name: 'junit' testCompile group: 'org.hsqldb', name: 'hsqldb' }
第三步 - 使用JUnit 编写单元测试
在 IDEA 中,进入你想要生测试的类,输入快捷键 Cmd + Shift + T (Windows 使用 Ctrl + Shift +T) 会出现一个弹窗,在弹窗里,选择"Create New Test..."。然后,IDEA 将会创建一个用于编写测试的文件。
这个文件将会创建到默认的位置。在我们的事例当中,如果我们要覆盖ArrivalController这个类,将会创建测试类到 test/java/com/demo/controller这个路径。你可以在下面的截图中看到:
我个人比较喜欢分组测试(你可以在相同的图片中看到 - 有3个文件夹:bdd,rest,unit) 根据不同的测试类型:REST,UNIT,BDD,等等 。这样做的原因是,我可以自己来创建我的测试类。在这个事例当中 ArrivalControllerTest 位于 test/java/com/demo/unit/controller 目录下
这里是测试类代码:
package com.demo.unit.controller; import com.demo.controller.ArrivalController; import com.demo.domain.Arrival; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; import java.util.List; import static com.demo.constant.Paths.ARRIVAL; import static com.demo.constant.Paths.VERSION; import static java.util.Collections.singletonList; import static org.hamcrest.collection.IsCollectionWithSize.hasSize; import static org.hamcrest.core.Is.is; import static org.mockito.BDDMockito.given; import static org.springframework.http.MediaType.APPLICATION_JSON; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @RunWith(SpringRunner.class) @WebMvcTest(ArrivalController.class) public class ArrivalControllerTest { @Autowired private MockMvc mvc; @MockBean private ArrivalController arrivalController; @Test public void getArrivals() throws Exception { Arrival arrival = new Arrival(); arrival.setCity("Yerevan"); ListallArrivals = singletonList(arrival); given(arrivalController.getAllArrivals()).willReturn(allArrivals); mvc.perform(get(VERSION + ARRIVAL + "all") .with(user("blaze").password("Q1w2e3r4")) .contentType(APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(jsonPath("$", hasSize(1))) .andExpect(jsonPath("$[0].city", is(arrival.getCity()))); } @Test public void getArrivalsById() throws Exception { Arrival arrival = new Arrival(); arrival.setCity("Yerevan"); given(arrivalController.getArrivalById(arrival.getId())).willReturn(arrival); mvc.perform(get(VERSION + ARRIVAL + arrival.getId()) .with(user("blaze").password("Q1w2e3r4")) .contentType(APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(jsonPath("city", is(arrival.getCity()))); } }
在这个测试类中我们有2个测试方法 getArrivals() 和 getArrivalsById()。我们有2个测试方法的原因是在contrller 中有2个方法,因此我们要测试这2个方法。
getArrivals() 这个方法做了以下的事情(上面的代码片段):
- 创建Arrival实体并设置city的测试值
- 创建Arrivals集合(因为我们只有一个成员,它就是一个signeltonList)
- 使用mockito given,确保模拟ArrivalController 将会返回一个Arrivals集合
- 执行一个带用户凭证的GET 请求 到mocked controller,并且执行简单的断言
- 状态 - 200 (which isOk)
- JSON object 有一个元素
- JSON body 中有一个我们设置了值的city
在第二个测试方法getArrivalsById()我们做了同样事情。唯一的区别是假定返回一个Arrivals JSON object 代替返回Arrivals JSON object 集合。
到这里。现在我们可以点击方法名前面的按钮(看下图)执行一个单元测试。这个执行的目的是为了确保单元测试能正确的运行:
在执行完成之后你可以看到测试执行的结果,包括状态,计数和堆栈跟踪。我们可以看到我们的测试通过了(在窗体左侧部分),测试的数量(在进度条的顶部)还有执行的堆栈信息
这里只是测试单个 controller。下一步将会为所有controller 添加测试。
第四步 - 为API 创建单元测试
现在,让我们为单个 API repository 类创建单元测试。这个单元测试主要是覆盖数据库测试部分。例如,往DB中写入测试数据,然后验证数据保存的正确性。
像前面一样,进入ArrivalRepository 类,输入快捷键 Cmd + Shift + T 通过IDEA 创建一个新的测试类。
这个代码就是我们的测试类:
package com.demo.unit.repository; import com.demo.domain.Arrival; import com.demo.repository.ArrivalRepository; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.junit4.SpringRunner; import java.util.List; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace.NONE; @ActiveProfiles("test") @RunWith(SpringRunner.class) @DataJpaTest @AutoConfigureTestDatabase(replace = NONE) public class ArrivalRepositoryTest { @Autowired private TestEntityManager entityManager; @Autowired private ArrivalRepository arrivalRepository; @Test public void whenFindAll() { //given Arrival firstArrival = new Arrival(); firstArrival.setCity("Yerevan"); entityManager.persist(firstArrival); entityManager.flush(); Arrival secondArrival = new Arrival(); secondArrival.setCity("Israel"); entityManager.persist(secondArrival); entityManager.flush(); //when Listarrivals = arrivalRepository.findAll(); //then assertThat(arrivals.size()).isEqualTo(9); assertThat(arrivals.get(7)).isEqualTo(firstArrival); assertThat(arrivals.get(8)).isEqualTo(secondArrival); } @Test public void whenFindAllById() { //given Arrival arrival = new Arrival(); arrival.setCity("Yerevan"); entityManager.persist(arrival); entityManager.flush(); //when Arrival testArrival = arrivalRepository.findAllById(arrival.getId()); //then assertThat(testArrival.getCity()).isEqualTo(arrival.getCity()); } }
在这个测试类中,我们测试是使用的H2 数据库。这是比较通常的做法。否则,你必须有一个相似类型的数据库安装在你的 test/dev 环境,你需要维护它们并且在测试完成后确保数据全部清掉。当你使用H2 DB的时候这些操作都不是必须的,因为它是运行在内存当中。测试完成之后,数据库将会全部销毁。
测试涵盖了一下内容:
- 使用 entityManager 在H2 数据库 Arrival表创建了2条新数据
- 通过findAll 查询所有的记录
- Perform assertions on the size of the data and equality of gathered objects
- 执行断言,对比查询获取的对象数量与实际数据大小
第二个方法做了类似的操作,但因为它是执行的findAllById, 检查的仅仅是单个对象相等。
像这样,你现在可以为其他repository 类创建他们的测试方法。
第五步 - 为所有的API 执行单元测试
所有的controller 和 repository 类单元测试都创建好之后,我们需要执行他。选中高亮的单元测试文件夹 -> 右击 -> 选择 "Run Tests" in 'com.demo.unit' with Coverage(看下一张图片),这样操作我们可以得到测试代码覆盖报告
这些覆盖结果,展示了有多少类和方法被单元测试覆盖。
就这酱紫!你现在知道怎么样运行JUnit单元测试测试 REST API了。你现在想自动化测试你的API吗?Get your API Testing started with BlazeMeter.(广告)
原文:https://www.blazemeter.com/blog/spring-boot-rest-api-unit-testing-with-junit