一、背景描述
在版本開發中,時間段大致的劃分為:需求,開發,測試;
- 需求階段:理解需求做好介面設計;
- 開發階段:完成功能開發和對接;
- 測試上線:自測,提測,修復,上線;
實際上開發階段兩個核心的工作,開發和流程自測,自測的根本目的是為自己提前解決可能出現的問題;如果缺少自測和提測兩個關鍵步驟,那麼問題就會被傳遞給更多的使用者,產生更多的資源消耗;
自測是於開發而言,提測是對專業的測試人員而言,如果儘可能在自測階段就發現問題,並解決問題,那麼一個問題就不會影響到團隊協作上的更多人員,如果一個簡單的問題上升到團隊協作層面,很可能會導致問題本身被放大。
工欲善其事必先利其器,開發如果要做好自測流程,學會使用工具提高效率是十分關鍵的,自測的關鍵在於發現問題和解決問題,所以選擇好用和高效的工具可以極大的降低自測的時間消耗。
下面圍繞幾個自己開發過程中常用的測試工具和手段,做簡單的總結,不在於對比方式的好壞,存在即合理,在不同場景中對合理手段的選擇,快速解決問題才是根本目的。
二、PostMan工具
PostMan很常用的介面測試工具,開發過程中快速測試介面,功能強大並且簡單方便,不但可以單個介面測試,也可以對介面分塊管理批量執行:
整體來說工具比較好用,適應於開發階段的介面快速測試,或者在解決問題的過程中單個介面的測試,同時對測試引數有儲存和記憶能力,這也是受歡迎的一大原因。
但是該工具不適應於複雜的流程化測試,例如需要根據上次介面的響應報文做分別處理,或者下次請求需要填充某個介面響應的資料。
三、Swagger文件
Swagger管理介面文件,是當下服務中很常用的元件,通過對介面和物件的簡單註釋,快速生成介面描述資訊,並且可以對介面傳送請求,協助除錯,該文件在前後端聯調中極大的提高效率。
介面文件的管理本身是一件麻煩事,介面通常會根據業務不斷的調整,如果單獨維護一份介面文件,需要付出很多時間成本,並且容易出問題,利用swagger就可以避免這個問題。
藉助swagger註解標記物件
@TableName("jt_activity")
@ApiModel(value="活動PO物件", description="活動資訊表【jt_activity】")
public class Activity {
@ApiModelProperty(value = "主鍵ID")
@TableId(type = IdType.AUTO)
private Integer id;
@ApiModelProperty(value = "活動主題")
private String activityTitle;
@ApiModelProperty(value = "聯絡號碼")
private String contactPhone;
@ApiModelProperty(value = "1線上、2線下")
private Integer isOnline;
@ApiModelProperty(value = "舉辦地址")
private String address;
@ApiModelProperty(value = "主辦單位")
private String organizer;
@ApiModelProperty(value = "建立時間")
private Date createTime;
}
藉助swagger註解標記介面
@Api(tags = "活動主體介面")
@RestController
public class ActivityWeb {
@Resource
private ActivityService activityService ;
@ApiOperation("新增活動")
@PostMapping("/activity")
public Integer save (@RequestBody Activity activity){
activityService.save(activity) ;
return activity.getId() ;
}
@ApiOperation("主鍵查詢")
@GetMapping("/activity/{id}")
public Activity getById (@PathVariable("id") Integer id){
return activityService.getById(id) ;
}
@ApiOperation("修改活動")
@PutMapping("/activity")
public Boolean updateById (@RequestBody Activity activity){
return activityService.updateById(activity) ;
}
}
通常來說,基於swagger註解標記介面類和方法上的入參和關鍵返參物件即可,這樣可以避免再單獨維護介面文件。
Swagger介面文件在開發的過程中更多是扮演文件的角色,真正使用swagger去除錯的介面也常是一些增刪改查的簡單介面,這個工具也同樣不適應於複雜流程的測試。
四、TestRestTemplate類
SpringBoot測試包中整合的測試API,需要依賴測試包,可以訪問控制層介面,非常方便的完成互動過程:
Jar包依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
使用案例
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class ActivityTest01 {
protected static Logger logger = LoggerFactory.getLogger(ActivityTest01.class) ;
@Resource
private TestRestTemplate restTemplate;
private Activity activity = null ;
@Before
public void before (){
activity = restTemplate.getForObject("/activity/{id}", Activity.class,1);
logger.info("\n"+JSONUtil.toJsonPrettyStr(activity));
}
@Test
public void updateById (){
if (activity != null){
activity.setCreateTime(new Date());
activity.setOrganizer("One商家");
restTemplate.put("/activity",activity);
}
}
@After
public void after (){
activity = restTemplate.getForObject("/activity/{id}", Activity.class,1);
logger.info("\n"+JSONUtil.toJsonPrettyStr(activity));
activity = null ;
}
}
在TestRestTemplate原始碼中可以發現,基於RestTemplate做封裝,很多功能的實現都是呼叫RestTemplate方法。
用寫程式碼的方式去實現介面測試,靈活度非常高,可以根據流程做定製開發,很適應於中等複雜的場景測試,這裡為什麼這樣描述,下面對比Http請求再細說。
五、Http請求模式
通過模擬介面的Http請求實現的方式,目前來說個人感覺靈活的最高的方式,先看簡單的案例:
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
public class ActivityTest03 {
protected static Logger logger = LoggerFactory.getLogger(ActivityTest03.class) ;
protected static String REQ_URL = "服務地址+埠";
@Test
public void testHttp (){
// 查詢
String getRes = HttpUtil.get(REQ_URL+"activity/1");
logger.info("\n {} ",JSONUtil.toJsonPrettyStr(getRes));
Activity activity = JSONUtil.toBean(getRes, Activity.class) ;
// 新增
activity.setId(null);
activity.setOrganizer("Http商家");
String saveRes = HttpUtil.post(REQ_URL+"/activity",JSONUtil.toJsonStr(activity));
logger.info("\n {} ",saveRes);
// 更新
activity.setId(Integer.parseInt(saveRes));
activity.setOrganizer("Put商家");
String putRes = HttpRequest.put(REQ_URL+"/activity")
.body(JSONUtil.toJsonStr(activity)).execute().body();
logger.info("\n {} ",putRes);
}
}
這種方式對於複雜的業務流程來說非常好用,當然這裡不排除個人習慣,在測試複雜流程的時候,一個簡單方案:
- 使用者資訊:模擬http中token資料;
- 業務流程:通過資料獲取包裝引數模型;
- 獨立服務管理,模擬併發場景;
- 根據執行過程生成分析資料結果;
對於複雜業務流程的測試,每個節點的模擬都具有一定的難度,通常在完整的流程中涉及到的服務和庫表都是多個,並且請求鏈路複雜,基於一個靈活的自動化流程,去測試完整的鏈路,可以對效率有極大的提升。
六、Service層測試
針對服務層的測試手段,其本意在於業務實現的邏輯測試:
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
public class ActivityTest04 {
protected static Logger logger = LoggerFactory.getLogger(ActivityTest04.class) ;
@Autowired
private ActivityService activityService ;
@Test
public void testService (){
// 查詢
Activity activity = activityService.getById(1) ;
// 新增
activity.setId(null);
activityService.save(activity) ;
// 修改
activity.setOrganizer("Ser商家");
activityService.updateById(activity) ;
// 刪除
activityService.removeById(activity.getId()) ;
}
}
該測試在實際的開發過程也並不常用,偶爾在於某個業務方法實現難度很大,用來針對性測試。
七、MockMvc方式
MockMvc同樣是SpringBoot整合測試包提供的測試方式,通過物件的模擬,驗證介面是否符合預期:
@AutoConfigureMockMvc
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
public class ActivityTest02 {
protected static Logger logger = LoggerFactory.getLogger(ActivityTest02.class) ;
@Resource
private MockMvc mockMvc ;
private Activity activity = null ;
@Before
public void before () throws Exception {
ResultActions resultAction = mockMvc.perform(MockMvcRequestBuilders.get("/activity/{id}",1)) ;
MvcResult mvcResult = resultAction.andReturn() ;
String result = mvcResult.getResponse().getContentAsString();
activity = JSONUtil.toBean(result,Activity.class) ;
}
@Test
public void updateById () throws Exception {
activity.setId(null);
activity.setCreateTime(new Date());
activity.setOrganizer("One商家");
ResultActions resultAction = mockMvc.perform(MockMvcRequestBuilders.post("/activity")
.contentType(MediaType.APPLICATION_JSON)
.content(JSONUtil.toJsonStr(activity))) ;
MvcResult mvcResult = resultAction.andReturn() ;
String result = mvcResult.getResponse().getContentAsString();
activity.setId(Integer.parseInt(result));
logger.info("result : {} ",result);
}
@After
public void after () throws Exception {
activity.setCreateTime(new Date());
activity.setOrganizer("Update商家");
ResultActions resultAction = mockMvc.perform(MockMvcRequestBuilders.put("/activity")
.contentType(MediaType.APPLICATION_JSON)
.content(JSONUtil.toJsonStr(activity))) ;
MvcResult mvcResult = resultAction.andReturn() ;
String result = mvcResult.getResponse().getContentAsString();
logger.info("result : {} ",result);
}
}
對於這種Mock型別的測試,非常專業,通常個人使用極少,暫時沒有Get到其精髓思想。
八、Mockito測試
Mock屬於非常專業和標準的測試手段,需要依賴powermock包:
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito2</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<scope>test</scope>
</dependency>
簡單使用案例:
@RunWith(PowerMockRunner.class)
@SpringBootTest
public class ActivityTest05 {
@Test
public void testMock (){
Set mockSet = PowerMockito.mock(Set.class);
PowerMockito.when(mockSet.size()).thenReturn(10);
int actual = mockSet.size();
int expected = 15 ;
Assert.assertEquals("返回值不符合預期",expected, actual);
}
@Test
public void testTitle (){
String expectTitle = "Mock主題" ;
Activity activity = PowerMockito.mock(Activity.class);
PowerMockito.when(activity.getMockTitle()).thenReturn(expectTitle);
String actualTitle = activity.getMockTitle();
Assert.assertNotEquals("主題相符", expectTitle, actualTitle);
}
}
可以通過Mock方式,快速模擬出複雜的物件結構,以便構建測試方法,由於使用很少,同樣個人暫時沒Get到點。
九、原始碼地址
GitHub·地址
https://github.com/cicadasmile/middle-ware-parent
GitEE·地址
https://gitee.com/cicadasmile/middle-ware-parent
閱讀標籤
【Java基礎】【設計模式】【結構與演算法】【Linux系統】【資料庫】
【分散式架構】【微服務】【大資料元件】【SpringBoot進階】【Spring&Boot基礎】