为了方便开发,对数据进行mock处理,形成文件,只修改文件内容达到mock指定数据的目的
1、定义测试模式
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface TestModel {
TestModelEnum value() default TestModelEnum.LOCAL_PRIOR;
String[] mockMethodNames() default {};
}
public enum TestModelEnum {
/**
* 读取本地数据,若无则抛异常
*/
LOCAL,
REMOTE,
/**
* 本地优先,若本地无mock数据则调用远端并保存到本地
*/
LOCAL_PRIOR,
}
注入拦截器
public class TestBean {
public static final String mockMethods = "mockMethods";
// feign拦截器,为了mock feign返回值
@Bean
public Feign.Builder feignBuilder() {
return Feign.builder().invocationHandlerFactory((target, dispatch) -> new FeignResultInvocationHandler(target, dispatch));
}
// sql拦截器,为了mock 查询sql返回值
@Bean
public SqlResultInterceptor sqlResultInterceptor() {
return new SqlResultInterceptor();
}
}
启动测试父类
StartApplication 为springboot的启动类,可自定义测试启动类也可直接复用项目启动类
@SpringBootTest(classes = {StartApplication.class, TestBean.class})
public abstract class AbstractBasicTest {
public final Logger logger = LoggerFactory.getLogger(getClass());
// 方法启动时初始化执行方法,可指定db等
@Before
public void before() {
logger.info("before ==================================init datasource====");
}
// 方法结束时执行方法,清除变量
@After
public void tearDown() {
// 清理环境变量,恢复测试环境
System.clearProperty("classPath");
System.clearProperty("classMethodName");
System.clearProperty("className");
System.clearProperty("testModel");
System.clearProperty(TestBean.mockMethods);
}
// 每个测试方法执行前初始化方法,赋值变量值
@BeforeEach
public void initService(TestInfo testInfo) throws Exception {
Method method = testInfo.getTestMethod().get();
String methodName = method.getName();
String className = testInfo.getTestClass().get().getName();
logger.info("initService before each ============> current className:{},methodName:{}", className, methodName);
String classPath = TestFileHelper.getClassPath(className);
System.setProperty("classPath", classPath);
System.setProperty("className", className);
System.setProperty("classMethodName", methodName);
TestModel annotation = method.getAnnotation(TestModel.class);
if (annotation != null) {
TestModelEnum value = Optional.ofNullable(annotation.value()).orElse(TestModelEnum.LOCAL_PRIOR);
System.setProperty("testModel", value.name());
String[] mockInterfaces = Optional.ofNullable(annotation.mockMethodNames()).orElse(new String[0]);
System.setProperty(TestBean.mockMethods, String.join(",", mockInterfaces));
}
}
protected String compareAndWrite(Object obj, boolean isCover){
Pair<String, String> pair = getFile(obj);
String filePath = pair.getKey();
String content = pair.getRight();
boolean exist = TestFileHelper.isExist(filePath);
String oldResult = "";
if (exist) {
oldResult = TestFileHelper.readFile(filePath);
}
boolean isEqual = content.equals(oldResult);
logger.info("=====> compare result isEqual : {} : \n oldResult:{} \n newResult:{} \n ", isEqual, oldResult, content);
if (isCover) {
TestFileHelper.writeFileObj(obj, filePath);
}
Assertions.assertEquals(content, oldResult, "result is not equal");
return content;
}
protected Pair<String, String> getFile(Object obj) {
String content = TestFileHelper.toJSONString(obj);
String path = System.getProperty("classPath");
String methodName = System.getProperty("classMethodName");
String fileName = "result.json";
String filePath = Paths.get(path, methodName, fileName).toString();
return Pair.of(filePath, content);
}
}
工具类
public class TestFileHelper {
public static final Logger logger = LoggerFactory.getLogger(TestFileHelper.class);
private static Map<String, Integer> sameMethodCountMap = Maps.newConcurrentMap();
private static String getNumSuffix(String methodName){
String className = System.getProperty("className");
String classMethodName = System.getProperty("classMethodName");
// 测试类名 + 测试方法名 + 执行类名_执行方法名
String key = className + classMethodName + methodName;
Integer integer = 0;
try {
integer = sameMethodCountMap.getOrDefault(key, 0);
if (integer == 0) {
return "";
}
} finally {
sameMethodCountMap.put(key, integer + 1);
}
return integer == 0 ? "" : String.valueOf(integer);
}
protected static String getFilePath(String methodName) {
String suffix = getNumSuffix(methodName);
String path = System.getProperty("classPath");
String classMethodName = System.getProperty("classMethodName");
String fileName = methodName.substring(methodName.lastIndexOf(".") + 1) + suffix + ".json";
return Paths.get(path, classMethodName, fileName).toString();
}
public static Object getResult(String methodName, Function mockFunction, Function remoteFunction) throws Throwable{
boolean isMock = false;
String testModel = System.getProperty("testModel");
String filePath = getFilePath(methodName);
Object result = null;
try {
if (TestModelEnum.LOCAL.name().equals(testModel)) {
boolean exist = TestFileHelper.isExist(filePath);
if (!exist) {
throw new RuntimeException(String.format("mock 文件不存在,文件名=【%s】", methodName));
}
isMock = true;
result = mockFunction.apply(filePath);
} else if (TestModelEnum.REMOTE.name().equals(testModel)) {
String mockInterfaces = System.getProperty(TestBean.mockMethods);
if (StringUtils.isNotBlank(mockInterfaces) && mockInterfaces.toLowerCase().contains(methodName.toLowerCase()) && TestFileHelper.isExist(filePath)) {
isMock = true;
result = mockFunction.apply(filePath);
} else {
result = remoteFunction.apply(filePath);
}
} else {
boolean exist = TestFileHelper.isExist(filePath);
if (exist) {
isMock = true;
result = mockFunction.apply(filePath);
} else {
result = remoteFunction.apply(filePath);
TestFileHelper.writeFileObj(result, filePath);
}
}
}catch (Exception e) {
e.printStackTrace();
throw e;
} finally {
logger.info(String.format("%s [%s] %s result is : %s",
isMock ? "MOCK" : "REMOTE",
testModel, methodName, JSONObject.toJSONString(result)));
}
return result;
}
public static String getClassPath(String className) {
String classPath = getTargetResourcePath();
String pathPrefix = classPath.substring(0, classPath.indexOf("/target"));
String substring = className.substring(0, className.lastIndexOf("."));
String packageName = substring.replaceAll("\\.", "/");
Path path = Paths.get(pathPrefix, "/src/test/java", packageName);
return path.toString();
}
public static String getTargetResourcePath(){
String classPath = ClassLoader.getSystemResource("").getPath();
if (classPath.startsWith("/")) {
classPath = classPath.substring(1);
}
return classPath;
}
public static boolean isExist(String fileName){
File file = new File(fileName);
return file.exists();
}
public static String readFile(String fileName) {
try {
String content = FileUtils.readFileToString(new File(fileName));
return content;
} catch (Exception e) {
logger.error("readFile error", e);
}
return null;
}
public static boolean writeFileObj(Object obj, String fileName) {
try {
String content = toJSONString(obj);
Path path = Paths.get(fileName);
if (!Files.exists(path.getParent())) {
Files.createDirectories(path.getParent());
}
FileUtils.writeStringToFile(new File(fileName), content, "UTF-8");
return true;
} catch (Exception e) {
logger.error("writeFile error", e);
}
return false;
}
public static String toJSONString(Object obj){
return JSONObject.toJSONString(obj,
SerializerFeature.PrettyFormat,
// SerializerFeature.WriteMapNullValue,
SerializerFeature.WriteDateUseDateFormat);
}
}