From 7ed60c84bb64b31795d4d70de7002b0729de1a3c Mon Sep 17 00:00:00 2001 From: Dmytro Serdiuk Date: Wed, 30 Oct 2019 20:44:50 +0200 Subject: [PATCH] Add JUnit5 support JUnit5 support works in the same way as for other xUnit engines. Also, all related documentation is updated. #160 --- README.md | 5 +- build.gradle | 8 + docs/index.rst | 6 +- docs/md/artifacts.md | 33 +++- docs/md/entry-points.md | 6 + docs/md/getting-started.md | 13 +- settings.gradle | 1 + sunshine-junit5/build.gradle | 15 ++ sunshine-junit5/gradle.properties | 2 + .../tatools/sunshine/junit5/Junit5Kernel.java | 84 +++++++++ .../tatools/sunshine/junit5/Junit5Status.java | 47 +++++ .../org/tatools/sunshine/junit5/Sunshine.java | 28 +++ .../tatools/sunshine/junit5/package-info.java | 7 + .../sunshine/junit5/Junit5KernelTest.java | 47 +++++ .../sunshine/junit5/Junit5StatusTest.java | 160 ++++++++++++++++++ 15 files changed, 443 insertions(+), 19 deletions(-) create mode 100644 sunshine-junit5/build.gradle create mode 100644 sunshine-junit5/gradle.properties create mode 100644 sunshine-junit5/src/main/java/org/tatools/sunshine/junit5/Junit5Kernel.java create mode 100644 sunshine-junit5/src/main/java/org/tatools/sunshine/junit5/Junit5Status.java create mode 100644 sunshine-junit5/src/main/java/org/tatools/sunshine/junit5/Sunshine.java create mode 100644 sunshine-junit5/src/main/java/org/tatools/sunshine/junit5/package-info.java create mode 100644 sunshine-junit5/src/test/java/org/tatools/sunshine/junit5/Junit5KernelTest.java create mode 100644 sunshine-junit5/src/test/java/org/tatools/sunshine/junit5/Junit5StatusTest.java diff --git a/README.md b/README.md index c7791c0..5f197bc 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # Sunshine -Sunshine allows you to manage suits of your automated tests directly from Java code. It can work -on top of [TestNg](https://testng.org/doc/index.html) or [JUnit4](https://junit.org/junit4/). +Sunshine allows you to manage suits of your automated tests directly from Java code. It supports +[TestNg](https://testng.org/doc/index.html), [JUnit 4](https://junit.org/junit4/), and +[JUnit 5](https://junit.org/junit5/). Please read the users documentation on [http://sunshine.tatools.org](http://sunshine.tatools.org). diff --git a/build.gradle b/build.gradle index d2aad53..d380a73 100644 --- a/build.gradle +++ b/build.gradle @@ -55,6 +55,14 @@ project(':sunshine-testng-integration-tests') { } } +project(':sunshine-junit5') { + apply from: rootProject.file('gradle/jacoco.gradle') + apply from: rootProject.file('gradle/bintray.gradle') + dependencies { + compile project(':sunshine-core') + } +} + defaultTasks 'clean', 'ready' //task to write the version parameter given via command line into the "gradle.properties" files. diff --git a/docs/index.rst b/docs/index.rst index 5060696..5ed6371 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -7,9 +7,11 @@ Welcome to Sunshine's documentation! ==================================== .. _TestNG: https://testng.org/doc/index.html .. _JUnit4: https://junit.org/junit4/ +.. _JUnit5: https://junit.org/junit5/ -Sunshine allows you to manage suits of your automated tests directly from Java code. It can work -on top of TestNG_ or JUnit4_. + +Sunshine allows you to manage suits of your automated tests directly from Java code. It supports +TestNG_, JUnit4_, and JUnit5_. .. toctree:: :maxdepth: 3 diff --git a/docs/md/artifacts.md b/docs/md/artifacts.md index f4811dc..d406723 100644 --- a/docs/md/artifacts.md +++ b/docs/md/artifacts.md @@ -1,17 +1,34 @@ # Artifacts -Sunshine consists of 3 libraries +Sunshine consists of 4 libraries - `sunshine-core` provides interfaces and common implementations - `sunshine-junit4` wraps JUnit4 to allow the creation of [entry points](entry-points.md) +- `sunshine-junit5` wraps JUnit5 to allow the creation of [entry points](entry-points.md) - `sunshine-testng` wraps TestNG to allow the creation of [entry points](entry-points.md) -Artifact|`sunshine-testng`|`sunshine-junit4`|`sunshine-core` ----|---|---|--- -Library|[![](https://img.shields.io/maven-central/v/org.tatools/sunshine-testng.svg)](https://search.maven.org/search?q=g:%22org.tatools%22%20AND%20a:%22sunshine-testng%22)|[![](https://img.shields.io/maven-central/v/org.tatools/sunshine-junit4.svg)](https://search.maven.org/search?q=g:%22org.tatools%22%20AND%20a:%22sunshine-junit4%22)|[![](https://img.shields.io/maven-central/v/org.tatools/sunshine-core.svg)](https://search.maven.org/search?q=g:%22org.tatools%22%20AND%20a:%22sunshine-core%22) -Javadoc|[![](https://www.javadoc.io/badge/org.tatools/sunshine-testng.svg)](https://www.javadoc.io/doc/org.tatools/sunshine-testng)|[![](https://www.javadoc.io/badge/org.tatools/sunshine-junit4.svg)](https://www.javadoc.io/doc/org.tatools/sunshine-junit4)|[![](https://www.javadoc.io/badge/org.tatools/sunshine-core.svg)](https://www.javadoc.io/doc/org.tatools/sunshine-core) +Since `0.4.x` version Sunshine uses `org.tatools` group ID. If you need the earlier version +please use `io.github.tatools` instead. +## `sunshine-core` +Artifacts: +[![](https://img.shields.io/maven-central/v/org.tatools/sunshine-core.svg)](https://search.maven.org/search?q=g:%22org.tatools%22%20AND%20a:%22sunshine-core%22) +[![](https://www.javadoc.io/badge/org.tatools/sunshine-core.svg)](https://www.javadoc.io/doc/org.tatools/sunshine-core) -`sunshine-junit4` was tested on `4.11` version of JUnit4 and `sunshine-testng` on `6.11` version of TestNG. +## `sunshine-junit4` +The library is tested on JUnit4 `4.11`. -Since `0.4.x` version Sunshine uses `org.tatools` group ID. If you need the earlier version -please use `io.github.tatools` instead. +Artifacts: +[![](https://img.shields.io/maven-central/v/org.tatools/sunshine-junit4.svg)](https://search.maven.org/search?q=g:%22org.tatools%22%20AND%20a:%22sunshine-junit4%22) +[![](https://www.javadoc.io/badge/org.tatools/sunshine-junit4.svg)](https://www.javadoc.io/doc/org.tatools/sunshine-junit4) + +## `sunshine-junit5` +Artifacts: +[![](https://img.shields.io/maven-central/v/org.tatools/sunshine-junit5.svg)](https://search.maven.org/search?q=g:%22org.tatools%22%20AND%20a:%22sunshine-junit5%22) +[![](https://www.javadoc.io/badge/org.tatools/sunshine-junit5.svg)](https://www.javadoc.io/doc/org.tatools/sunshine-junit5) + +## `sunshine-testng` +The library is tested on TestNG `6.11`. + +Artifacts: +[![](https://img.shields.io/maven-central/v/org.tatools/sunshine-testng.svg)](https://search.maven.org/search?q=g:%22org.tatools%22%20AND%20a:%22sunshine-testng%22) +[![](https://www.javadoc.io/badge/org.tatools/sunshine-testng.svg)](https://www.javadoc.io/doc/org.tatools/sunshine-testng) diff --git a/docs/md/entry-points.md b/docs/md/entry-points.md index 96cda88..011f94d 100644 --- a/docs/md/entry-points.md +++ b/docs/md/entry-points.md @@ -55,6 +55,12 @@ Class: [`org.tatools.sunshine.junit4.Sunshine`](https://github.com/tatools/sunsh The class exposes the same behavior as default TestNG entry point exposes except the last option. All tests will be executed using JUnit4. +## Default JUnit5 entry point +Class: [`org.tatools.sunshine.junit5.Sunshine`](https://github.com/tatools/sunshine/blob/master/sunshine-junit5/src/main/java/org/tatools/sunshine/junit5/Sunshine.java) + +The class exposes the same behavior as default TestNG entry point exposes except the last option. All tests will be +executed using JUnit5. + ## Code snippets for different use cases ### Specify regex There is a regex which has used to filter classes from current classpath. diff --git a/docs/md/getting-started.md b/docs/md/getting-started.md index a4ae49e..22fc219 100644 --- a/docs/md/getting-started.md +++ b/docs/md/getting-started.md @@ -17,14 +17,13 @@ According to [Sunshine's concept](concept.md), the code and automated tests have So, please move everything to `src/main` except unit tests. ## Step 2: Add Sunshine library -Sunshine's library has to be selected based on a test runner is used by the project. -Please refer to -[![](https://img.shields.io/maven-central/v/org.tatools/sunshine-testng.svg?label=sunshine-testng)](https://search.maven.org/search?q=g:%22org.tatools%22%20AND%20a:%22sunshine-testng%22) -if you use TestNG or -[![](https://img.shields.io/maven-central/v/org.tatools/sunshine-junit4.svg?label=sunshine-junit4)](https://search.maven.org/search?q=g:%22org.tatools%22%20AND%20a:%22sunshine-junit4%22) -for JUnit4. +Based on the xUnit engine you are using, please select an appropriate library and add to your build configuration. +Available libraries: +- [![](https://img.shields.io/maven-central/v/org.tatools/sunshine-testng.svg?label=sunshine-testng)](https://search.maven.org/search?q=g:%22org.tatools%22%20AND%20a:%22sunshine-testng%22) +- [![](https://img.shields.io/maven-central/v/org.tatools/sunshine-junit4.svg?label=sunshine-junit4)](https://search.maven.org/search?q=g:%22org.tatools%22%20AND%20a:%22sunshine-junit4%22) +- [![](https://img.shields.io/maven-central/v/org.tatools/sunshine-junit5.svg?label=sunshine-junit5)](https://search.maven.org/search?q=g:%22org.tatools%22%20AND%20a:%22sunshine-junit5%22) -To find out more references please visit [artifacts page](artifacts.md). +To find out more references, please visit [artifacts page](artifacts.md). ## Step 3: Configure code packaging The recommended packaging is an `uber-JAR` - also known as a `fat JAR` or `JAR with dependencies` - diff --git a/settings.gradle b/settings.gradle index 2909e6b..38566ae 100644 --- a/settings.gradle +++ b/settings.gradle @@ -2,3 +2,4 @@ rootProject.name = 'sunshine' include 'sunshine-core-deps', 'sunshine-core' include 'sunshine-testng', 'sunshine-testng-integration-tests' include 'sunshine-junit4', 'sunshine-junit4-integration-tests' +include 'sunshine-junit5' diff --git a/sunshine-junit5/build.gradle b/sunshine-junit5/build.gradle new file mode 100644 index 0000000..ad97a1d --- /dev/null +++ b/sunshine-junit5/build.gradle @@ -0,0 +1,15 @@ +dependencies { + compileOnly 'org.projectlombok:lombok:1.16.14' + annotationProcessor 'org.projectlombok:lombok:1.16.14' + testCompileOnly 'org.projectlombok:lombok:1.16.14' + testAnnotationProcessor 'org.projectlombok:lombok:1.16.14' + compile 'org.junit.jupiter:junit-jupiter:5.5.2' + compile 'org.junit.platform:junit-platform-launcher:1.5.2' + testCompile 'org.hamcrest:hamcrest-all:1.3' +} + +task ready(dependsOn: check) { + doLast { + println("Unit testing of Sunshine Junit5 is completed.") + } +} diff --git a/sunshine-junit5/gradle.properties b/sunshine-junit5/gradle.properties new file mode 100644 index 0000000..899e6d6 --- /dev/null +++ b/sunshine-junit5/gradle.properties @@ -0,0 +1,2 @@ +NAME="Sunshine JUnit5 adapter" +DESCRIPTION="The package is Sunshine's adapter for JUnit5 tests runner." diff --git a/sunshine-junit5/src/main/java/org/tatools/sunshine/junit5/Junit5Kernel.java b/sunshine-junit5/src/main/java/org/tatools/sunshine/junit5/Junit5Kernel.java new file mode 100644 index 0000000..4dcca65 --- /dev/null +++ b/sunshine-junit5/src/main/java/org/tatools/sunshine/junit5/Junit5Kernel.java @@ -0,0 +1,84 @@ +package org.tatools.sunshine.junit5; + +import org.junit.platform.engine.DiscoverySelector; +import org.junit.platform.engine.discovery.DiscoverySelectors; +import org.junit.platform.launcher.Launcher; +import org.junit.platform.launcher.TestExecutionListener; +import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder; +import org.junit.platform.launcher.core.LauncherFactory; +import org.junit.platform.launcher.listeners.SummaryGeneratingListener; +import org.tatools.sunshine.core.*; + +/** + * The class provides a {@link Kernel} implementation of JUnit 5 runner. + * + * @author Dmytro Serdiuk + * @version $Id$ + */ +public class Junit5Kernel implements Kernel { + + private final Launcher launcher; + private final SunshineSuite tests; + private final SummaryGeneratingListener reporter; + + /** + * Initializes a newly created {@link Junit5Kernel} object so that it represents + * an JUnit4 runner. + * + * @param sunshineSuite the suite with desired tests + */ + public Junit5Kernel(SunshineSuite sunshineSuite) { + this(LauncherFactory.create(), sunshineSuite); + } + + /** + * Initializes a newly created {@link Junit5Kernel} object so that it represents + * an JUnit4 runner. + * + * @param launcher the launcher for a given test suite + * @param sunshineSuite the suite with desired tests + */ + private Junit5Kernel(Launcher launcher, SunshineSuite sunshineSuite) { + this.tests = sunshineSuite; + this.launcher = launcher; + this.reporter = new SummaryGeneratingListener(); + this.launcher.registerTestExecutionListeners(this.reporter); + } + + /** + * Returns a status of JUnite 5 tests execution. + * + * @return the status for the current execution + * @throws KernelException if any error occurs during JUnit tests execution + */ + @Override + public final Status status() throws KernelException { + try { + launcher.execute( + LauncherDiscoveryRequestBuilder.request() + .selectors( + tests.tests().stream().map( + sunshineTest -> DiscoverySelectors.selectClass(sunshineTest.toString()) + ).toArray(DiscoverySelector[]::new) + ).build() + ); + return new Junit5Status(this.reporter.getSummary()); + } catch (SuiteException e) { + throw new KernelException("Some problem occurs in the Junit5Kernel", e); + } + } + + /** + * Returns a new instance of the JUnit 5 kernel with provided listeners based + * on the current instance configuration. + * + * @param testExecutionListeners at least one desired listener + * @return the new instance of the JUnit 5 kernel + */ + @Override + public final Kernel with(TestExecutionListener... testExecutionListeners) { + final Launcher fork = LauncherFactory.create(); + fork.registerTestExecutionListeners(testExecutionListeners); + return new Junit5Kernel(fork, this.tests); + } +} diff --git a/sunshine-junit5/src/main/java/org/tatools/sunshine/junit5/Junit5Status.java b/sunshine-junit5/src/main/java/org/tatools/sunshine/junit5/Junit5Status.java new file mode 100644 index 0000000..de8e7c3 --- /dev/null +++ b/sunshine-junit5/src/main/java/org/tatools/sunshine/junit5/Junit5Status.java @@ -0,0 +1,47 @@ +package org.tatools.sunshine.junit5; + +import org.junit.platform.launcher.listeners.SummaryGeneratingListener; +import org.junit.platform.launcher.listeners.TestExecutionSummary; +import org.tatools.sunshine.core.Status; + +/** + * The class provides an implementation of the {@link Status} for JUnit 5 execution. + * + * @author Dmytro Serdiuk + * @version $Id$ + */ +public class Junit5Status implements Status { + private final TestExecutionSummary summary; + private final short passed = 0; + private final short failed = 1; + + /** + * Initializes a newly created instance to represent a status of + * JUnit 5 execution. + * + * @param testExecutionSummary the report provided by {@link SummaryGeneratingListener} + */ + public Junit5Status(TestExecutionSummary testExecutionSummary) { + this.summary = testExecutionSummary; + } + + @Override + public final short code() { + return this.summary.getTotalFailureCount() == 0 ? this.passed : this.failed; + } + + @Override + public final int runCount() { + return Math.toIntExact(this.summary.getTestsFoundCount()); + } + + @Override + public final int failureCount() { + return Math.toIntExact(this.summary.getTestsFailedCount()); + } + + @Override + public final int ignoreCount() { + return Math.toIntExact(this.summary.getTestsSkippedCount()); + } +} diff --git a/sunshine-junit5/src/main/java/org/tatools/sunshine/junit5/Sunshine.java b/sunshine-junit5/src/main/java/org/tatools/sunshine/junit5/Sunshine.java new file mode 100644 index 0000000..975445e --- /dev/null +++ b/sunshine-junit5/src/main/java/org/tatools/sunshine/junit5/Sunshine.java @@ -0,0 +1,28 @@ +package org.tatools.sunshine.junit5; + + +import org.tatools.sunshine.core.*; + +/** + * The {@link Sunshine} class is a main class to run JUnit 5 tests. + * + * @author Dmytro Serdiuk + * @version $Id$ + */ +public final class Sunshine { + + public static void main(String[] args) { + new Sun( + new Junit5Kernel( + new SunshineSuitePrintable( + new SunshineSuiteFilterable( + new SuiteFromFileSystem( + new FileSystemOfClasspathClasses() + ), + new VerboseRegex(new RegexCondition()) + ) + ) + ) + ).shine(); + } +} diff --git a/sunshine-junit5/src/main/java/org/tatools/sunshine/junit5/package-info.java b/sunshine-junit5/src/main/java/org/tatools/sunshine/junit5/package-info.java new file mode 100644 index 0000000..6522fd8 --- /dev/null +++ b/sunshine-junit5/src/main/java/org/tatools/sunshine/junit5/package-info.java @@ -0,0 +1,7 @@ +/** + * The main package of the integration with JUnit 5. + * + * @author Dmytro Serdiuk + * @version $Id$ + */ +package org.tatools.sunshine.junit5; diff --git a/sunshine-junit5/src/test/java/org/tatools/sunshine/junit5/Junit5KernelTest.java b/sunshine-junit5/src/test/java/org/tatools/sunshine/junit5/Junit5KernelTest.java new file mode 100644 index 0000000..2eeec2a --- /dev/null +++ b/sunshine-junit5/src/test/java/org/tatools/sunshine/junit5/Junit5KernelTest.java @@ -0,0 +1,47 @@ +package org.tatools.sunshine.junit5; + +import lombok.EqualsAndHashCode; +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Test; +import org.junit.platform.engine.TestExecutionResult; +import org.junit.platform.launcher.TestExecutionListener; +import org.junit.platform.launcher.TestIdentifier; +import org.tatools.sunshine.core.KernelException; + +import java.util.ArrayList; + + +/** + * @author Dmytro Serdiuk + * @version $Id$ + */ +public class Junit5KernelTest { + + @Test + public void run() throws KernelException { + MatcherAssert.assertThat( + new Junit5Kernel(ArrayList::new).status().code(), + Matchers.equalTo((short) 0) + ); + } + + + @Test + public void with() throws KernelException { + final Listener l1 = new Listener(); + final Listener l2 = new Listener(); + new Junit5Kernel(ArrayList::new).with(l1).with(l2).status(); + MatcherAssert.assertThat(l1, Matchers.not(Matchers.equalTo(l2))); + } + + @EqualsAndHashCode + private final class Listener implements TestExecutionListener { + private int call = 0; + + @Override + public void executionFinished(TestIdentifier testIdentifier, TestExecutionResult testExecutionResult) { + this.call = 1; + } + } +} diff --git a/sunshine-junit5/src/test/java/org/tatools/sunshine/junit5/Junit5StatusTest.java b/sunshine-junit5/src/test/java/org/tatools/sunshine/junit5/Junit5StatusTest.java new file mode 100644 index 0000000..ee86f9a --- /dev/null +++ b/sunshine-junit5/src/test/java/org/tatools/sunshine/junit5/Junit5StatusTest.java @@ -0,0 +1,160 @@ +package org.tatools.sunshine.junit5; + +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Test; +import org.junit.platform.launcher.listeners.TestExecutionSummary; + +import java.io.PrintWriter; +import java.util.List; + +/** + * @author Dmytro Serdiuk + * @version $Id$ + */ +class Junit5StatusTest { + + @Test + void codePassed() { + MatcherAssert.assertThat( + new Junit5Status(new Summary(1, 0, 0, 0)).code(), + Matchers.is((short)0) + ); + } + + @Test + void codeFailed() { + MatcherAssert.assertThat( + new Junit5Status(new Summary(1, 0, 0, 1)).code(), + Matchers.is((short)1) + ); + } + + @Test + void runCount() { + MatcherAssert.assertThat( + new Junit5Status(new Summary(5, 4, 3, 1)).runCount(), + Matchers.is(5) + ); + } + + @Test + void failureCount() { + MatcherAssert.assertThat( + new Junit5Status(new Summary(5, 4, 3, 1)).failureCount(), + Matchers.is(4) + ); + } + + @Test + void ignoreCount() { + MatcherAssert.assertThat( + new Junit5Status(new Summary(5, 4, 3, 1)).ignoreCount(), + Matchers.is(3) + ); + } + + private final class Summary implements TestExecutionSummary { + private final long totalTests; + private final long failedCount; + private final long skippedCount; + private final long totalFailedCount; + + public Summary(long totalTests, long failedCount, long skippedCount, long totalFailedCount) { + this.totalTests = totalTests; + this.failedCount = failedCount; + this.skippedCount = skippedCount; + this.totalFailedCount = totalFailedCount; + } + + @Override + public long getTimeStarted() { + return 0; + } + + @Override + public long getTimeFinished() { + return 0; + } + + @Override + public long getTotalFailureCount() { + return this.totalFailedCount; + } + + @Override + public long getContainersFoundCount() { + return 0; + } + + @Override + public long getContainersStartedCount() { + return 0; + } + + @Override + public long getContainersSkippedCount() { + return 0; + } + + @Override + public long getContainersAbortedCount() { + return 0; + } + + @Override + public long getContainersSucceededCount() { + return 0; + } + + @Override + public long getContainersFailedCount() { + return 0; + } + + @Override + public long getTestsFoundCount() { + return this.totalTests; + } + + @Override + public long getTestsStartedCount() { + return 0; + } + + @Override + public long getTestsSkippedCount() { + return this.skippedCount; + } + + @Override + public long getTestsAbortedCount() { + return 0; + } + + @Override + public long getTestsSucceededCount() { + return 0; + } + + @Override + public long getTestsFailedCount() { + return this.failedCount; + } + + @Override + public void printTo(PrintWriter writer) { + + } + + @Override + public void printFailuresTo(PrintWriter writer) { + + } + + @Override + public List getFailures() { + return null; + } + } +}