关于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

JAVA中单元测试的常用方式

什么是单元测试单元测试(英语:Unit Testing)又称为模块测试, 是针对程序模块(软件设计的最小单位)来进行正确性检验的测试工作。程序单元是应用的最小可测试部件。在过程化编程中,一个单元就是单个程序、函数、过程等;对于面向对象编程,最小单元就是方法,包括基类(超类)、抽象类、或者派生类(子类)中的方法。通常来说,程序员每修改一次程序就会进行最少一次单元测试,在编写程序的过程中前后很可能要进行多次单元测试,以证实程序达到软件规格书要求的工作目标,没有程序错误;虽然单元测试不是什么必须的,但也不坏,这牵涉到项目管理的政策决定。单元测试的优点优质的单元测试可以保障开发质量和程序的鲁棒性。在大多数互联网企业中开发工程师在研发过程中都会频繁地执行测试用例,运行失败的单测能帮助我们快速排查和定位问题 使问题在被带到线上之前完成修复。正如软件工程界的一条金科玉律----越早发现的缺陷,其修复成本越低。一流的测试能发现未发生的故障;二流的测试能快速定位故障的发生点;三流的测试则疲于奔命,一直跟在故障后面进行功能回归。JAVA中常用的单元测试工具JUnit/JUnit5https://junit.org/junit5/ junit是老牌测试框架了,也是目前引用最广泛的一个框架。当前已经更新到Junit5,功能更强大。 class StandardTests { @BeforeAll static void initAll() { } @BeforeEach void init() { } @Test void succeedingTest() { } @Test void failingTest() { fail("a failing test"); } @Test @Disabled("for demonstration purposes") void skippedTest() { // not executed } @Test void abortedTest() { assumeTrue("abc".contains("Z")); fail("test should have been aborted"); } @AfterEach void tearDown() { } @AfterAll static void tearDownAll() { }}assertjhttps://assertj.github.io/doc/ 一个功能强悍的断言工具,支持各种断言方式 ...

June 22, 2019 · 2 min · jiezi

第三讲使用JUnit对Spring-Boot中的Rest-Controller进行单元测试

(第三讲)使用JUnit对Spring Boot中的Rest Controller进行单元测试本次教程主要讲解如何对Spring Boot中的Rest Service进行单元测试。以往我们主要是使用JUnit对业务层进行单元测试,本次课程将使用一个简单的案例来说明如何使用JUnit对Spring Boot的Rest Service进行单元测试。1. 主要类容快速搭建Restfull Service 环境创建GET请求以检索用户信息创建GET请求检索用户角色信息创建POST请求新增用户角色信息如何使用PostMan请求Restfull Service使用JUnit对GET请求进行单元测试使用JUnit对POST请求进行单元测试2. 你将需要准备的工具JDK 1.8及以上版本Maven 3.0及以上版本的项目构建工具IDEA代码编辑器3. 你可以通过以下的地址获取本次课程的所有示例代码项目代码已经上传到GitHub仓库中,你可以通过以下的地址获取示例源码:https://github.com/ramostear/Spring_Boot_2.X_Tutorial/tree/master/spring-boot-junit-rest-service 4. 项目结构下面通过一张截图来了解以下本次课程中我们使用到的项目结构。 首先我们需要位单元测试提供一个可用的Rest Controller。UserController文件为我们提供了一个可用于测试的Rest Controller。在UserController类中,我们提供两种请求类型的方法,一种是GET请求,另一种是POST请求。然后我们为这两种请求方式的方法编写单元测试用例。 在接下来的测试过程中,我们将使用Mockito来模拟请求UserService的过程,使用MockMvc来模拟请求UserController。单元测试的目的是将测试范围尽可能的缩小。在本次案例中,我们仅对UserController中的方法进行测试。 5. 初始化项目我们依然使用Spring Initializr来初始化本次课程的项目,你需要配置如下图中的参数: 现在我们需要提供两个实体类:User和Role: User.java Role.java 6. 提供可用的业务服务所有的应用都需要有数据的存储,本次课程主要的重点是为了Rest Controller的单元测试,因此使用ArrayList来充当数据库的角色。在案例中,一个用户可以有多个角色,一个角色也可以被赋予给多个用户。用户有ID,名字,别名和角色列表,角色具有ID,名称和描述。在UserService类中,将提供如图所示的公共方法。 7. 提供GET请求方法在UserController类中,我们将提供如下几个公开的GET请求方法: @GetMapping(value="/users") : 获取所有的用户信息@GetMapping(value="/users/{id}/roles") : 根据用户ID获取该用户的所有角色信息UserController.java类中的详细代码如下: package com.ramostear.spring.boot.test.restservice.controller;import com.ramostear.spring.boot.test.restservice.model.Role;import com.ramostear.spring.boot.test.restservice.model.User;import com.ramostear.spring.boot.test.restservice.service.UserService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.http.HttpStatus;import org.springframework.http.ResponseEntity;import org.springframework.web.bind.annotation.*;import java.util.List;@RestControllerpublic class UserController { private final UserService userService; @Autowired public UserController(UserService userService){ this.userService = userService; } @GetMapping(value = "/users") public List<User> findAllStudents(){ return userService.findAllUsers(); } @GetMapping(value = "/users/{id}/roles") public List<Role> findUserRoles(@PathVariable(value = "id")String id){ return userService.findUserAllRoles(id); }}8. 使用Postman对RestController进行测试我们将使用Postman工具对上述两个Rest API进行请求,首先向Postman地址栏输入http://localhost:8080/users 进行测试,获得的响应信息如下: ...

May 10, 2019 · 2 min · jiezi

带你探索JUnit 5.4

Java最受欢迎的测试库JUnit发布了一个新版本5.4。自上一次5.3.2发布以来,这个版本带来了一些值得我们去尝试的地方,本文中,我将介绍最重要的内容,并在适用的地方提供代码示例。你可能知道,JUnit 5.x(Jupiter)是对JUnit 4.x(Vintage)的巨大改进。JUnit 5是一个更优秀的版本,包括Lambda支持,JUnit 5扩展,测试方法参数注入以及许多其他功能。JUnit 5.4是JUnit 5的演变,进一步改善了测试体验。什么是Junit 5?官方使用说明JUnit 5 = JUnit平台 + JUnit Jupiter + JUnit Vintage以下是原文就不翻译了。The JUnit Platform serves as a foundation for launching testing frameworks on the JVM. It also defines the TestEngineAPI for developing a testing framework that runs on the platform. Furthermore, the platform provides a Console Launcher to launch the platform from the command line and a JUnit 4 based Runner for running any TestEngine on the platform in a JUnit 4 based environment. First-class support for the JUnit Platform also exists in popular IDEs (see IntelliJ IDEA, Eclipse, NetBeans, and Visual Studio Code) and build tools (see Gradle, Maven, and Ant).JUnit Jupiter is the combination of the new programming model and extension model for writing tests and extensions in JUnit 5. The Jupiter sub-project provides a TestEngine for running Jupiter based tests on the platform.JUnit Vintage provides a TestEngine for running JUnit 3 and JUnit 4 based tests on the platform.Junit 5 对应的JDK版本一定要是1.8以上JUnit 5.4入门要开始使用JUnit 5.4,通过Maven引入。使用JUnit 5.4已经简化了。而在先前版本的JUnit 5中,需要分别导入几个Jar包(例如,jupiter-api,jupiter-params,jupiter-engine),新版本允许你通过包括仅仅单一的使用JUnit 5.4 junit-jupiter依赖性聚集工件。<!– https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter –><dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <version>5.4.1</version> <scope>test</scope></dependency>项目代码结构@TempDir支持临时目录在测试时,有时你需要访问临时文件。JUnit 5现在提供了一个创建和清理临时文件的扩展,而不是自己处理这样一个文件的创建。您可以使用它来放置@TempDirectory注释Path或File测试输入参数或字段。一个例子:@Testvoid writeItemsToFile(@TempDir Path tempDir) throws IOException { Path file = tempDir.resolve(“test.txt”); new ListWriter(file).write(“a”, “b”, “c”); assertEquals(singletonList(“a,b,c”), Files.readAllLines(file)); }参数化测试null和空支持参数化测试是为了测试的时候提供好的入参,比如下面的的Limit就会直接打印Limit的所有枚举列表public enum Limit { IN(1,“包含”), NOT_IN(0,“不包含”); public final int value; public final String alias; Limit(int value, String k) { this.value = value; this.alias = k; }}@ParameterizedTest@EnumSource(Limit.class)void checkLimit(Limit limit) { assertTrue(limitCheck.isValid(limit));}但是,在这样的设置中,假如我们需要去测试极端场景下,代码的健壮性,模拟提供null元素有点困难,或者在其他情况下,提供空元素。@NullSource(并@EmptySource加@NullAndEmptySource)就可以帮助到我们。@NullSource相当于Mock了一个Null值做为入参,以此我们可以去测试代码@ParameterizedTest@NullSource // now also null is tested@EnumSource(Limit.class)void checkLimit(Limit limit) { assertTrue(limitCheck.isValid(limit));}设置单元测试名称通常,在使用测试类时,可以使用@DisplayName注释覆盖测试方法名称或类。例如,见下文@Test@DisplayName(“计算加法的一个单元测试”)public void add() { }虽然上面提供了更易读的格式,但它非常静态。您现在可以做的是根据例如嵌套类或方法名称生成显示名称。可以在文档中找到一个好的DisplayNameGeneration示例。如何排序JUnit 5测试方法?虽然通常将测试方法相互依赖并不是一个好主意,但对于某些情况,在测试执行中有一定的顺序是很方便的。例如,一个测试可能在REST端点中创建资源,而其他测试则验证此资源的某些属性。在以前版本的JUnit中,这很难做到,但是从5.4开始,你可以使用一个新TestMethodOrder命名的OrderAnnotation。你可以以此来结合使用,Order以强制按特定顺序执行测试方法。@TestMethodOrder(OrderAnnotation.class)class SequenceTest { @Test @Order(1) void createResource() { // Create a resource first… } @Test @Order(2) void verify() { // …then verify some attributes. }}下一步?新的JUnit 5版本中还有许多其他功能,可以在官网找到。除了框架之外,JUnit 5的一大优点是文档很好。有很多可用的文档,其中的概念用非常简单的代码示例进行了解释,这一点我觉得和Spring的官网说明一样具有高质量,简单清晰。如果您尚未升级到JUnit 5,那么最新版本提供了许多强大的功能,这些功能将使迁移变得有价值。 ...

March 27, 2019 · 2 min · jiezi

模拟HTTP请求调用controller

可参考本人简书:模拟HTTP请求调用controller写在前面MockMvc实现了对Http请求的模拟,能够直接使用网络的形式,转换到Controller调用,这样使得测试速度更快,不依赖网络环境。而且提供了一套验证的工具。单测代码如下:@RunWith(SpringRunner.class)@WebMvcTest(MyController.class)public class MyControllerTest { @Autowired private MockMvc mockMvc; /** * 测试方法 / private void bindAndUnbindTenantPoiTest() throws Exception { MvcResult mvcResult = mockMvc.perform(post(${“访问的url”}) .param("${key1}", “${value1}”) .param("${key2}", “${value2}”) .param("${key3}", “${value3}”)) .andDo(print()) // 定义执行行为 .andExpect(status().isOk()) // 对请求结果进行验证 .andReturn(); // 返回一个MvcResult jsonObject = toJsonObject(mvcResult); assert jsonObject.getIntValue(“code”) == code; // 断言返回内容是否符合预期 assert message.equals(jsonObject.getString(“message”)); } }Perform介绍perform用来调用controller业务逻辑,有post、get等多种方法,具体可以参考利用Junit+MockMvc+Mockito对Http请求进行单元测试参数Param介绍通过param添加http的请求参数,格式是K-V,一个参数一个参数添加或者通过params添加MultiValueMap<String, String>。parma部分源码如下:/* * Add a request parameter to the {@link MockHttpServletRequest}. * <p>If called more than once, new values get added to existing ones. * @param name the parameter name * @param values one or more values / public MockHttpServletRequestBuilder param(String name, String… values) { addToMultiValueMap(this.parameters, name, values); return this; } /* * Add a map of request parameters to the {@link MockHttpServletRequest}, * for example when testing a form submission. * <p>If called more than once, new values get added to existing ones. * @param params the parameters to add * @since 4.2.4 */ public MockHttpServletRequestBuilder params(MultiValueMap<String, String> params) { for (String name : params.keySet()) { for (String value : params.get(name)) { this.parameters.add(name, value); } } return this; }写在后面还有个坑就是使用注解的时候,看看注解之间是否有重叠,否则会报错。如果同时使用@WebMvcTest @Configuration就错了。具体可以查看注解源码 ...

March 14, 2019 · 1 min · jiezi

Junit借助Groboutils Core进行并发测试

背景junit是无法进行并发测试,但是又有需要并发测试的场景怎么办呢?此时可以借助一个插件(Groboutils Core)来完成这种功能。maven仓库地址:点我直达实现第一步:在项目的pom.xml中加入依赖<!– https://mvnrepository.com/artifact/net.sourceforge.groboutils/groboutils-core –><dependency> <groupId>net.sourceforge.groboutils</groupId> <artifactId>groboutils-core</artifactId> <version>5</version> <scope>test</scope></dependency>第二步:在单测中进行代码编写@Test public void testConcurrentInitOrBind() { // mock一个返回 doReturn(Lists.newArrayList(userMemberCard)).when(operateCardDao) .queryCardByRegisterMobileAndTenantId(anyString(), anyLong()); TestRunnable runner = new TestRunnable() { // 在runTest方法中填写自己的测试方法 @Override public void runTest() throws Throwable { InitCardResVo resVoFirst = operateCardService.initOrBindCard(requestVo); System.out.println(“result resVoFirst is:” + resVoFirst.toString()); } }; // 一个数组,代表并发个数。此处并发5个 TestRunnable[] trs = new TestRunnable[5]; for (int i = 0; i < 5; i++) { trs[i] = runner; } MultiThreadedTestRunner mttr = new MultiThreadedTestRunner(trs); try { mttr.runTestRunnables(); } catch (Throwable ex) { ex.printStackTrace(); } } ...

March 13, 2019 · 1 min · jiezi

再谈自动化测试——我们在编写测试时,应该注意什么

本文首发于泊浮目的专栏:https://segmentfault.com/blog…背景最近项目在测试阶段陆陆续续的测出了一些bug.这个情况刚出现的时候,让笔者很困惑——平时我们的每个feature代码都是跟随着大量看起来考虑很周全的case进入代码仓库的,然而事实还是打了我们的脸.故在本文,笔者将会从最近的所学所想来谈谈编写测试的时候我们应该注意什么.AIR原则与BCDE原则前阵子看了一本书,里面提到了单元测试的一些原则:宏观上,单元测试要符合AIR原则微观上,单元测试的代码层面要符合BCDE原则AIR原则AIR即空气,单元测试亦是如此。当业务代码在线上运行时,可能感觉不到测试用例的存在和价值,但在代码质量的保障上,却是非常关键的。新增代码应该同步增加测试用例,修改代码逻辑时也应该同步保证测试用例成功执行。AIR原则具体包括:A: Automatic (自动化)I: Independent (独立性)R: Repeatable (可重复)简单的解释一下三个原则:单元测试应该是全自动执行的。测试用例通常会被频繁地触发执行,执行过程必须完全自动化才有意义。如果单元测试的输出结果需要人工介入检查,那么它一定是不合格的。单元测试中不允许使用System.out等方法来进行人工验证,而必须使用断言来验证。为了保证单元测试稳定可靠且便于维护,需要保证其独立性。用例之间不允许互相调用,也不允许出现执行次序的先后依赖。BCDE原则编写单元测试用例时,为了保证被测模块的交付质量,需要符合BCDE原则。B: Border,边界值测试,包括循环边界、特殊取值、特殊时间点、数据顺序等。C: Correct,正确的输入,并得到预期的结果。D: Design,与设计文档相结合,来编写单元测试。E: Error,单元测试的目标是证明程序有错,而不是程序无错。为了发现代码中潜在的错误,我们需要在编写测试用例时有一些强制的错误输入(如非法数据、异常流程、非业务允许输入等)来得到预期的错误结果。在ZStack白盒集成测试中实践原则之前提到的原则是基于单元测试的,但在ZStack的白盒测试中也可以作为有价值的参考.戳此了解ZStack的白盒集成测试:https://segmentfault.com/a/11…由于ZStack的整套测试框架也是基于Junit扩展而来,因此也是一定程度上遵循了上面提到的AIR原则.除了A原则,I和R原则在一定程度上打了折扣:I: 如果上一个测试没有清理干净状态,则会影响下一个测试R: 基于上面提到的I,很有可能导致可重复性大打折扣当然,出现这些问题时则表示当前的代码中有bug.但单元测试则不会受到这样的影响——它能测出bug,AIR原则也得以保证.在本次示例中,我们将以VmInstance的创建API即——APICreateVmInstacneMsg作为测试对象.如果读者不是很了解上下文,也可以简单的看一下这个Case:OneVmBasicLifeCycleCaseBorder Test && Error Test边界测试是用来探测和验证代码在处理极端的情况下会发生什么.而错误测试为了保证ZStack在一些错误的状态下做出我们所期待的行为.那么我们该如何编写这样的测试呢?我们先来简单的理一下创建Vm的流程:VmImageSelectBackupStorageFlowVmAllocateHostFlowVmAllocatePrimaryStorageFlowVmAllocateVolumeFlowVmAllocateNicFlowVmInstantiateResourcePreFlowVmCreateOnHypervisorFlowVmInstantiateResourcePostFlow而其中每一个步骤可以分成好几个小步骤,以VmAllocateHostFlow为例:我们可以看到,根据不同的策略,allocateHost里还会有好几个flow.而由于松耦合架构,我们可以在测试中轻易的模拟极端问题的出现,如:找不到合适的BackupStorageHostCapacity的不够Agent返回的回复在某一个时刻与管理节点的状态不同…….以此类推,以上创建vm的8个flow都可以轻易模拟各种边界条件及错误情况.Correct Test && Design Test正确性测试听起来应该会很简单,(比如调用一个API,然后看结果返回是否正确)但如果放到集成测试中,我们还是可以拓展出一些额外的关注点的.还是以上面提到的createVm为例子,我们看到了8个flow,然后里面可能还嵌套着好几个子flow.如图所示:在编写正确性测试时,我们可以考虑额外关注以下几点:APIParam在各个Flow间中转时是否如预期关注管理节点内的服务:Flow之间调用的时序是否符合预期Flow之间流转时,业务目标状态是否符合预期关注管理节点外的服务:对于agent的请求是否符合预期在API调用完后,相关资源的目标状态是否符合预期而与文档结合的测试用例,则应当由团队的测试人员来定义.可以确定的是,这类的测试更加关注于API(即输入输出),而不是内部的状态.

March 12, 2019 · 1 min · jiezi

Spock - 行为驱动开发的好帮手

测试驱动开发2017-7-13TDD在写新代码前写一个失败的测试用例消除重复主要工具JUnitSpockGeb信条没有测试的功能=没有的功能有测试=可重构有测试> 有文档JUnit 的打开方式CL: java -cp junit.jar junit.textui.TestRunner className.methodNameAnt, Maven, Gradle, IDEKeep the bar green to keep the code cleanJUnit 信条Tests are the Programmer’s Stone, transmuting fear into boredom.效率工具之MockCagetory, TestSuit, TestRunnerJUnit 最佳实践保持代码的可测试性:new vs @Autowired new 许多情况下更方便测试, 为方便new,可借鉴Builder工厂化方法模式@Autowired 需要 @WebAppConfiguration @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = {“classpath*:/spring-test.xml” }) …protected vs private package可见性更方便测试, private不便于测试不要滑入为测试写测试的深渊Keep simpleif(if(if(if)))) 这样的类没法测试解耦方法:拆分成多个方法解耦;通过面向对象的继承多态解耦.JUnit 便于拆除的脚手架目录结构,分离test与src测试代码与正式代码分开放到不同文件目录src/main/javasrc/test/java但每个类的测试类与被测试类采用同样的包名src/main/java/com/example/MyBeauty.javasrc/test/java/com/example/MyBeautyTest.java依赖分离 maven dependency依赖增加 <scope>test</scope>gradle 依赖增加 testCompile 后缀方法命名以test开头,兼容JUnit3,4,5AclassTest.testMethod()JUnit 集成测试依赖容器的测试:Jetty的配置集成测试:mvn integration-testSelenium相应配置,浏览器插件JUnit 测试专属配置配置文件spring-test.xml,pom_test.xml 通过 注解和mvn -f 参数分别指定 Spring MVC测试:mockMvc (略)Spring security 权限处理:(略)Spock 参考资料BDD vs TDDhttp://farenda.com Java programming tutorials with many code examples!https://github.com/spockframeworksmarter-testing-with-spock.pdfspock-next-generation.pdfSpock 生态圈基于GroovyGroovy Grape Geb Gradle …测试类需要继承自 Specification(说明书) class MyBeautyControllerSpec extends Specification {标记关键词Spec: when then expect given whereGeb - web测试扩展:GebSpec //基于selenium动作: go, isAt, doAt内置对象:pageUrl,_browser,page,$参见:adminssll/test/script/LoginSpecGeb - 象jQuery一样 go “https://bixuebihui.com/" println pageUrl assert $(“div.title h3”).text() == “认证” $(“form”).with { username = “user1” password = “123” $(‘input’, name:‘submit’).click() } go ‘/blog/‘追求速度,覆盖率,可重复测试指标测试报告查看: mvn site ...

September 26, 2018 · 1 min · jiezi