关于junit:Junit执行器Runner探索之旅-京东云技术团队

单元测试是每个程序员必备的技能,而Runner是每个单元测试类必有属性。本文通过解读Junit源码,介绍junit中每个执行器的应用办法,让读者在单元测试时,能够灵便的应用Runner执行器。 一、背景在往年的麻利团队建设中,京东物流通过Suite执行器实现了一键自动化单元测试。Juint除了Suite执行器还有哪些执行器呢?由此京东物流的Runner探索之旅开始了! 二、RunWith RunWith的正文是当一个类用@RunWith正文或扩大一个用@RunWith正文的类时,JUnit将调用它援用的类来运行该类中的测试,而不是内置到JUnit中的运行器,就是测试类依据指定运行形式进行运行。 代码如下: public @interface RunWith { Class<? extends Runner> value();}其中:Runner 就是指定的运行形式。 三、RunnerRunner的作用是通知Junit如何运行一个测试类,它是一个抽象类。通过RunWith 指定具体的实现类,如果不指定默认应用BlockJUnit4ClassRunner,Runner的代码如下: public abstract class Runner implements Describable { public abstract Description getDescription(); public abstract void run(RunNotifier notifier); public int testCount() { return getDescription().testCount(); }}3.1 ParentRunnerParentRunner是一个抽象类,提供了大多数特定于运行器的性能,是常常应用运行器的父节点。实现了Filterable,Sortable接口,能够过滤和排序子对象。 提供了3个形象办法: protected abstract List<T> getChildren();protected abstract Description describeChild(T child);protected abstract void runChild(T child, RunNotifier notifier);3.1.1 BlockJUnit4ClassRunnerBlockJUnit4ClassRunner是Juint4默认的运行器,具备与旧的测试类运行器(JUnit4ClassRunner)完全相同的行为。 ParentRunner3个形象办法的实现如下: @Overrideprotected void runChild(final FrameworkMethod method, RunNotifier notifier) { Description description = describeChild(method); if (isIgnored(method)) { notifier.fireTestIgnored(description); } else { runLeaf(methodBlock(method), description, notifier); }}@Overrideprotected Description describeChild(FrameworkMethod method) { Description description = methodDescriptions.get(method); if (description == null) { description = Description.createTestDescription(getTestClass().getJavaClass(), testName(method), method.getAnnotations()); methodDescriptions.putIfAbsent(method, description); } return description;}@Overrideprotected List<FrameworkMethod> getChildren() { return computeTestMethods();}runChild() : ...

June 13, 2023 · 3 min · jiezi

关于junit:java开发框架之JUnit-学习分享

运行流程JUnit的启动形式有很多,比方在Android Studio中咱们能够间接点击某个被@Test注解的函数来运行:此时,启动的是JUniteStarter,该类是intellij为咱们提供的。感java培训趣味能够查看其源码:https://github.com/JetBrains/...如果咱们应用gradle, 能够执行gradle test运行测试,实际上是在一个线程中执行SuiteTestClassProcessor的processTestClass办法来进行启动。其源码能够查看https://github.com/gradle/gra...如上两种都是第三方工具为咱们提供的便捷形式,实际上JUnit也提供了一个名为JUnitCore的类来供咱们不便的运行测试用例。只管启动JUnit的形式有很多,但这都是关上与JUnit对话的一些形式,最终执行的还是JUnit当中的起到核心作用的一些类,为了让大家对这些外围boss有一个初步理解,我画了一个类图:上图中仅是JUnit中的几个外围的类,也是本分次要剖析的对象。这里先给出一些对象的职责,能够有个大体的理解,前面会通过代码就会更分明每个对象是如何实现这些职责的:• 在类图的地方,有个叫做ParentRunne的对象很引人注目,它继承自Runner.• Runner则示意着JUnit对整个测试的形象• Runner实现了Describable接口,Describable接口中惟一的函数getDescription()返回了Description对象,记录着测试的信息。• Statement 是一个抽象类,其 evaluate()函数代表着在测试中将被执行的办法。• ParentRunner 共有两个子类,BlockJUnit4ClassRunner 用来运行单个测试类,Suite用来一起运行多个测试类• RunnerBuilder 是生产Runner的策略,如应用@RunWith(Suite.class)标注的类须要应用Suite, 被@Ignore标注的类须要应用IgnoreClassRunner。• TestClass是对被测试的类的封装 综上,咱们先从ParentRunner看起,其构造函数如下:protected ParentRunner(Class<?> testClass) throws InitializationError {this.testClass = createTestClass(testClass);validate();}this.testClass即前文所说的TestClass,咱们进入createTestClass办法来查看其如何将class对象转换为TestClass。protected TestClass createTestClass(Class<?> testClass) {return new TestClass(testClass);}并没什么货色,具体的逻辑都写在TestClass的外部:public TestClass(Class<?> clazz) {this.clazz = clazz;if (clazz != null && clazz.getConstructors().length > 1) {throw new IllegalArgumentException("Test class can only have one constructor");}Map<Class<? extends Annotation>, List<FrameworkMethod>> methodsForAnnotations =new LinkedHashMap<Class<? extends Annotation>, List<FrameworkMethod>>();Map<Class<? extends Annotation>, List<FrameworkField>> fieldsForAnnotations =new LinkedHashMap<Class<? extends Annotation>, List<FrameworkField>>();scanAnnotatedMembers(methodsForAnnotations, fieldsForAnnotations);this.methodsForAnnotations = makeDeeplyUnmodifiable(methodsForAnnotations);this.fieldsForAnnotations = makeDeeplyUnmodifiable(fieldsForAnnotations);}能够看到,整个构造函数大抵都在做一些验证和初始化的工作,须要引起咱们留神的应该是scanAnnotatedMembers办法:protected void scanAnnotatedMembers(Map<Class<? extends Annotation>, List<FrameworkMethod>> methodsForAnnotations, Map<Class<? extends Annotation>, List<FrameworkField>> fieldsForAnnotations) {for (Class<?> eachClass : getSuperClasses(clazz)) {for (Method eachMethod : MethodSorter.getDeclaredMethods(eachClass)) {addToAnnotationLists(new FrameworkMethod(eachMethod), methodsForAnnotations);}// ensuring fields are sorted to make sure that entries are inserted// and read from fieldForAnnotations in a deterministic orderfor (Field eachField : getSortedDeclaredFields(eachClass)) {addToAnnotationLists(new FrameworkField(eachField), fieldsForAnnotations);}}}整个函数的作用就是扫描class中办法和变量上的注解,并将其依据注解的类型进行分类,缓存在methodsForAnnotations与fieldsForAnnotations当中。须要留神的是,JUnit对办法和变量别离封装为FrameworkMethod与FrameworkField,它们都继承自FrameworkMember,这样就为办法和变量进行了对立形象。看完了ParentRunner的构造函数,咱们来看ParentRunner继承自Runner的run办法是如何工作的:@Overridepublic void run(final RunNotifier notifier) {EachTestNotifier testNotifier = new EachTestNotifier(notifier,getDescription());try {Statement statement = classBlock(notifier);statement.evaluate();} catch (AssumptionViolatedException e) {testNotifier.addFailedAssumption(e);} catch (StoppedByUserException e) {throw e;} catch (Throwable e) {testNotifier.addFailure(e);}}其中比拟要害的代码是classBlock函数将notifier转换为Statement:protected Statement classBlock(final RunNotifier notifier) {Statement statement = childrenInvoker(notifier);if (!areAllChildrenIgnored()) {statement = withBeforeClasses(statement);statement = withAfterClasses(statement);statement = withClassRules(statement);}return statement;}持续追进childrenInvoker之前,容许我当初这里先存个档,记为A,一会咱们会回到classBlock这里protected Statement childrenInvoker(final RunNotifier notifier) {return new Statement() {@Overridepublic void evaluate() {runChildren(notifier);}};}childrenInvoker返回的是一个Statement,看它的evaluate办法,其调用的是runChildren办法,这也是ParentRunner中十分重要的一个函数:private void runChildren(final RunNotifier notifier) {final RunnerScheduler currentScheduler = scheduler;try {for (final T each : getFilteredChildren()) {currentScheduler.schedule(new Runnable() {public void run() {ParentRunner.this.runChild(each, notifier);}});}} finally {currentScheduler.finished();}}这个函数就体现了形象的重要性,留神泛型T,它在ParentRunner的每个实现类中各不相同,在BlockJUnit4ClassRunner中T示意FrameworkMethod,具体到这个函数来讲getFilteredChildren拿到的是被@Test注解标注的FrameworkMethod,而在Suite中,T为Runner,而ParentRunner.this.runChild(each, notifier);这句的中的runChild(each, notifier)办法仍旧是个形象办法,咱们先看BlockJUnit4ClassRunner中的实现:@Overrideprotected void runChild(final FrameworkMethod method, RunNotifier notifier) {Description description = describeChild(method);if (isIgnored(method)) {notifier.fireTestIgnored(description);} else {runLeaf(methodBlock(method), description, notifier);}}isIgnored办法判断了method办法是否被@Ignore注解标识,如果是的话则间接告诉notifier触发ignored事件,否则,执行runLeaf办法, runLeaf的第一个参数是Statement,所以,BlockJUnit4ClassRunner通过methodBlock办法将method转换为Statement:protected Statement methodBlock(FrameworkMethod method) {Object test;try {test = new ReflectiveCallable() {@Overrideprotected Object runReflectiveCall() throws Throwable {return createTest();}}.run();} catch (Throwable e) {return new Fail(e);}Statement statement = methodInvoker(method, test);statement = possiblyExpectingExceptions(method, test, statement);statement = withPotentialTimeout(method, test, statement);statement = withBefores(method, test, statement);statement = withAfters(method, test, statement);statement = withRules(method, test, statement);return statement;}后面的几行代码是在生成test 对象,而test对象的类型则是咱们待测试的class,接下来追进methodInvoker办法:protected Statement methodInvoker(FrameworkMethod method, Object test) {return new InvokeMethod(method, test);}可见,咱们生成的Statement实例为InvokeMethod,咱们看下其evaluate办法:testMethod.invokeExplosively(target);invokeExplosively函数做的事件就是对target对象调用testMethod办法。而后面咱们说过,这个testMethod在BlockJUnit4ClassRunner中就是被@Test所标注的办法,此时,咱们终于找到了@Test办法是在哪里被调用的了。别急,咱们接着方才的函数持续剖析:statement = possiblyExpectingExceptions(method, test, statement);statement = withPotentialTimeout(method, test, statement);statement = withBefores(method, test, statement);statement = withAfters(method, test, statement);statement = withRules(method, test, statement);咱们能够看到,statement一直的在变形,而通过withBefores,withRules这些函数的名字咱们能够很容易猜到,这里就是在解决@Before,@Rule等注解的中央,咱们以withBefores为例:protected Statement withBefores(FrameworkMethod method, Object target,Statement statement) {List<FrameworkMethod> befores = getTestClass().getAnnotatedMethods(Before.class);return befores.isEmpty() ? statement : new RunBefores(statement,befores, target);}这个函数里首先拿到了所有被@Before标注的办法,将其封装为RunBefores,咱们看下其构造函数和public RunBefores(Statement next, List<FrameworkMethod> befores, Object target) {this.next = next;this.befores = befores;this.target = target;}public void evaluate() throws Throwable {for (FrameworkMethod before : befores) {before.invokeExplosively(target);}next.evaluate();}很是明了,evaluate执行时,首先将before办法全副invoke来执行,而后才调用原始statement的evaluate办法。其余几个函数与此相似,感兴趣能够持续查看。如此,咱们就明确了runLeaf办法的第一个参数Statement的由来,接下来就看下这个runLeaf办法做了什么,runLeaf在ParentRunner中有默认的实现:protected final void runLeaf(Statement statement, Description description,RunNotifier notifier) {EachTestNotifier eachNotifier = new EachTestNotifier(notifier, description);eachNotifier.fireTestStarted();try {statement.evaluate();} catch (AssumptionViolatedException e) {eachNotifier.addFailedAssumption(e);} catch (Throwable e) {eachNotifier.addFailure(e);} finally {eachNotifier.fireTestFinished();}}非常简单,间接执行了statement的evaluate办法,须要留神的是这里的statement实例不肯定是什么了,有可能是RunBefores,也有可能是RunAfters,这就和被测试类中的注解无关了。讲到这里,还记得后面咱们说过的存档A吗?咱们回到存档A:protected Statement classBlock(final RunNotifier notifier) {Statement statement = childrenInvoker(notifier);if (!areAllChildrenIgnored()) {statement = withBeforeClasses(statement);statement = withAfterClasses(statement);statement = withClassRules(statement);}return statement;}刚刚存档后所产生的一起,其实就是在执行Statement statement = childrenInvoker(notifier)这个代码。换句话说,childrenInvoker的作用就是将所有须要执行的测试用例用一个Statement封装起来。进而点燃这个Statement,就会触发所有的测试用例。但同样须要留神到被if语句突围的代码,咱们又看到了相熟的语句,Statement还在被一直的转换,但此时是在类的层面,withBeforeClasses函数操作的就是@BeforeClass注解了:protected Statement withBeforeClasses(Statement statement) {List<FrameworkMethod> befores = testClass.getAnnotatedMethods(BeforeClass.class);return befores.isEmpty() ? statement :new RunBefores(statement, befores, null);}须要留神的是这回RunBefores的第三个参数为null,阐明被@BeforeClass注解的办法只能是static的。如上,咱们剖析了BlockJUnit4ClassRunner的运行流程,也就是说当测试类为一个的时候JUnit是如何工作的。前文也提到过,ParentRunner还有一个子类Suite,示意须要运行一组测试,BlockJUnit4ClassRunner的一个运行单元为FrameworkMethod,而Suite的一个运行单元为Runner,咱们看其runChild办法:protected void runChild(Runner runner, final RunNotifier notifier) {runner.run(notifier);}很是明了,间接滴啊用runner的run办法。这样,如果这个runner的实例依然是Suite,则会持续向里运行,如果这个runner为BlockJUnit4ClassRunner,这执行咱们后面剖析的逻辑。这里有个问题是,那这个runner是如何生成的呢?这就要看Suite的构造函数:protected Suite(Class<?> klass, Class<?>[] suiteClasses) throws InitializationError {this(new AllDefaultPossibilitiesBuilder(true), klass, suiteClasses);}AllDefaultPossibilitiesBuilder的职责就是为每个类生找到对应的Runner,感兴趣能够查看其runnerForClass办法,比拟容易了解,这里就不再赘述。Matcher验证下面咱们剖析了用@Test标注的函数是如何被JUnit执行的,但单单有@Test标注是必定不够的,既然是测试,咱们必定须要肯定的伎俩来验证程序的的执行是合乎预期的。JUnit提供了Matcher机制,能够满足咱们大部分的需要。Matcher相干类次要在org.hamcrest包下,先来看下类图:上图仅仅列出了org.hamcrest包下的一部分类,这些类一起组合起来造成了JUnit弱小的验证机制。验证的根本写法是:MatcherAssert.assertThat("saymagic", CoreMatchers.containsString("magic"));首先咱们须要调用的是MatcherAssert的assertThat办法,这个办法最终辗转为:public static <T> void assertThat(String reason, T actual, Matcher<? super T> matcher) {if (!matcher.matches(actual)) {Description description = new StringDescription();description.appendText(reason).appendText("\nExpected: ").appendDescriptionOf(matcher).appendText("\n but: ");matcher.describeMismatch(actual, description);throw new AssertionError(description.toString());}}这个函数目标很是明确,直接判断matcher是否匹配,不匹配则封装形容信息,而后抛出异样。所以咱们来关注matcher的matchs办法都做了些什么,CoreMatchers.containsString("magic")返回的就是一个matcher, CoreMatchers相当于一个动态工厂,提供了大量的静态方法来返回各种Matcher:咱们就已刚刚的containsString为例,查看其外部代码:public static org.hamcrest.Matcher<java.lang.String> containsString(java.lang.String substring) {return org.hamcrest.core.StringContains.containsString(substring);} ...

October 29, 2021 · 3 min · jiezi

关于junit:关于-junit4-90-的人都不知道的特性详解-junitperf-的实现原理

前言上一节介绍了 https://github.com/houbb/junitperf 的入门应用。 这一节咱们从源码的角度,分析一下其实现形式。 性能测试该怎么做?Junit Rulesjunit4 小伙伴们必定不生疏,那么 junit rules 你听过说过吗? 要想基于 junit4 实现一个性能测试框架,最外围的一点在于了解 Junit Rules。 官网文档:https://github.com/junit-team/junit4/wiki/Rules Rules 作用规定容许非常灵活地增加或从新定义测试类中每个测试方法的行为。 测试人员能够重用或扩大上面提供的规定之一,或者编写本人的规定。 自定义规定ps: 上面的内容来自官网的例子。 大多数自定义规定能够作为 ExternalResource 规定的扩大来实现。 然而,如果您须要无关所探讨的测试类或办法的更多信息,则须要实现 TestRule 接口。 import org.junit.rules.TestRule;import org.junit.runner.Description;import org.junit.runners.model.Statement;public class IdentityRule implements TestRule { @Override public Statement apply(final Statement base, final Description description) { return base; }}当然,实现 TestRule 的弱小性能来自应用自定义构造函数的组合、向类增加办法以用于测试,以及将提供的 Statement 包装在新的 Statement 中。 例如,思考以下为每个测试提供命名记录器的测试规定: package org.example.junit;import java.util.logging.Logger;import org.junit.rules.TestRule;import org.junit.runner.Description;import org.junit.runners.model.Statement;public class TestLogger implements TestRule { private Logger logger; public Logger getLogger() { return this.logger; } @Override public Statement apply(final Statement base, final Description description) { return new Statement() { @Override public void evaluate() throws Throwable { logger = Logger.getLogger(description.getTestClass().getName() + '.' + description.getDisplayName()); base.evaluate(); } }; }}而后这个规定就能够依照上面的形式应用: ...

July 24, 2021 · 3 min · jiezi

关于junit:性能测试到底该怎么做

三高人群作为一名开发者,咱们最长听到的就是编程界的三高: 高性能、高并发、高可用。 听起来十分高大上,然而性能到底如何呢?又该如何评定呢? 这次咱们谈一谈性能测试,看一看到底什么样才叫做高性能。 本文次要从以下几个方面进行探讨。 (1)性能测试是什么? (2)为什么须要性能测试? (3)性能测试如何做? (4)有哪些性能测试的工具 性能测试是什么?老马已经说过,你想了解一件事物,首先必须先定义它。 这里间接援用一下百科中的定义: 性能测试是通过自动化的测试工具模仿多种失常、峰值以及异样负载条件来对系统的各项性能指标进行测试。性能测试的定义也不难理解,往往定义自身论述了性能测试的作用。 为什么须要性能测试?如果你是一名开发、测试,平时接手过不少需要,可能性能测试接触的也不多。 每一个需要,都有对应的功能性需要和肺功能性需要。 功能性需要是产品需要文档中最间接的,须要实现的性能指标。简称,能用就行。 非功能性需要则要宽泛的多,架构设计是否正当?是否便于前期拓展?是否便于监控?代码实现是否优雅?文档正文是否残缺? 就像你写了一只鸟,鸟头做螺旋桨非能飞起来,然而在架构设计上可能是不合理的。 一个查问性能,用户点击查问,10S 种才返回数据,性能上是满足的,然而性能上是不能承受的。 线上的交易性能平时各方面都很棒,节假日高峰期间接零碎就瘫痪了。 那如何防止这些问题呈现在生产上呢? 这就须要上线之前,首先做好对应的性能测试,防止再生产上呈现问题,带来重大的生产事变。 性能要高,性能要硬,性能测试,又高又硬! 如何做好性能测试做一件事件之前,咱们首先要确定好本人的指标。 性能测试,到底要测试什么? 有些相似于开发过程中的需要剖析,常见的测试指标如下。 测试指标响应工夫响应工夫是指某个申请或操作从收回到接管到反馈所耗费的工夫,包含应用服务器(客户端)解决工夫、网络传输工夫以及数据库服务器解决工夫。 作为用户而言,在页面点击查问,期待了多久能力获取后果,这个就是响应工夫。 用户不关怀你后端通过了多少个服务,慢就是原罪。 对于微服务零碎,链路监控就显得比拟重要。能够帮忙咱们疾速定位到底慢在哪里。 TPS/QPSTPS(Transaction Per Second)是指单位工夫(每秒)零碎解决的事务量。 我看网上还有很多相似的概念:点击量/点击率、吞吐量/吞吐率、PV/UV,这里不做赘述。 集体看来实质上 TPS/QPS 就是去压测你利用的极限,当访问量较大的时候,程序是否活下来? 这里次要波及到两个概念:高性能和高可用。 咱们前面会简略探讨下这两点。 测试筹备明确了测试指标之后,就须要进行测试的筹备。 环境筹备:比方你想压测数据库,那就须要筹备对应配置的数据库资源。 脚本的筹备:数据初始化脚本,调用脚本等。 这个能够类比开发过程中的代码开发。 ps: 性能压测个别不是很罕用,所以环境筹备流程会比拟长,这一点须要留神。 测试报告当进行测试之后,测试的后果肯定要给出一份报告进去。 是否通过压测要求? 最高的 QPS 是多少? 这样开发能够依据这份报告进行相应的优化。 如何晋升程序性能晋升性能的内容写一本书也不为过,这里简略列举一些最罕用的几点: (1)慢 SQL 个别程序如果响应工夫较长,能够首先看一下慢 SQL。 看下是否须要减少索引,或者进行 SQL 优化。 (2)缓存 针对查问,性能晋升最显著的就是引入缓存。 当然,引入缓存会使架构变得复杂,这一点要联合本人的理论业务。 (3)硬件降级 如果程序优化的空间比拟小,能够思考降级一下硬件资源。 比方服务器配置翻倍,数据库配置翻倍。 什么?你说公司没钱降级? 没钱降级做什么压测? ...

July 23, 2021 · 2 min · jiezi