Write BDD tests in javadocs!
public class MathUtils {
/**
* Calculate square of x.
*
* <pre><code lang="spock">
* def "returns square"() {
* expect:
* MathUtils.sqr(2) == 4
* }
* </code></pre>
*
* <pre><code lang="gherkin">
* Feature: square calculation
* Scenario Outline: integers
* When input value equals <x>
* Then result should be <sqr>
* Examples:
* | x | sqr |
* | 0 | 0 |
* | 1 | 1 |
* | 2 | 4 |
* |-1 | 1 |
* | 10| 100 |
* | 11| 121 |
* </code></pre>
*/
public static int sqr(int x) {
return x * x;
}
}
jdoc-test is a framework for javadoc sourced java tests.
Javadoc writing is cumbersome. Documentation quickly becomes outdated. There is no guarantee that code does what documentation says. Developers often prefer to write tests instead of documentation. Tests never lie.
So why not just write tests in documentation? BDD frameworks use test specifications written in (more or less) human language. Such documentation goes in sync with actual code and shows code usage example. Java code, tests and documentation become tightly coupled by putting BDD specification in javadoc.
jdoc-spock jupiter engine library runs spockframework test specifications written in javadocs.
jdoc-spock-gradle-plugin gradle plugin automates spockframework specs generation and testing.
jdoc-cucumber jupiter engine library runs gherkin features written in javadocs.
jdoc-cucumber-gradle-plugin gradle plugin automates cucumber feature generation and testing.
⚠️ Library tests itself using itself executing own jdoc-spock
tests written in javadocs.
Yes, see jdoc-spock
and jdoc-cucumber
test examples in source code.
<dependency>
<groupId>io.github.boolivar.jdoctest</groupId>
<artifactId>jdoc-spock</artifactId>
<version>0.9.0</version>
<scope>test</scope>
</dependency>
jdoc-spock
tests.jdoc-spock
contains junit platform engine to run tests. It considers text in javadoc or block comment between <code lang="spock">
</code>
tags as spock specification code.
Additional non-mandatory <pre>
tag keeps code formatting for javadoc presentation:
/**
* <pre><code lang="spock">
* def "Calling delegate bar method"() {
* when:
* $target.foo()
* then:
* 1 * delegate.bar()
* }
* </code></pre>
*/
public void foo() {
delegate.bar();
}
jdoc-spock
dependency.build.gradle
example:
repositories {
mavenCentral()
}
dependencies {
testRuntimeOnly "io.github.boolivar.jdoctest:jdoc-spock:0.9.0"
}
[!IMPORTANT]
jdoc-spock
versions before0.9.0
available only on jitpack.repositories { maven { url "https://jitpack.io" } } dependencies { testRuntimeOnly "com.github.boolivar.jdoc-test:jdoc-spock:0.8.1" }
javac
-parameters
argument.build.gradle
example:
compileJava {
options.compilerArgs << "-parameters"
}
jdoc-spock
uses constructor argument names to generate fields in specification initialized with mocks.
$target
field of spock specification is initialized with instance of class under test (instance of primary class in java file where jdoc-spock specification is located).
jdoc-spock
searches for biggest constructor with mockable (non-final class) arguments and creates mock for each constructor argument. Mocks stored in spec fields using corresponding names.
As an example for java class:
public class Foo {
private final Bar delegate;
public Foo(Bar delegate) {
this.delegate = delegate;
}
}
generated spock fields will be:
def delegate = Mock(Bar)
def $target = new Foo(delegate)
@SelectDirectories
or @SelectFile
:import org.junit.platform.suite.api.IncludeEngines;
import org.junit.platform.suite.api.SelectDirectories;
import org.junit.platform.suite.api.Suite;
@Suite
@IncludeEngines("jdoc-spock")
@SelectDirectories("src/main/java")
public class JdocSpockTestSuite {
}
jdoc-spock supports platform engine DiscoverySelector
and FileSelector
.
Optionally comma-separated paths to java sources can be provided using either jdoc.spock.test-dirs
or jdoc.spock.test-files
junit platform Configuration Parameters.
build.gradle
example:
test {
useJUnitPlatform()
systemProperties = ["jdoc.spock.test-dirs" : sourceSets.main.java.srcDirs.join(",")]
}
jdoc-spock
junit engine.gradle
example:
gradle test
<dependency>
<groupId>io.github.boolivar.jdoctest</groupId>
<artifactId>jdoc-cucumber</artifactId>
<version>0.9.0</version>
<scope>test</scope>
</dependency>
<code lang="gherkin">
tag:/**
* <pre><code lang="gherkin">
* Feature: foo() invokes bar()
* Scenario: invoke foo()
* When invoke foo()
* Then bar() invoked
* </code></pre>
*/
public class Foo {
private final Bar bar;
public Foo(Bar bar) {
this.bar = bar;
}
public void foo() {
bar.bar();
}
}
build.gradle
example:
repositories {
mavenCentral()
}
dependencies {
testRuntimeOnly "io.github.boolivar.jdoctest:jdoc-cucumber:0.9.0"
testImplementation "io.cucumber:cucumber-java:7.17.0"
}
[!IMPORTANT]
jdoc-cucumber
versions before0.9.0
available only on jitpack.
build.gradle
example:repositories { maven { url "https://jitpack.io" } } dependencies { testRuntimeOnly "com.github.boolivar.jdoc-test:jdoc-cucumber:0.8.1" testImplementation "io.cucumber:cucumber-java:7.17.0" }
import io.cucumber.java.en.Then;
import io.cucumber.java.en.When;
import static org.mockito.BDDMockito.*;
public class StepDefinitions {
private final Bar bar = mock(Bar.class);
private final Foo foo = new Foo(bar);
@When("invoke foo()")
public void invokeFoo() {
foo.foo();
}
@Then("bar() invoked")
public void verifyBarInvoked() {
then(bar).should().bar();
}
}
@SelectDirectories
or @SelectFile
and step definitions packageio.cucumber.junit.platform.engine.Constants.GLUE_PROPERTY_NAME
configuration parameter:import org.junit.platform.suite.api.ConfigurationParameter;
import org.junit.platform.suite.api.IncludeEngines;
import org.junit.platform.suite.api.SelectDirectories;
import org.junit.platform.suite.api.Suite;
import static io.cucumber.junit.platform.engine.Constants.GLUE_PROPERTY_NAME;
@Suite
@IncludeEngines("jdoc-cucumber")
@SelectDirectories("src/main/java")
@ConfigurationParameter(key = GLUE_PROPERTY_NAME, value = "step.definitions.package")
public class JdocCucumberTestSuite {
}
jdoc-cucumber
junit engine.gradle
example:
gradle test
Gradle plugin available on gradle plugin portal that automates cucumber feature generation and cucumber testing tasks.
build.gradle
:
plugins {
id "java"
id "io.github.boolivar.jdoctest.jdoc-cucumber" version "0.8.1"
}
repositories {
mavenCentral()
}
check.dependsOn jdocCucumberTest
gradle check
build.gradle
example:
jdocCucumber {
gluePackages = ["org.bool.cucumber.stepdefs"]
cucumberVersion = "7.18.1"
sources = sourceSets.custom.java
}
Extension property | Type | Default value | Description |
---|---|---|---|
outputDir |
Directory |
project.layout.buildDirectory.dir("generated/sources/jdoc-cucumber") | Path to store generated features |
langTag |
String |
"gherkin" |
lang tag to parse. Only <code lang="<langTag>"> javadoc blocks will be parsed and written as features |
sources |
SourceDirectorySet |
sourceSets.main.java | Java sources to parse |
cucumberVersion |
String |
"7.17.0" |
io.cucumber:cucumber-java dependency version to register in testImplementation configuration |
gluePackages |
List<String> |
List of packages with cucumber glue code |
When java
plugin is applied to a project, jdoc-cucumber
plugin registers io.cucumber:cucumber-java
dependency in testImplementation
configuration and creates 2 tasks:
JdocCucumberTask
jdocCucumber.outputDir
path.JavaExec
JdocCucumberTask
. Runs cucumber tests using cucumber CLI Runner.[!NOTE] By default
jdocCucumberTest
task is not a dependency forcheck
task. To includejdocCucumberTest
in build this should be configured manually.
build.gradle
example:check.dependsOn jdocCucumberTest
Gradle plugin available on gradle plugin portal that automates spockframework specs generation and testing tasks.
build.gradle
:
plugins {
id "java"
id "io.github.boolivar.jdoctest.jdoc-spock" version "0.8.1"
}
repositories {
mavenCentral()
}
jdocSpockTest {
testLogging {
events "passed", "skipped", "failed"
}
}
check.dependsOn jdocSpockTest
gradle check
When java
plugin is applied to a project, jdoc-spock
plugin:
groovy
pluginjdocSpock
with groovy sources configured to outputDir property of extensionorg.spockframework:spock-core
as implementation dependency for jdocSpock
source setnet.bytebuddy:byte-buddy
and org.objenesis:objenesis
as runtimeOnly dependencies for jdocSpock
source setgenerateSpockSpecs
taskjdocSpockTest
taskcompileJdocSpockGroovy
task to depend on generateSpockSpecs
taskbuild.gradle
example:
jdocSpock {
outputDir = project.layout.buildDirectory.dir("spock-specs")
spockVersion = "2.3-groovy-4.0"
byteBuddyVersion = null
objenesisVersion = null
}
Extension property | Type | Default value | Description |
---|---|---|---|
outputDir |
Directory |
project.layout.buildDirectory.dir("generated/sources/jdoc-spock") | Path to store generated groovy specs |
langTag |
String |
"spock" |
lang tag to parse. Only <code lang="<langTag>"> javadoc blocks will be parsed and included in spec generation |
sources |
SourceDirectorySet |
sourceSets.main.java | Java sources to parse |
classPath |
FileCollection |
sourceSets.main.output | Classpath containing classes under test, used for mockable constructor search. |
spockVersion |
String |
"2.3-groovy-4.0" |
org.spockframework:spock-core dependency version to register in jdocSpockImplementation configuration |
byteBuddyVersion |
String |
"1.14.15" |
net.bytebuddy:byte-buddy dependency version to register in jdocSpockRuntimeOnly configuration, null value will exclude dependency. |
objenesisVersion |
String |
"3.3" |
org.objenesis:objenesis dependency version to register in jdocSpockRuntimeOnly configuration, null value will exclude dependency. |
JdocSpockTask
compileJava
. Generates spockframework test specs from javadocs and stores them in jdocSpock.outputDir
path.Test
[!NOTE] By default
jdocSpockTest
task is not a dependency forcheck
task. To includejdocSpockTest
in build this should be configured manually.
build.gradle
example:check.dependsOn jdocSpockTest