问题发现
写完逻辑需要跑一个单元测试,来测试数据的结果,每次启动测试方法都先加载上下文(相当于启动项目)然后才定位到对应的测试方法去执行,其中加载上下文非常耗时,每次运行一个测试方法,就要花费4到5分钟的时间,非常需要优化一下。
单元测试运行原理
- 引入
maven 依赖,在对应的测试方法上标注@Test 注解 @RunWith 说明
@RunWith 就是一个运行器 @RunWith(JUnit4.class) 就是指用JUnit4来运行 @RunWith(SpringJUnit4ClassRunner.class),让测试运行于Spring测试环境 @RunWith(Suite.class) 的话就是一套测试集合, @ContextConfiguration Spring整合JUnit4测试时,使用注解引入多个配置文件@RunWith
SpringBoot环境下单元测试一般是加@RunWith(SpringJUnit4ClassRunner.class)注解,SpringJUnit4ClassRunner继承BlockJUnit4ClassRunner类, 然后在测试方式时会执行SpringJUnit4ClassRunner类的run方法(重写了BlockJUnit4ClassRunner的run方法), run方法主要是初始化spring环境数据,与执行测试方法
优化思路
- 首先,先启动上下文,一次启动,每次运行测试方法都用启动好的那个上下文的数据
- 其次,自定义执行器,继承:
BlockJUnit4ClassRunner - 暴露接口,处理测试方法,post提交,提交测试方法与测试类,使用Spring的bean注入,将测试类作为bean注入到容器中,反射执行测试方法
实现细节
自定义执行器
class SpringDelegateRunner(private val testClass: Class<*>?) : BlockJUnit4ClassRunner(testClass) {
private val mapper = createObjectMapper()
override fun runChild(method: FrameworkMethod?, notifier: RunNotifier?) {
val description = describeChild(method)
if (isIgnored(method)) {
notifier!!.fireTestIgnored(description)
return
}
if (method == null || notifier == null) {
return
}
val invokeRequest = InvokeRequest()
invokeRequest.testClass = method.declaringClass
invokeRequest.methodName = method.name
try {
notifier.fireTestStarted(description)
val json = mapper.writeValueAsString(invokeRequest)
val resultBody = HttpRequest.post("http://127.0.0.1:9210/do1/junit/test")
.body(json)
.execute().body()
if (StringUtils.isBlank(resultBody)) {
notifier.fireTestFailure(Failure(description, RuntimeException("远程执行失败!")))
}
val invokeResult = mapper.readValue(resultBody, InvokeResult::class.java)
val success = invokeResult.success
if (success) {
notifier.fireTestFinished(description)
} else {
val exception = invokeResult.exception
if (exception?.assertionError != null) {
notifier.fireTestFailure(Failure(description, exception.assertionError))
} else {
notifier.fireTestFailure(Failure(description, RuntimeException("执行失败!!!!!!")))
}
}
} catch (e: Exception) {
notifier.fireTestFailure(Failure(description, e))
}
// super.runChild(method, notifier)
}
}
暴露接口,接收执行器的请求
@Controller
@RequestMapping("do1/junit")
class TestController {
private val mapper = createObjectMapper()
@Autowired
private lateinit var applicationContext: ApplicationContext
private val log: Logger = LoggerFactory.getLogger(this::class.java)
private val methodMap: ConcurrentHashMap<String, Method> = ConcurrentHashMap<String, Method>()
@PostMapping("/test")
fun test(request: HttpServletRequest, response: HttpServletResponse) {
val contentLength = request.contentLength
val inputStream: ServletInputStream
var buffer: ByteArray? = null
try {
inputStream = request.inputStream
buffer = ByteArray(contentLength)
inputStream.read(buffer, 0, contentLength)
inputStream.close()
} catch (e: IOException) {
e.printStackTrace()
}
try {
val invokeRequest: InvokeRequest = mapper.readValue(buffer, InvokeRequest::class.java)
val execute: InvokeResult = execute(invokeRequest)
val result: String = mapper.writeValueAsString(execute)
log.info("===================$result")
response.setHeader("Content-Type", "application/json")
response.writer.write(result)
response.writer.close()
} catch (e: Exception) {
e.printStackTrace()
}
}
private fun registerBeanOfType(type: Class<*>) {
val beanDefinition: BeanDefinition = GenericBeanDefinition()
beanDefinition.beanClassName = type.name
((applicationContext as GenericApplicationContext).beanFactory as DefaultListableBeanFactory)
.registerBeanDefinition(type.name, beanDefinition)
}
private fun getMethod(clazz: Class<*>, methodName: String): Method? {
val key = clazz.canonicalName + ":" + methodName
var md: Method? = null
if (methodMap.containsKey(key)) {
md = methodMap[key]
} else {
val methods = clazz.methods
for (mth in methods) {
if (mth.name == methodName) {
methodMap.putIfAbsent(key, mth)
md = mth
break
}
}
}
return md
}
private fun execute(invokeRequest: InvokeRequest): InvokeResult {
val testClass: Class<*> = invokeRequest.testClass?: return InvokeResult()
val bean = try {
applicationContext.getBean(testClass.name)
} catch (e: java.lang.Exception) {
registerBeanOfType(testClass)
applicationContext.getBean(testClass.name)
}
val invokeResult = InvokeResult()
val method = getMethod(testClass, invokeRequest.methodName!!)
try {
val start = System.currentTimeMillis()
log.info("开始调用测试方法:")
method!!.invoke(bean)
log.info("调用测试方法结束, 耗时: ${(System.currentTimeMillis() - start) / 1000} 秒")
invokeResult.success = true
} catch (e: IllegalAccessException) {
if (e !is InvocationTargetException
|| (e as InvocationTargetException).targetException !is AssertionError
) {
log.error("fail to invoke code, cause: {}", e)
}
invokeResult.success = false
val invokeFailedException = InvokeFailedException()
invokeFailedException.message = e.message.toString()
invokeFailedException.stackTrace = e.stackTrace
// 由Assert抛出来的错误
if (e.cause is AssertionError) {
invokeFailedException.assertionError = e.cause as AssertionError?
}
invokeResult.exception = invokeFailedException
} catch (e: IllegalArgumentException) {
if (e !is InvocationTargetException
|| (e as InvocationTargetException).targetException !is AssertionError
) {
log.error("fail to invoke code, cause: {}", e)
}
invokeResult.success = false
val invokeFailedException = InvokeFailedException()
invokeFailedException.message = e.message.toString()
invokeFailedException.stackTrace = e.stackTrace
if (e.cause is AssertionError) {
invokeFailedException.assertionError = e.cause as AssertionError?
}
invokeResult.exception = invokeFailedException
} catch (e: InvocationTargetException) {
if (e !is InvocationTargetException
|| e.targetException !is AssertionError
) {
log.error("fail to invoke code, cause: {}", e)
}
invokeResult.success = false
val invokeFailedException = InvokeFailedException()
invokeFailedException.message = e.message.toString()
invokeFailedException.stackTrace = e.stackTrace
if (e.cause is AssertionError) {
invokeFailedException.assertionError = e.cause as AssertionError?
}
invokeResult.exception = invokeFailedException
} catch (e: java.lang.Exception) {
log.error("fail to invoke code, cause: {}", e)
invokeResult.success = false
val invokeFailedException = InvokeFailedException()
invokeFailedException.message = e.message.toString()
invokeFailedException.stackTrace = e.stackTrace
}
return invokeResult
}
}
思路来源自:https://blog.csdn.net/qq_37803406/article/details/114778041
|