背景
在生產上線時,可能遇到有一些case不好立即驗證;
- 例如用戶必須是xx用戶(新用戶,流失用戶...)才能領到某些活動券,而這樣的用戶賬號不好獲取;
- 例如想讓測試用戶看到不同的頁面效果;
所以希望在調用一些方法接口的時候針對指定入參可以返回指定的返回結果。
這些方法可以是調用上游的dubbo方法,也可以是內部自己的本地方法等。
方案設計
入參出參分析
首先來看方法的參數和返回結果的類型,以及入參和出參組裝分類;
入參出參數據類型分類
- 基本類型或者包裝類型:int, double, Integer,String,Boolean...
- 集合類型:List,Map<K, V>...
- 復雜類型:xxxRequest,xxxResponse
入參出參組裝分類
入參:
-
無入參
public int fun();
-
一個入參(可能是三種類型的其中一種)
-
public int fun(String s);
-
public int fun(List list);
-
public int fun(xxxRequest request);
-
-
多個入參(可能是三種類型都有)
- public int fun(String s, List list, xxxRequest request);
出參:
-
一個出參(可能是三種類型的其中一種)
-
public Integer fun();
-
public List fun();
-
public xxxResponse fun();
-
-
無出參(不考慮)
mock配置方式
針對上面對方法入參和出參的分析,可以確定我們需要實現mock的場景和配置方式。
入參:match配置內容則返回mock結果
-
方法沒有入參,無需配置對比;
-
方法入參類型為基本類型或集合類型(Integer,List);序列化后整體做對比
-
方法入參類型為復雜類型(xxxRequest);可選取部分字段對比
需要注意List也需要序列化后整體做對比
出參:
-
想要構造整個出參的結果;
-
想要從真實調用中修改結果的某幾個字段的值;
其中有些字段嵌套的比較深,可利用"a.b.c"的配置方式來修改,可參考下文的配置;
mock流程
從流程上可以看出,為了不影響到接口調用,流程中做了嚴格的校驗處理,一旦報錯或者配置信息有誤都要真實調用並返回結果。
代碼實現
完整代碼見github:github.com/XDcherish/j…
1.引入切面與注解
引入切面:
@Bean
public MethodMockAspect methodMockAspect() {
return new MethodMockAspect();
}
切面的掃描范圍是添加了@MethodMock的類,這樣類中的方法在調用過程中會被切面攔截 。
@MethodMock
public class TestMockClass {
public Boolean testSimpleWithoutInput() {
System.out.println("真實執行啦:");
return true;
}
}
2.增加mock配置
可參考com.xh.utils.mock.dto.MethodMockDTO中的注釋進行配置,各字段有詳細解釋;
mockRequestDTOsList和mockResponseDTOS 都是List表示支持同一個方法的多組入參和出參匹配。
例如下面的配置:
{
"com.xh.utils.mock.test.TestMockClass#testSimpleWithoutInput": { //簡單無入參的配置
"openMock": true, //是否開啟配置
"mockResponseDTOS": [ //只需要配置response即可,雖然是數組,但只能配置一個
{
"mockType": 1, //"mockType" 為1表示自己構造返回,不填默認為修改類型的出參,效果和"mockType": 0一致
"responseContent": false
}
]
},
"com.xh.utils.mock.test.TestMockClass#testSimpleInputOutput": {
"openMock": true,
"mockRequestDTOsList": [ //數組下標為1的入參配置對應下標為1的出參配置
[
{
"requestType": 1, //requestType為1表示基本類型或者集合類型的入參,requestType不填默認為復雜類型的入參,效果和requestType為0一致
"requestCompareContent": "mock"
}
]
],
"mockResponseDTOS": [
{
"mockType": 1,
"responseContent": true
}
]
},
"com.xh.utils.mock.test.TestMockClass#testComplexInputOutput": { //復雜類型的配置
"openMock": true,
"mockRequestDTOsList": [
[
{
"requestType": 1,
"requestCompareContent": "1"
},
{
"requestCompareContent": { //選擇其中的id和inputDTO兩個字段進行對比即可
"id": 1,
"inputDTO":{
"subId": 101
}
}
}
],
[
{
"requestType": 1,
"requestCompareContent": "2"
},
{
"requestCompareContent": {
"id": 2,
"inputDTO": {
"subId": 201
}
}
}
]
],
"mockResponseDTOS": [
{
"responseContent": { //只想修改真實返回結果中的resName 和 outputDTOS
"resName": "modifyName",
"outputDTOS": "[{\"resSubId\":21},{\"resSubId\":22}]",
"outputDTO.resSubName": "修改嵌套較深的字段"
}
},
{
"mockType": 1,
"responseContent": { //構造整體的返回結果
"resId": 1,
"resName": "mockName",
"outputDTOS": [
{
"resSubId": 11
},
{
"resSubId": 12
}
]
}
}
]
}
}
下文會有單測進行補充說明
3.解析mock配置
解析配置內容可靈活實現,這里為了方便直接在項目的resources目錄下存放config.json,然后直接解析如下代碼,實際上可以用一些配置框架實現線上和測試環境配置不同,實時切換,例如接入攜程開源的appllo(分布式配置中心)。
//解析config.json的代碼
private Map<String, MethodMockDTO> getConfigContent() throws IOException {
String path = "/config.json";
InputStream config = MethodMockAspect.class.getResourceAsStream(path);
if (config != null) {
Map<String, MethodMockDTO> configMap = objectMapper.readValue(config, new TypeReference<Map<String, MethodMockDTO>>() {});
System.out.println(JsonUtils.toJson(configMap));
return configMap;
}
return null;
}
4.代碼測試
com.xh.utils.mock.test.TestMockClass 中給了幾個方法並寫了單測;
@Service
@MethodMock
public class TestMockClass {
public Boolean testSimpleWithoutInput() {
System.out.println("真實執行啦:");
return true;
}
public Boolean testSimpleInputOutput(String input) {
System.out.println("真實執行啦:" + input);
return false;
}
public MockTestResponse testComplexInputOutput(String requestFirst, MockTestRequest requestSecond) {
MockTestResponse response = new MockTestResponse();
response.setResId(9L);
response.setResName("真實調用");
response.setOutputDTOS(new ArrayList<>());
MockTestOutputDTO outputDTO = new MockTestOutputDTO();
outputDTO.setResSubId(901L);
outputDTO.setResSubName("真實調用subName");
response.setOutputDTO(outputDTO);
System.out.println("真實執行啦:" + JsonUtils.toJson(response));
return response;
}
}
單測內容:
@SpringBootTest
class MockApplicationTests {
@Autowired
private TestMockClass mockClass;
@Test
void testSimpleInputOutput() {
System.out.println(mockClass.testSimpleInputOutput("mock"));
}
@Test
void testSimpleWithoutInput() {
System.out.println(mockClass.testSimpleWithoutInput());
}
@Test
void testComplexInputOutput() {
// mockClass.testSimpleInputOutput("mock");
// mockClass.testSimpleWithoutInput();
MockTestRequest request = new MockTestRequest();
request.setId(1L);
MockTestInputDTO inputDTO = new MockTestInputDTO();
inputDTO.setSubId(101L);
request.setInputDTO(inputDTO);
MockTestResponse response = mockClass.testComplexInputOutput("1", request);
System.out.println("最后的結果:" + JsonUtils.toJson(response));
}
}
- 對於testSimpleWithoutInput(),根據配置可看到固定返回mock的結果false不會走到具體方法中去。
- 對於testSimpleInputOutput(String input),根據配置可看到當輸入內容是"mock"時,會返回true。
- 對於testComplexInputOutput(String requestFirst, MockTestRequest requestSecond),根據配置可知有兩組配置結果,以第一組為例,當requestFirst等於"1",requestSecond中選擇id 和 inputDTO進行對比,當id等於1L,inputDTO序列化結果為{"subId": 101} 時,會真實調用方法,然后取到結果將resName修改為"modifyName",outputDTOS修改為"[{"resSubId":21},{"resSubId":22}]", outputDTO的resSubName字段修改為"修改嵌套較深的字段"。
總結
本文並沒有對代碼實現進行詳細的說明,因為其實並不復雜,暫時也沒有時間。其中構造返回結果的時候有遇到一些泛型擦除的問題,后續可以補充一下,可能也會有一些地方存在一些bug沒被發現,希望發現bug的同學能評論下,萬分感激。還有就是其實可以支持更復雜的mock場景,如果讀者覺得有必要也可以自行進行擴展,后續如果有必要也會再對文章進行補充。
小編本着雷鋒精神在此分享一份36W字的面試寶典,內容涵蓋:基礎&進階篇字符串&集合面試題匯總、.Java並發編程、JVM、數據結構與算法、網絡協議、數據庫、MySQL、52條SQL性能優化策略、一千行SQL命令、Redis、MongoDB、Spring(共485頁,36W字)
Java基礎篇
字符串&&集合篇
如果你覺得這些內容對你有幫助,可以加入csdn進階交流群:714827309,領取資料
並發編程
JVM
數據結構與算法
網絡協議
如果你覺得這些內容對你有幫助,可以加入csdn進階交流群:714827309,領取資料
MySQL
Redis
Mongo
Spring
MyBatis
SpringBoot
常用注解
由於內容過多就不一一展示了,面試寶典內容還涵蓋了:MyBatis、SpringBoot、Spring & SpringBoot常用注解、微服務、Dubbo、Nginx、Zookeeper、MQ、kafka、Elasticsearch、Linux面試專題。
如果你覺得這些內容對你有幫助,可以加入csdn進階交流群:714827309,領取資料
近日,經過一朋友的透露,Alibaba也首發了一份限量的“Java成長手冊”,里面記載的知識點非常齊全,看完之后才知道,差距真的不止一點點!
手冊主要是將Java程序員按照年限來進行分層,清晰的標注着Java程序員應該按照怎樣的路線來提升自己,需要去學習哪些技術點。
0-1年入門:
- Java基礎復盤 (面向對象+Java的超類+Java的反射機制+異常處理+集合+泛型+基礎IO操作+多線程+網絡編程+JDK新特性)
- Web編程初探 (Servlet+MySQL數據庫+商品管理系統實戰)
- SSM從入門到精通 (Spring+SpringMVC+Mybatis+商品管理系統實戰-SSM版)
- SpringBoot快速上手 (SpringBoot+基於SpringBoot的商品管理系統實戰)
- 零距離互聯網項目實戰 (Linux+Redis+雙十一秒殺實戰系統)
1-3年高工:
- 並發編程進階 (並發工具類實戰+CAS+顯示鎖解析+線程池內部機制+性能優化)
- JVM深度剖析 (理解運行時數據區+堆外內存解讀+JDK+內存泄漏問題排查+Arthas+GC算法和垃圾回收器+類加載機制)
- MySQL深度進階
- 深入Tomcat底層 (線程模型+性能調優)
3-5年資深:
- 數據庫(調優+事務+鎖+集群+主從+緩存等)
- Linux(命令+生產環境+日志等)
- 中間件&分布式 (dubbo+MQ/kafka、ElasticSearch、SpringCloud等組件)
5-7年架構:
- 開源框架 (Spring5源碼+SpringMVC源碼+Mybatis源碼)
- 分布式架構 (Zk實戰+RabbitMQ+RocketMQ+Kafka)
- 高效存儲 (Redis+mongoDB+MySQL高可用+Mycat+Sharing-Sphere)
- 微服務架構(RPC+SpringBoot+SpringCloud+Netflix+SpringCloudAlibaba+docker+k8s)
注:含答案 ! 篇幅有限,已整理到網盤 ,添加助理微信,免費獲取。
如果你覺得這些內容對你有幫助,可以加入csdn進階交流群:714827309,領取資料
基礎篇
JVM 篇
由於篇幅限制,詳解資料太全面,細節內容太多,所以只把部分知識點截圖出來粗略的介紹,每個小節點里面都有更細化的內容!
如果你覺得這些內容對你有幫助,可以加入csdn進階交流群:714827309,領取資料
MySQL 篇
Redis 篇
由於篇幅限制,詳解資料太全面,細節內容太多,所以只把部分知識點截圖出來粗略的介紹,每個小節點里面都有更細化的內容!
如果你覺得這些內容對你有幫助,可以加入csdn進階交流群:714827309,領取資料