Springboot08-项目单元测试(接口测试)
前言
1-本文重点在于源码层面,分析Springboot单元测试的使用,对于其中的注解、方法等,不会仔细分析;
2-本文项目实例相关配置:Java-1.8; Springboot-1.5.12.RELEASE,使用的是Intellij
正文
1- JUnitGenerator V2.0 单元测试代码自动生成插件的使用
因为插件的安装和使用,比较简单,见参考资料-1,或自行百度;这里不再赘述;
注意:JUnitGenerator主要是可以针对需要测试的类,在test目录下快速生成对应的测试类,而测试的具体实现还是要手动处理;
2-最简单的单元测试,源码如下:
package com.hs.web.controller.system.login; import net.sf.json.JSONObject; @RunWith(SpringRunner.class) @SpringBootTest(classes= FrameworkApplication.class) public class LoginControllerTest { private MockMvc mvc; @Autowired private WebApplicationContext context; @Before public void setUp() throws Exception { mvc = MockMvcBuilders.webAppContextSetup(context).build(); } @Test public void login() throws Exception{ //1-封装请求参数 Map<String, String> paramMap = new HashMap<>(); paramMap.put("openid", "tangyujie"); JSONObject jsonObject = JSONObject.fromObject(paramMap); //2-构建请求 RequestBuilder builder = MockMvcRequestBuilders.post("/api/shop/login").content(jsonObject.toString()). accept(MediaType.APPLICATION_JSON).contentType(MediaType.APPLICATION_JSON); //3-执行请求 mvc.perform(builder) .andExpect(status().isOk()) .andExpect(jsonPath("code").value(200)) .andDo(MockMvcResultHandlers.print()); } @After public void after(){ } }
如果只测试一个接口,或者只测试一个类,那么上面的方法基本上已经可以满足需求,但是针对上百个类或方法的情况,则会出现大量的代码冗余,并且还需要手动启动每一个测试类,这明显是不可取的;
所以,在上述简单的单元测试方法上,进行封装
3-封装通用的单元测试模板
单元测试模板可以满足如下需求:
- 抽取公用的方法和注解,优化代码,实现代码复用,也方便维护
- 多个单元测试类,一个启动入口,实现一键测试
接口测试的步骤
- 请求数据
- 构造请求
- 执行请求及判断请求结果
- 处理返回参数(本步骤可以没有)
备注:本项目的接口因为供App使用,(除了登录接口)其他都是要传token(跟在UTL?后面)
源码如下
测试模板目录
BaseTest抽象类,用于封装通用方法
package com.hs.api.base; import net.sf.json.JSONObject; public abstract class BaseTest { //1-1-构建post请求 protected RequestBuilder getPostBuilder(String url, JSONObject jsonObject){ RequestBuilder builder = MockMvcRequestBuilders .post(url) .content(jsonObject.toString()) .accept(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON); return builder; } //1-2-构建get请求 protected RequestBuilder getGetBuilder(String url){ RequestBuilder builder = MockMvcRequestBuilders .get(url) .accept(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON); return builder; } //2-执行请求 protected MvcResult performMVC(MockMvc mvc, RequestBuilder bulider ){ try { MvcResult mvcResult = mvc.perform(bulider) .andExpect(status().isOk()) .andExpect(jsonPath("code").value(200)) //.andDo(MockMvcResultHandlers.print()) .andReturn(); return mvcResult; } catch (Exception e) { e.printStackTrace(); return null; } } }
BaseControllerTest类,继承 BaseTest抽象类,且是所有单元测试类的父类, 使用了@RunWith、@SpringBootTest等类注解,并且实现了通用的@Before和@After方法,以及登录接口的具体实现,主要是从普通的单元测试类中提取出公有的因素
package com.hs.api.base; import net.sf.json.JSONObject; //所有单元测试类的基类 @RunWith(SpringRunner.class) @SpringBootTest(classes= FrameworkApplication.class) @WebAppConfiguration public abstract class BaseControllerTest extends BaseTest { @Autowired protected WebApplicationContext context; protected MockMvc mvc ; protected static String shopToken = null;//apptoken @Before protected void setUp() throws Exception { System.out.println("controller test is starting"); mvc = MockMvcBuilders.webAppContextSetup(context).build(); if(shopToken == null){ System.out.println("获取shoptoken"); shopToken = shopLogin(); } } @After protected void tearDown(){ System.out.println("controller test is over"); } //1-App登录操作 protected String shopLogin() { //1-请求数据 String url = "/api/shop/login"; LoginReq loginReq = new LoginReq("tangyujie"); JSONObject content = JSONObject.fromObject(loginReq); //2-构造请求 RequestBuilder postBuilder = getPostBuilder(url, content); try { //3-执行请求-及判断请求结果 MvcResult mvcResult = performMVC(mvc, postBuilder); //4-c处理返回参数,获取shopToken String contentAsString = mvcResult.getResponse().getContentAsString(); //System.out.println("contentAsString=== " + contentAsString); JSONObject jsonObject = JSONObject.fromObject(contentAsString); Map<String,Class<?>> classMap = new HashMap<String,Class<?>>(); classMap.put("data", LoginRes.class); TestShopLoginRes entity = (TestShopLoginRes)JSONObject.toBean(jsonObject,TestShopLoginRes.class,classMap); //System.out.println("entity: " + entity); return entity .getData().getShopToken(); } catch (Exception e) { e.printStackTrace(); return null; } } }
ApiShopLoginControllerTest类,登录的接口的测试类,继承BaseControllerTest类;本类包含一个POST接口实例
package com.hs.api.shopapp.controller; public class ApiShopLoginControllerTest extends BaseControllerTest{ //POST请求示例 //1-登录 @Test public void loginOrRegister() { shopLogin(); } }
ApiShopMemberControllerTest类,用户接口的测试类,继承BaseControllerTest类;本类包含一个GET接口实例
package com.hs.api.shopapp.controller; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; public class ApiShopMemberControllerTest extends BaseControllerTest{ //GET请求示例 @Test public void getMemberShopInfo() { //1-请求参数 System.out.println("shopToken == " + shopToken); String url = "/api/shop/member/detail?shopToken=" + shopToken; //2-构建请求 RequestBuilder getBuilder = getGetBuilder(url); try { //3-执行请求 MvcResult mvcResult = performMVC(mvc, getBuilder); System.out.println("getMemberShopInfo mvcResult: " + mvcResult); } catch (Exception e) { e.printStackTrace(); } } }
TestSuits类,本类没有具体的方法,唯一的作用是作为启动入口,测试全部测试类的方法
package com.hs.api; import org.junit.runner.RunWith; import org.junit.runners.Suite; //单元测试总入口,在@Suite.SuiteClasses里面添加需要测试的测试类 @RunWith(Suite.class) @Suite.SuiteClasses({ ApiShopLoginControllerTest.class, ApiShopMemberControllerTest.class }) public class TestSuits { }
以下是模板用到的实体类POJO,主要是用户封装请求参数或返回参数,注意实体类和JSON字符串之间的转换
//请求参数实体类 public class LoginReq { private String openid;//openid public LoginReq() { } //省略geter和setter方法 } //返回参数实体父类 package com.hs.api.common; public class TestResponseEntity { public int code; public String message; public TestResponseEntity() { } //省略geter和setter方法 } //登录接口返回参数实体类(两个,相互嵌套关系) public class TestShopLoginRes extends TestResponseEntity { private LoginRes data; public TestShopLoginRes() { } public TestShopLoginRes(LoginRes data) { this.data = data; } //省略geter和setter方法 @Override public String toString() { return "TestShopLoginRes{" + "data=" + data + '}'; } } public class LoginRes { private Long shopId;// private int mobileExistFlag;//手机号是否存在 private String shopToken;// private int vipFlag;//是否是会员 public LoginRes() { } //省略geter和setter方法 @Override public String toString() { return "LoginRes{" + "shopId=" + shopId + ", mobileExistFlag=" + mobileExistFlag + ", shopToken='" + shopToken + '\'' + ", vipFlag=" + vipFlag + '}'; } }
以下是实例中测试的实际接口类
//登录接口类 @RestController @RequestMapping(value="/api/shop/login") public class ApiShopLoginController extends ApiShopBaseController { //1-登陆或注册 @RequestMapping(value="",method = RequestMethod.POST,produces = {JSON_UTF8}) public String loginOrRegister(@RequestBody LoginReq loginReqt){ //省略业务 } } //用户接口类 @RestController @RequestMapping(value="/api/shop/member") public class ApiShopMemberController extends ApiShopBaseController { //1-店主基本信息 @RequestMapping(value = { "/detail" }, method={RequestMethod.GET},produces = {JSON_UTF8}) public String getMemberShopInfo() throws ServerSqlErrorException { //省略业务 } }
参考文献:
1-JUnitGenerator使用方法:http://www.pianshen.com/article/516640707/
2-测试用例整合:https://blog.csdn.net/weixin_39800144/article/details/79241620
3-返回参数处理:https://blog.csdn.net/qq_16513911/article/details/83018027