转自 原创 方圆(铭楚) 阿里开发者 2022-09-26 09:00 发表于北京 前言 单元测试是软件开发过程中的重要一环,好的单测可以帮助我们更早的发现问题,为系统的稳定运行提供保障。单测还是很好的说明文档,我们往往看单测用例就能够了解到作者对类的设计意图。代码重构时也离不开单测,丰富的单测用例会使我们重构代码时信心满满。虽然单测如此重要,但是一直来都不是很清楚其运行原理,也不知道为什么要做这样或那样的配置,这样终究是不行的,于是准备花时间探究下单测原理,并在此记录。 当在IDEA中Run单元测试时发生了什么? 首先,来看一下当我们直接通过IDEA运行单例时,IDEA帮忙做了哪些事情: 将工程源码和测试源码进行编译,输出到了target目录 通过java命令运行com.intellij.rt.junit.JUnitStarter,参数中指定了junit的版本以及单测用例名称 这里着重追下JUnitStarter的代码,该类在IDEA提供的junit-rt.jar插件包中,具体目录:/Applications/IntelliJ IDEA.app/Contents/plugins/junit/lib/junit-rt.jar。可以将这个包引入到我们自己的工程项目中,方便阅读源码: JUnitStarter的main函数 这里主要有两个核心方法 接下来看下prepareStreamsAndStart方法运行的时序图,这里以JUnit4为例: 当IDEA确认好要启动的框架版本后,会通过类的全限定名称反射创建IdeaTestRunner的实例。这里以JUnit4为例,IDEA会实例化com.intellij.junit4.JUnit4IdeaTestRunner类对象并调用其startRunnerWithArgs方法,在该方法中会通过buildRequest方法构建org.junit.runner.Request,通过getDescription方法获取org.junit.runner.Description,最后创建org.junit.runner.JUnitCore实例并调用其run方法。 简而言之就是,IDEA最终会借助Junit4框架的能力启动并运行单测用例,所以接下来有必要对Junit4框架的源码做些深入的探究。 Junit4源码探究 Junit是一个由Java语言编写的单元测试框架,已在业界被广泛运用,其作者是大名鼎鼎的Kent Beck和Erich Gamma,前者是《重构:改善既有代码的设计》和《测试驱动开发》的作者,后者则是《设计模式》的作者,Eclipse之父。Junit4发布于2006年,虽然是老古董了,但其中所蕴含的设计理念和思想却并不过时,有必要认真探究一番。 首先我们还是从一个简单的单测用例开始: 这里我们不再通过IDEA的插件启动单元测试,而是直接通过main函数,核心代码如下: 着重看下runner.run(request.getRunner()),先看run函数的代码: 可以看到最终运行哪种类型的测试流程取决于传入的runner实例,即不同的Runner决定了不同的运行流程,通过实现类的名字可以大概猜一猜,JUnit4ClassRunner应该是JUnit4基本的测试流程,MockitoJUnitRunner应该是引入了Mockito的能力,SpringJUnit4ClassRunner应该和Spring有些联系,可能会启动Spring容器。 现在,我们回过头来看看runner.run(request.getRunner())中request.getRunner()的代码: 可以看到Runner是基于传入的测试类(testClass)的信息选择的,这里的规则如下: 如果解析失败了,则返回ErrorReportingRunner 如果测试类上有@Ignore注解,则返回IgnoredClassRunner 如果测试类上有@RunWith注解,则使用@RunWith的值实例化一个Runner返回 如果canUseSuiteMethod=true,则返回SuiteMethod,其继承自JUnit38ClassRunner,是比较早期的JUnit版本了 如果JUnit版本在4之前,则返回JUnit38ClassRunner 如果上面都不满足,则返回BlockJUnit4ClassRunner,其表示的是一个标准的JUnit4测试模型 我们先前举的那个简单的例子返回的就是BlockJUnit4ClassRunner,那么就以BlockJUnit4ClassRunner为例,看下它的run方法是怎么执行的吧。 首先会先走到其父类ParentRunner中的run方法 这里有必要展开说下Statement,官方的解释是:Represents one or more actions to be taken at runtime in the course of running a JUnit […]
前言 Java 19 引入了Virtual Threads并Preview,这个便是协程,对于它的实现原理还未做了解,理论上应该为此在JVM层面会有一些Support,但也有一个疑惑,就是在此之前,不少JVM语言已经有协程了,它们是如何在API层面来实现这个的呢(似乎JVM不支持这个的样子),所以对Kotlin的协程做了些了解,虽然当前还是很懵,但也算有些收获。文章主体引用了文末的第一篇文章。第一篇有些浅显,第二篇比较详细,有时间慢慢看一下。 总结下kotlin的协程实现原理:continuation 方法+ 状态机(每个continuation 风格的方法会创建一个状态机) 为什么使用Kotlin协程 在 Android 上,避免阻塞主线程是非常必要的。主线程是一个处理所有界面更新的线程,也是调用所有点击处理程序和其他界面回调的线程。因此,主线程必须顺畅运行才能确保出色的用户体验 在实际开发中我们会遇到这种场景 创建子线程执行耗时操作,然后切换到主线程处理界面显示逻辑;但是如果我们在一个接口请求完成后,拿到这个接口返回的结果,在需要去请求另一个接口时,逻辑就会十分复杂,就会出现回调地狱;并且如果在需要考虑接口失败的场景呢? 使用Rxjava优化上面的场景;没有内存泄漏、支持取消、正确的使用线程,但是它比较复杂,如 subscribeOn、observeOn、map 或者 subscribe,都需要学习 这个时候kotlin 协程就出场了,它可以帮我们解决上面的痛点 前置知识 在阅读 Kotlin 源码之前,可以先了解一些前置知识。 Function Function 是 Kotlin 对函数类型的封装,对于函数类型,它会被编译成 FunctionX 系列的类: Kotlin 提供了从 Function0 到 Function22 之间的接口,这意味着我们的 lambda 函数最多可以支持 22 个参数,另外 Function 接口有一个 invoke 操作符重载,因此我们可以直接通过 () 调用 lambda 函数: 编译成 Java 代码后: 可以看到对于 lambda […]