This document describes usage of classes to create test used to generate documentation.
-
ApprovalsExtension: JUnit extension to check document.
-
DocWriter: Store document before writting it.
-
Printer: Utilities for result presentation.
-
SvgGraph: Create a svg graph.
Approvals extension
JUnit5 extension that verify written document matches with approved one.
It checks that everything written during test is identical to the approved content.
Creating a test using ApprovalsExtension
This is an example to create a simple test using ApprovalsExtension.
You have to write a class and add register an ApprovalsExtension attribute using RegisterExtension annotation.
This extension will check that everything wrote using write method has not changed since the last execution.
ApprovalsExtensionpackage com.github.docastest.samples.justone;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import com.github.docastest.doctesting.junitextension.ApprovalsExtension;
import com.github.docastest.doctesting.junitextension.SimpleApprovalsExtension;
public class OneTest {
@RegisterExtension
static final ApprovalsExtension doc = new SimpleApprovalsExtension();
@Test
public void test_A() {
doc.write("In my *test*");
}
}
When executing test method test_A, the following text is generated.
[#com_github_docastest_samples_justone_onetest_test_a] = Test A In my *test*
If this content is identical to the _OneTest.test_A.approved.adoc, then the test is a success.
Otherwise, test fails and the generated text is written to the _OneTest.test_A.received.adoc file.
So we can compare those two files to see what has changed.
File name and title come from method name.
The chapter content contains what was written using write.
Files are stored in src/test/docs/com/github/docastest/samples/justone directory which contains:
-
_OneTest.approved.adoc
-
_OneTest.test_A.approved.adoc
There is one file per test and one file for the class.
Using displayName
You can use DisplayName annotation to customize test title
@DisplayName("Title for the document")
class UsingDisplayNameTest {
@RegisterExtension
static final ApprovalsExtension doc = new SimpleApprovalsExtension();
@Test
@DisplayName("Title for this test")
public void test_A() {
doc.write("In my *test*");
}
}
[#com_github_docastest_doctesting_junitextension_usingdisplaynametest_test_a] = Title for this test In my *test*
Hide title
You can hide a test title adding the NoTitle annotation to the test. It’s usefull to reuse content of a test in different place without keeping the original structure. You also want to separate the content of one chapter through different test but display them under only title.
public class MyTestWithoutTitleOnOneTest {
@RegisterExtension
static final ApprovalsExtension doc = new SimpleApprovalsExtension();
@Test
public void intro() {
doc.write("First line");
}
@Test
@NoTitle
public void my_method() {
doc.write("My content without title");
}
@Test
public void conclusion() {
doc.write("Last line");
}
}
The file generated from the method with NoTitle annotation has no header.
On the final rendering, it’s like it was part of the previous chapter.
Rendering of the result
My test without title on one test
Intro
First line
My content without title
Conclusion
Last line
Using a custom writer
It’s possible to give a specific DocWriter to ApprovalsExtension
and modify what it will be written in the final document.
ApprovalsExtensionclass MyCustomWriterTest {
@RegisterExtension
static final ApprovalsExtension doc = new ApprovalsExtension(
new DocWriter() {
@Override
public void write(String... texts) {
super.write(texts);
super.write(" // Add a comment after each call to write");
}
@Override
public String formatOutput(String title, Method testMethod) {
return "// Add an header to the document\n"
+ super.formatOutput(title + ": Custom title", testMethod);
}
}
);
@Test
public void test_A() {
doc.write("In my *test*");
}
}
When executing test method test_A, a file _MyCustomWriterTest.test_A.approved.adoc is generated and contains the following text
// Add an header to the document [#com_github_docastest_samples_mycustomwritertest_test_a] = Test A: Custom title In my *test* // Add a comment after each call to write
Using a custom formatter
It’s possible to give a specific Formatter to ApprovalsExtension
and change the rendering for some instructions or create another formatter.
ApprovalsExtensionclass MyCustomFormatterTest {
@RegisterExtension
static final ApprovalsExtension doc = new ApprovalsExtension(
new DocWriter(
new AsciidocFormatter() {
@Override
/// Add the word `Warning` before the message.
public String warning(String message) {
return super.warning("Warning: " + message);
}
})
);
@Test
public void test_A() {
doc.write(doc.getFormatter().warning("My custom warning."));
}
}
When executing test method test_A, a file _MyCustomFormatterTest.test_A.approved.adoc is generated and contains the following text
[#com_github_docastest_samples_mycustomformattertest_test_a] = Test A [WARNING] ==== Warning: My custom warning. ====
Nested class
Nested class can be used to organize tests. Each nested class create a nested title.
/**
* Demo of a simple usage to generate documentation.
*/
public class DemoNestedTest {
@RegisterExtension
static final ApprovalsExtension writer = new SimpleApprovalsExtension();
/**
* Document of Addition operations.
*/
@Nested
class Adding {
@Test
@DisplayName("Adding 2 simple numbers")
public void should_be_5_when_adding_2_and_3() {
writer.write(String.format("%d + %d = %d", 2, 3, 2 + 3));
}
/**
* A nested test.
*/
@Test
@DisplayName("Adding 3 simple numbers")
public void should_be_9_when_adding_2_3_and_4() {
writer.write(String.format("%d + %d + %d = %d", 2, 3, 4, 2 + 3 + 4));
}
}
@Nested
class Multiply {
@Test
@DisplayName("Multiply 2 simple numbers")
public void should_be_12_when_multiply_4_and_3() {
writer.write(String.format("%d * %d = %d", 4, 3, 4 * 3));
}
}
}
Generated files in com/github/docastest/doctesting/junitextension:
-
_DemoNestedTest.Adding.should_be_5_when_adding_2_and_3.approved.adoc
-
_DemoNestedTest.Adding.should_be_9_when_adding_2_3_and_4.approved.adoc
-
_DemoNestedTest.Multiply.should_be_12_when_multiply_4_and_3.approved.adoc
-
_DemoNestedTest.approved.adoc
[#com_github_docastest_doctesting_junitextension_demonestedtest] = Demo nested test Demo of a simple usage to generate documentation. [#com_github_docastest_doctesting_junitextension_demonestedtest_adding] == Adding Document of Addition operations. include::_DemoNestedTest.Adding.should_be_5_when_adding_2_and_3.approved.adoc[leveloffset=+2] include::_DemoNestedTest.Adding.should_be_9_when_adding_2_3_and_4.approved.adoc[leveloffset=+2] [#com_github_docastest_doctesting_junitextension_demonestedtest_multiply] == Multiply include::_DemoNestedTest.Multiply.should_be_12_when_multiply_4_and_3.approved.adoc[leveloffset=+2]
Final rendering
Rendering
Demo nested test
Demo of a simple usage to generate documentation.
Adding
Document of Addition operations.
2 + 3 = 5
A nested test.
2 + 3 + 4 = 9
Multiply
4 * 3 = 12
Document with all tests in a testclass
At the end of a test, a file is created including files generated on each test.
ApprovalsExtension must be static to be able to run AfterAll callback.
class MyTest {
@RegisterExtension
static final ApprovalsExtension doc = new SimpleApprovalsExtension();
@Test
public void test_A() {
doc.write("In my *test*");
}
}
Class MyTest is in package com.github.docastest.samples.
ifndef::ROOT_PATH[:ROOT_PATH: ../../../..] [#com_github_docastest_samples_mytest] = My test include::_MyTest.test_A.approved.adoc[leveloffset=+1]
Final rendering
My test
Test A
In my test
Failing test output
When a test fails, the error is written in the final document. It’s help to understand and investigate on the problem.
When the test fails, the reason (exception) is written into the generated document.
class FailingTest {
@RegisterExtension
static final ApprovalsExtension doc = new SimpleApprovalsExtension();
@Test
public void failing_test() {
doc.write("Some information before failure.", "", "");
fail("Problem on the test, it fails.");
doc.write("Information added after failure are not in the final document.", "");
}
}
ifndef::ROOT_PATH[:ROOT_PATH: ../../../..] [#com_github_docastest_samples_failingtest_failing_test] = Failing test Some information before failure. *Error generating documentation* ---- org.opentest4j.AssertionFailedError: Problem on the test, it fails. at org.junit.jupiter.api.AssertionUtils.fail(AssertionUtils.java:39) at org.junit.jupiter.api.Assertions.fail(Assertions.java:134) at com.github.docastest.samples.FailingTest.failing_test(FailingTest.java:23) ... ----
Final rendering
Failing test
Some information before failure.
Error generating documentation
org.opentest4j.AssertionFailedError: Problem on the test, it fails. at org.junit.jupiter.api.AssertionUtils.fail(AssertionUtils.java:39) at org.junit.jupiter.api.Assertions.fail(Assertions.java:134) at com.github.docastest.samples.FailingTest.failing_test(FailingTest.java:23) ...
A received file is produced for the class containing the failing test. This file includes received files if they exist. It is thus possible to visualize the received file in the file with other methods.
ifndef::ROOT_PATH[:ROOT_PATH: ../../../..] [#com_github_docastest_samples_failingtest] = Failing test include::_FailingTest.failing_test.received.adoc[leveloffset=+1]
Failure report
Report when files are differents
When the received file is not the same as the approved file, first different line is displayed on report.
= Title Description Line from received text Footer received
= Title Description Line from approved text Footer approved
Differences between files:
Approved: [TEMPORARY FOLDER]/files/approved.adoc
Received: [TEMPORARY FOLDER]/files/received.adoc
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
3: Line from approved text
=================================================================
3: Line from received text
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
Report when files are identicals
When the received file is identical to the approved file, it’s indicated it. But this report is normally not displayed when files are identical.
= Title Description
= Title Description
Files are identical:
Approved: [TEMPORARY FOLDER]/files/approved.adoc
Received: [TEMPORARY FOLDER]/files/received.adoc
Received file shorter than approved file
When the received file is shorter than the approved file, We indicate that there is no more lines.
= Title Description
= Title Description Footer
Differences between files:
Approved: [TEMPORARY FOLDER]/files/approved.adoc
Received: [TEMPORARY FOLDER]/files/received.adoc
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
3: Footer
=================================================================
3 (no line)
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
Received file longer than approved file
When the received file is longer than the approved file, We indicate that there is no more lines.
= Title Description Footer
= Title Description
Differences between files:
Approved: [TEMPORARY FOLDER]/files/approved.adoc
Received: [TEMPORARY FOLDER]/files/received.adoc
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
3 (no line)
=================================================================
3: Footer
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
Approved file does not exist
We indicate a specific message when approved file does not exist.
= Title Description Footer
No approved file yet
Classes order
In order
We retrieve names of methods and nested classes in the same order they appear in source file.
Class<?> testClass = MyClass.class;
Stream<ClassesOrder.EncapsulateDeclared> declaredInOrder
= new ClassesOrder().getDeclaredInOrder(testClass);
Only methods and classes directly under the class passed in parameter are returned.
-
method_1
-
MySubClassA
-
method_2
-
MySubClassB
-
method_3
class MyClass {
public void method_1() {
}
class MySubClassA {
public void method_A_1() {
}
}
public void method_2() {
}
class MySubClassB {
public void method_B_1() {
}
}
public void method_3() {
}
}
Filter methods and classes
We can give filters to select methods and classes to return.
-
method_1
-
MySubClassB
class MyClass {
public void method_1() {
}
class MySubClassA {
public void method_A_1() {
}
}
public void method_2() {
}
class MySubClassB {
public void method_B_1() {
}
}
public void method_3() {
}
}
Nested class using outer method
A nested class calling an outer method generate some special method that are not in source. Those methods should not be in sorted method list
-
methodOfClassAtFirstLevel
-
MySubClassA
static class MyClassWithOnlySubClassExterne {
private void methodOfClassAtFirstLevel() {
}
class MySubClassA {
public void method_A_1() {
methodOfClassAtFirstLevel();
}
}
}
Configuration
Available properties
-
SOURCE_PATH: Path of source code.
-
TEST_PATH: Path of test code.
-
DOC_PATH: Path where documentation is generated.
-
RESOURCE_PATH: Path of resources.
-
FORMATTER: Full qualified name of the formatter class.
Default values
By default, values are:
| Key | Type | Value |
|---|---|---|
SOURCE_PATH |
Path |
src/main/java |
TEST_PATH |
Path |
src/test/java |
DOC_PATH |
Path |
src/test/docs |
RESOURCE_PATH |
Path |
src/test/resources |
FORMATTER |
Formatter |
com.github.docastest.docformatter.asciidoc.AsciidocFormatter |
Configuration file
You can change those folders by creating a properties file named docAsTest.properties and put it in a resource folder.
When this file contains:
SOURCE_PATH:source/java TEST_PATH:source/test DOC_PATH:source/docs FORMATTER:com.github.docastest.samples.MyFormatter
Values are:
| Key | Type | Value |
|---|---|---|
SOURCE_PATH |
Path |
source/java |
TEST_PATH |
Path |
source/test |
DOC_PATH |
Path |
source/docs |
RESOURCE_PATH |
Path |
src/test/resources |
FORMATTER |
Formatter |
com.github.docastest.samples.MyFormatter |
When config file does not exist, values are the default ones.
Document path
One of the essential points to create a documentation, is links between files. It’s even more important with documentation as test. For a java method, there is the java file, the approved and received files, and the html file. We need to easily create paths to those files. We also need to reference one file from another one either to include it or to make a link to another page.
This class helps to navigate between all that files.
Available paths from DocPath
DocPath contains the information defining the location of the item to be documented.
It’s not a real path but just the location in the tree of documents.
From this class, we can generate the real paths to the different kinds of associated documents.
We can create a DocPath the code below (where MyTest is declared in package com.github.docastest.samples).
Class<?> clazz = MyTest.class;
final DocPath docPath = new DocPath(clazz);
| Kind of document | Method called onePath.path() | Description |
|---|---|---|
page() |
src/test/docs/com/github/docastest/samples/MyTest.adoc |
File to create a page in documentation. |
approved() |
src/test/docs/com/github/docastest/samples/_MyTest.approved.adoc |
The approved file. |
received() |
src/test/docs/com/github/docastest/samples/_MyTest.received.adoc |
The received file. |
test() |
src/test/java/com/github/docastest/samples/MyTest.java |
The java source file. |
html() |
com/github/docastest/samples/MyTest.html |
The final rendered file. |
Path by type
DocPath is created with this code:
final DocPath docPath = new DocPath(MyTest.class);
Note that MyTest class is declared in package com.github.docastest.samples.
We also used com.github.docastest.doctesting.utils.DocPathTest class to display from and to results with approved file (src/test/docs/com/github/docastest/doctesting/utils/_DocPathTest.approved.adoc).
| Method | Result |
|---|---|
name() |
MyTest |
| Code | Method | Result |
|---|---|---|
|
path() |
src/test/docs/com/github/docastest/samples/MyTest.adoc |
folder() |
src/test/docs/com/github/docastest/samples |
|
filename() |
MyTest.adoc |
|
from() |
../../samples/MyTest.adoc |
|
to() |
../doctesting/utils/_DocPathTest.approved.adoc |
|
|
path() |
src/test/docs/com/github/docastest/samples/_MyTest.approved.adoc |
folder() |
src/test/docs/com/github/docastest/samples |
|
filename() |
_MyTest.approved.adoc |
|
from() |
../../samples/_MyTest.approved.adoc |
|
to() |
../doctesting/utils/_DocPathTest.approved.adoc |
|
|
path() |
src/test/docs/com/github/docastest/samples/_MyTest.received.adoc |
folder() |
src/test/docs/com/github/docastest/samples |
|
filename() |
_MyTest.received.adoc |
|
from() |
../../samples/_MyTest.received.adoc |
|
to() |
../doctesting/utils/_DocPathTest.approved.adoc |
|
|
path() |
src/test/java/com/github/docastest/samples/MyTest.java |
folder() |
src/test/java/com/github/docastest/samples |
|
filename() |
MyTest.java |
|
from() |
../../../../../../java/com/github/docastest/samples/MyTest.java |
|
to() |
../../../../../docs/com/github/docastest/doctesting/utils/_DocPathTest.approved.adoc |
|
|
path() |
src/test/resources/com/github/docastest/samples/MyTest.adoc |
folder() |
src/test/resources/com/github/docastest/samples |
|
filename() |
MyTest.adoc |
|
from() |
../../../../../../resources/com/github/docastest/samples/MyTest.adoc |
|
to() |
../../../../../docs/com/github/docastest/doctesting/utils/_DocPathTest.approved.adoc |
|
|
path() |
com/github/docastest/samples/MyTest.html |
folder() |
com/github/docastest/samples |
|
filename() |
MyTest.html |
|
from() |
../../../../../../../../com/github/docastest/samples/MyTest.html |
|
to() |
../../../../src/test/docs/com/github/docastest/doctesting/utils/_DocPathTest.approved.adoc |
Path by type with method
DocPath is created with this code:
final DocPath docPath = new DocPath(MethodReference.getMethod(MyTest::test_A));
Note that MyTest class is declared in package com.github.docastest.samples.
We also used com.github.docastest.doctesting.utils.DocPathTest class to display from and to results with approved file (src/test/docs/com/github/docastest/doctesting/utils/_DocPathTest.approved.adoc).
| Method | Result |
|---|---|
name() |
MyTest.test_A |
| Code | Method | Result |
|---|---|---|
|
path() |
src/test/docs/com/github/docastest/samples/MyTest.test_A.adoc |
folder() |
src/test/docs/com/github/docastest/samples |
|
filename() |
MyTest.test_A.adoc |
|
from() |
../../samples/MyTest.test_A.adoc |
|
to() |
../doctesting/utils/_DocPathTest.approved.adoc |
|
|
path() |
src/test/docs/com/github/docastest/samples/_MyTest.test_A.approved.adoc |
folder() |
src/test/docs/com/github/docastest/samples |
|
filename() |
_MyTest.test_A.approved.adoc |
|
from() |
../../samples/_MyTest.test_A.approved.adoc |
|
to() |
../doctesting/utils/_DocPathTest.approved.adoc |
|
|
path() |
src/test/docs/com/github/docastest/samples/_MyTest.test_A.received.adoc |
folder() |
src/test/docs/com/github/docastest/samples |
|
filename() |
_MyTest.test_A.received.adoc |
|
from() |
../../samples/_MyTest.test_A.received.adoc |
|
to() |
../doctesting/utils/_DocPathTest.approved.adoc |
|
|
path() |
src/test/java/com/github/docastest/samples/MyTest.test_A.java |
folder() |
src/test/java/com/github/docastest/samples |
|
filename() |
MyTest.test_A.java |
|
from() |
../../../../../../java/com/github/docastest/samples/MyTest.test_A.java |
|
to() |
../../../../../docs/com/github/docastest/doctesting/utils/_DocPathTest.approved.adoc |
|
|
path() |
com/github/docastest/samples/MyTest.test_A.html |
folder() |
com/github/docastest/samples |
|
filename() |
MyTest.test_A.html |
|
from() |
../../../../../../../../com/github/docastest/samples/MyTest.test_A.html |
|
to() |
../../../../src/test/docs/com/github/docastest/doctesting/utils/_DocPathTest.approved.adoc |
Nested class
Name for nested class com.github.docastest.doctesting.sample.MyClass$MySubClass is MyClass.MySubClass.
Name for method doSomething in nested class com.github.docastest.doctesting.sample.MyClass$MySubClass is MyClass.MySubClass.doSomething.
Make path independent of operating system
Path in asciidoc files must used '/' independently of operating system and file separator.
It’s important to always generate the same reference file (.adoc) because we compare it with the last generated one. Otherwise, the test could fail when executed on another operating system.
Path path = Paths.get("src", "main", "java");
String asciiDocPath = DocPath.toAsciiDoc(path);
asciiDocPath = src/main/java
Build a path
You can create a DocPath using one of the constructor available.
With one of this code:
new DocPath(Paths.get(""), "DocPathTest")
new DocPath("DocPathTest")
Approved file is:
src/test/docs/_DocPathTest.approved.adoc
With one of this code:
new DocPath(DocPathTest.class)
new DocPath(DocPathTest.class.getPackage(), "DocPathTest")
new DocPath(Paths.get("com", "github", "docastest", "doctesting", "utils"), "DocPathTest")
Approved file is:
src/test/docs/com/github/docastest/doctesting/utils/_DocPathTest.approved.adoc
DocWriter
Usage
DocWriter is just a buffer. Everything wrote in DocWriter will be returned when asking for output. The output is composed with a title, the comment of the method (without params). An id is also added above the title to be able to apply a specific style in the chapter if needed.
public void simple_method() {
}
final DocWriter doc = new DocWriter();
doc.write(
"Some text added to show DocWriter output.",
"Multiple lines can be added."
);
final String output = doc.formatOutput(
"My title",
getClass().getMethod("simple_method")
);
[#com_github_docastest_doctesting_utils_docwritertest_simple_method] = My title Some text added to show DocWriter output. Multiple lines can be added.
Output with a class
DocWriter is also used to format output of a test class. In that case, we add a title and include all test files of the class. What is written on DocWriter is not used in this case.
public class MyTestWithTests {
@RegisterExtension
static final ApprovalsExtension doc = new SimpleApprovalsExtension();
@Test
public void test_A() {
}
@Test
public void test_B() {
}
@Test
public void test_C() {
}
}
final DocWriter doc = new DocWriter();
final Class<?> clazz = MyTestWithTests.class;
final String output = doc.formatOutput(clazz);
[#com_github_docastest_samples_mytestwithtests] = My test with tests include::_MyTestWithTests.test_A.approved.adoc[leveloffset=+1] include::_MyTestWithTests.test_B.approved.adoc[leveloffset=+1] include::_MyTestWithTests.test_C.approved.adoc[leveloffset=+1]
Hide title
If you don’t want the default title in the generated file, add @NoTitle annotation. It can be useful when you want to include this file in another test that have its own title for example.
public class MyTestWithoutTitle {
@Test
@NoTitle
public void my_method() {
// my doc generation
}
}
final DocWriter doc = new DocWriter();
String output = String.join("\n",
"Some text added to show DocWriter output.",
doc.formatOutput(
"This title will not be displayed",
MyTestWithoutTitle.class.getMethod("my_method")),
"Some text added at the end."
);
Some text added to show DocWriter output. Some text added at the end.
Format title
When a title is specified, it is used as title.
Method testMethod = DocWriterTest.class.getMethod("format_title");
final String output = docWriter.formatOutput("My title", testMethod);
[#com_github_docastest_doctesting_utils_docwritertest_format_title] = My title
When a title is not specified, title comes from the method name or the class name after some formatting (remove '_', uppercase first letter).
Method testMethod = DocWriterTest.class.getMethod("format_title");
final String method_output = docWriter.formatOutput(testMethod);
final String class_output = docWriter.formatOutput(MyTest.class);
[#com_github_docastest_doctesting_utils_docwritertest_format_title] = Format title
[#com_github_docastest_samples_mytest] = My test include::_MyTest.test_A.approved.adoc[leveloffset=+1]
Add a description
A description can be added after the title using the Javadoc. It can be done with the method javadoc or the class javadoc.
/**
* My comment for MyTestComment
*/
public class MyTestWithComment {
@RegisterExtension
static final ApprovalsExtension doc = new SimpleApprovalsExtension();
/**
* To describe a method, you can add a comment.
* It will be added under title.
*/
@Test
public void test_A() {
}
}
final DocWriter writer = new DocWriter();
final String method_output = writer.formatOutput(
"My title",
MyTestWithComment.class.getMethod("test_A")
);
final String class_output = writer.formatOutput(MyTestWithComment.class);
[#com_github_docastest_samples_mytestwithcomment_test_a] = My title To describe a method, you can add a comment. It will be added under title.
[#com_github_docastest_samples_mytestwithcomment] = My test with comment My comment for MyTestComment include::_MyTestWithComment.test_A.approved.adoc[leveloffset=+1]
If we want to add description of an other class (class under test for example),
we can use ClassToDocument to define the class containing the description we want.
It can be combine with the description on the test class.
/**
* Description of the test class.
*/
@ClassToDocument(clazz = ClassUnderTest.class)
public class MyTestWithClassToDocument {
@RegisterExtension
static final ApprovalsExtension doc = new SimpleApprovalsExtension();
@Test
public void test_A() {
}
}
/**
* Description of the class ClassUnderTest.
*/
public class ClassUnderTest {
}
[#com_github_docastest_samples_mytestwithclasstodocument] = My test with class to document Description of the class ClassUnderTest. Description of the test class. include::_MyTestWithClassToDocument.test_A.approved.adoc[leveloffset=+1]
Customize description
If you want to modify the comment before adding it to the document, you can extend the DocWriter.
/**
* My comment for MyTestComment
*/
public class MyTestWithComment {
@RegisterExtension
static final ApprovalsExtension doc = new SimpleApprovalsExtension();
/**
* To describe a method, you can add a comment.
* It will be added under title.
*/
@Test
public void test_A() {
}
}
final DocWriter writer = new DocWriter() {
/**
* Override getComment to upper case the comment.
*/
@Override
protected Optional<String> getComment(Class classFile, Method testMethod) {
return ((Optional<String>) super.getComment(classFile, testMethod))
.map(c -> c.toUpperCase());
}
};
final String method_output = writer.formatOutput(
"My title",
MyTestWithComment.class.getMethod("test_A")
);
final String class_output = writer.formatOutput(MyTestWithComment.class);
[#com_github_docastest_samples_mytestwithcomment_test_a] = My title TO DESCRIBE A METHOD, YOU CAN ADD A COMMENT. IT WILL BE ADDED UNDER TITLE.
Printer
List of codes and values they provided
We can use the following code to get values and the code used to obtain it
final List<CodeAndResult<Integer>> result = new Printer().getCodeAndResult(
5 + 3,
4 + 2
);
Result is
| getCode() | getValue() |
|---|---|
5 + 3 |
8 |
4 + 2 |
6 |
Show result with code
We can use the following code to get values and the code used to obtain it.
final String result = new Printer().showResult(
"abcd".substring(2),
"abcd".substring(2, 4)
);
Result is
-
"abcd".substring(2) = cd
-
"abcd".substring(2, 4) = cd
We can use it with any type returned by code.
final String result = new Printer().showResult(
3 + 6,
5 + 4
);
Result is
-
3 + 6 = 9
-
5 + 4 = 9
Null values are displayed with null.
String value = "abcdef";
String nullValue = null;
final String result = new Printer().showResult(
value,
nullValue
);
Result is
-
value = abcdef
-
nullValue = null
Show result as describe with code
We can use the following code to get values and the code used to obtain it
final String result = new Printer().showResultWithFormat(
(value, code) -> "Extracted value: `" + value + "` +\nCode to extract:\n----\n" + code + "\n----\n\n",
"abcdef".substring(2),
"abcdef".substring(2, 4)
);
Result is
Extracted value: cdef
Code to extract:
"abcdef".substring(2)
Extracted value: cd
Code to extract:
"abcdef".substring(2, 4)
Group by result
Description
It’s sometime interesting to group all values that provides the same result. It can be used to show several ways of doing the same thing. We can simply display result once with all cases that can be used to build it. You also can just check that we obtain the same result with different values. In that case, there is only one key in the result and you can just say that all values are the same.
Applying a function
final Map<String, List<String>> stringListMap = Printer.groupByResult(
String::toLowerCase,
Arrays.asList("abc", "ABC", "XyZ", "Abc")
);
You obtain a map with value returned by the function as key and a list of values that provides the key value.
abc: abc, ABC, Abc
xyz: XyZ
Using code and result class
You can create an object that associate the code and the value it return. This class provides method to help rendering data.
final CodeAndResultList<Integer> codeAndResultList = Printer.extractCodes(
2 + 4,
3 + 5,
3 + 3
);
// Extract codes grouped by value.
Map<Integer, List<CodeAndResult<Integer>>> result = codeAndResultList.groupBy();
// Format an output grouping codes by value and providing a function to generate lines.
final String output = codeAndResultList.formatGroupedByValue((value, codes) ->
"*" + value + "*: " + Printer.join(codes, code -> "`" + code.trim() + "`", ", ")
);
6: 2 + 4, 3 + 3
8: 3 + 5
*6*: `2 + 4`, `3 + 3` + *8*: `3 + 5`
You can change delimiter between values. Here, we used " / ".
codeAndResultList.formatGroupedByValue(
(value, codes) -> "*" + value + "*: " + Printer.join(codes, code -> "`" + code.trim() + "`")
, " / ")
6: 2 + 4,3 + 3 / 8: 3 + 5
Group by a modified value
final CodeAndResultList<String> codeAndResultList = Printer.extractCodes(
"abc",
"ijkl",
"xyz",
"a" + "b" + "c"
);
You can group all code that produce the same value.
final Map<String, List<CodeAndResult<String>>> groupedCode = codeAndResultList.groupBy();
groupedCode contains:
ijkl: "ijkl"
abc: "abc", "a" + "b" + "c"
xyz: "xyz"
If you want to group with another key, you can pass a function to build the key from the value or from the code.
final Map<Integer, List<CodeAndResult<String>>> groupedCode
= codeAndResultList.groupBy(codeAndResult -> codeAndResult.getValue().length());
groupedCode contains:
3: "abc", "xyz", "a" + "b" + "c"
4: "ijkl"
You can group using the value after applying a function. The key used to group can be of another type than that of the initial value.
// Format an output grouping codes by value applying a function.
final String output = codeAndResultList.formatGroupedByValue(
(value, code) -> value.length(),
(value, codes) -> "*" + value + "*: " + Printer.join(codes, Function.identity(), ", "),
" +\n"
);
3: "abc", "xyz", "a" + "b" + "c"
4: "ijkl"
*3*: "abc", "xyz", "a" + "b" + "c" + *4*: "ijkl"
Extract one code and result
final CodeAndResult<Integer> codeAndResultList = Printer.extractCode(
2 + 4
);
2 + 4
Result is 6
Extract one code and result keep format
final CodeAndResult<List> codeAndResultList = Printer.extractCodeAsItWrite(
Arrays.asList(2,
3,
4)
);
Arrays.asList(2,
3,
4)
Result is [2, 3, 4]
Simple tools
Join list to string
We provide a method to transform a list to a string applying a mapper It’s just a encapsulated call to stream, map, collect
Join with mapper
String result = Printer.join(
Arrays.asList("abc", "efg", "ijk"),
String::toUpperCase
);
ABC,EFG,IJK
Join with separator
String result = Printer.join(
Arrays.asList("abc", "efg", "ijk"),
" - "
);
abc - efg - ijk
Join with mapper and separator
String result = Printer.join(
Arrays.asList("abc", "efg", "ijk"),
String::toUpperCase,
" - "
);
ABC - EFG - IJK
Svg graph
Generate an empty grid
final String svg = new SvgGraph().generate();
Specify grid size
final String svg = new SvgGraph()
.withHeight(100)
.withWidth(200)
.generate();
Draw a single line
final String svg = new SvgGraph()
.withLine("Values", Arrays.asList(0, 20, 10))
.generate();
Draw a single line with negative values
final String svg = new SvgGraph()
.withLine("Values", Arrays.asList(0, -20, -10))
.generate();
Draw a single line with negative and positive values
final String svg = new SvgGraph()
.withLine("Values", Arrays.asList(-15, 4, -6, 30, 25))
.generate();
Draw a single line with float values
final String svg = new SvgGraph()
.withLine("Values", Arrays.asList(-2.5, 1.2, -0.5, 3.6))
.generate();
Draw several lines
final String svg = new SvgGraph()
.withLine("Line A", Arrays.asList(-15, 6, 30, 25))
.withLine("Line B", Arrays.asList(5, 12, 45, 17))
.withLine("Line C", Arrays.asList(-5, -10, -20, 8))
.generate();
Apply factor on axes
By default, the factor apply for axis is calculated to fill the space. This factor could be set manually. It can be use to display several graph with the same scale.
final String svg = new SvgGraph()
.withXFactor(290) // 580 / 2 => grid width / nb values
.withYFactor(10) // 380 / 38 => grid height / max value
.withLine("Values", Arrays.asList(10, 38, 20)).generate();
Here, with a max value at 38 and a factor at 4, the max point is 38 * 4 = 152 This point is only 8 pixels below the height of the grid specified (160).
final String svg = new SvgGraph()
.withHeight(160)
.withYFactor(4)
.withLine("Values", Arrays.asList(10, 38, 20)).generate();