Getting Started
The basics of creating living documents using Concordion
This page explains getting started with Markdown specifications in Java. Click the toggle buttons above to choose other options.
Creating a living document is a 4 step process:
- Discussing
- Documenting
- Instrumenting
- Coding
Depending on your skillset and role you might be involved in one or more of these steps.
To follow along the tutorial, we’ve created a project you can download, or clone using Git:
git clone https://github.com/concordion/concordion-tutorial-java-markdown
This project contains folders for each stage of the tutorial. You can either start from scratch with the initial
folder of the project, jump some steps to the documented
or instrumented
folders, or go straight to the completed
folder to see the final solution.
1. Discussing
By collaboratively exploring requirements with realistic examples, teams build a shared understanding and detect issues and misunderstandings prior to developing a new feature.
For this tutorial, we are working on a system for creating marketing mailshots. We want to have the first name and last name of the customer. Unfortunately the customer data that we are supplied only contains full names, so we will need to split them.
We start off discussing a simple example:
When discussing examples, we need to consider the context (preconditions), actions and outcomes for each example. In this example, the context is the name Jane Smith
, the action is split
and the outcomes are the first name Jane
and last name Smith
.
As we progress, we discuss more complex cases. We often find it convenient to use tables, timelines or other diagrams to quickly and concisely describe examples:
Find out more about discussing examples.
2. Documenting
The next step is to create a specification of the new feature.
If starting the tutorial from this stage, start with the initial
folder of the tutorial project.
In the src/test/resources/marketing/mailshots
folder of the tutorial project, edit the file SplittingNames.md
to contain the following.
# Splitting Names
To help personalise our mailshots we want to have the first name and last name of the customer.
Unfortunately the customer data that we are supplied only contains full names.
The system therefore attempts to break a supplied full name into its constituents by splitting
around whitespace.
### Example
The full name Jane Smith is broken into first name Jane and last name Smith.
This uses a formatting language called Markdown, which makes it easy to create rich documents using plain text. In the Markdown above:
- The
#
characters at the start of the line create headings, where the heading level is determined by the number of#
characters. - The lines without a
#
character are treated as plain paragraphs.
Opening this specification in Github, or in an editor that supports Markdown preview, we see it looks like:
The team are happy with the specification, so we share it (for example, by adding the file to our version control system).
Note: Prior to v2.0, Concordion only supported HTML specifications, which are harder to read and write than Markdown. However, HTML provides a richer language, so may be preferred for complex scenarios.
Find out more about documenting specifications.
3. Instrumenting
If starting the tutorial from this stage, start with the documented
folder of the tutorial project.
In order to make the specification executable, it must be instrumented with commands. The instrumentation is invisible to a browser, but is processed by the fixture code.
The first step is to select the words in the example that define the context (preconditions), actions and outcomes. In our example, the context is the name Jane Smith
, the action is broken
and the outcomes are the first name Jane
and last name Smith
. We select these parts of the example using Markdown’s link syntax:
The full name [Jane Smith]() is [broken]() into first name [Jane]() and last name [Smith]().
Previewing our specification, we now see the example looks like
Next, we add Concordion commands to the links:
The full name [Jane Smith](- "#name") is [broken](- "#result = split(#name)")
into first name [Jane](- "?=#result.firstName") and last name [Smith](- "?=#result.lastName").
These commands are:
- setting our context, by setting a new variable
#name
to the valueJane Smith
- executing our action, by executing the method
split()
with the variable#name
and returning the value#result
- verifying our outcomes, by checking whether
#result.firstName
is set toJane
, and#result.lastName
is set toSmith
.
Previewing our specification, we can hover over the links to see the command on each link
We also mark up the example header, to turn it into a named example. When the specification is run, this will show as a JUnit test named basic
.
### [Example](- "basic")
Find out more about instrumenting specifications.
4. Coding
If starting the tutorial from this stage, start with the instrumented
folder of the tutorial project.
Finally we create some code, called a fixture, that links the instrumented specification with the system under test.
In the src/test/java/marketing/mailshots
folder of the tutorial project, the file SplittingNamesFixture.java
already contains the following:
JUnit Jupiter
package marketing.mailshots;
import org.concordion.api.ConcordionFixture;
@ConcordionFixture
public class SplittingNamesFixture {
}
JUnit Vintage
package marketing.mailshots;
import org.concordion.integration.junit4.ConcordionRunner;
import org.junit.runner.RunWith;
@RunWith(ConcordionRunner.class)
public class SplittingNamesFixture {
}
You may have noticed that this fixture uses a JUnit runner. If you run the fixture as a JUnit test, for example from an IDE or running gradlew test
from the command line, the location of the output will be shown on the console, such as:
file:///tmp/concordion/marketing/mailshots/SplittingNames.html
Opening this URL in a browser, the output should look something like this:
The test of the example is failing since we haven’t implemented the split()
method. We’ll flesh out our fixture code:
JUnit Jupiter
package marketing.mailshots;
import org.concordion.api.ConcordionFixture;
@ConcordionFixture
public class SplittingNamesFixture {
public Result split(String fullName) {
return new Result();
}
class Result {
public String firstName = "TODO";
public String lastName = "TODO";
}
}
JUnit Vintage
package marketing.mailshots;
import org.concordion.integration.junit4.ConcordionRunner;
import org.junit.runner.RunWith;
@RunWith(ConcordionRunner.class)
public class SplittingNamesFixture {
public Result split(String fullName) {
return new Result();
}
class Result {
public String firstName = "TODO";
public String lastName = "TODO";
}
}
Run it now and you get:
Let’s implement the function. Obviously the implementation should be in the real system not in the test case, but just for fun…
JUnit Jupiter
package marketing.mailshots;
import org.concordion.api.ConcordionFixture;
@ConcordionFixture
public class SplittingNamesFixture {
public Result split(String fullName) {
Result result = new Result();
String[] words = fullName.split(" ");
result.firstName = words[0];
result.lastName = words[1];
return result;
}
class Result {
public String firstName;
public String lastName;
}
}
JUnit Vintage
package marketing.mailshots;
import org.concordion.integration.junit4.ConcordionRunner;
import org.junit.runner.RunWith;
@RunWith(ConcordionRunner.class)
public class SplittingNamesFixture {
public Result split(String fullName) {
Result result = new Result();
String[] words = fullName.split(" ");
result.firstName = words[0];
result.lastName = words[1];
return result;
}
class Result {
public String firstName;
public String lastName;
}
}
The test now passes:
Find out more about coding fixtures.
This is the end of the basic tutorial. Feel free to move straight onto Next Steps, or follow the advanced tutorial below.
Advanced Tutorial
- Now you understand the basics, alter your specification to use a table to show several examples of behaviour.
-
To check a single example that returns a collection of results, you’ll need to use the
verify-rows
command. Create aPartialMatches.md
specification and add the example verify-rows command. Implement agetSearchResultsFor(String searchString)
method in thePartialMatchesFixture.java
fixture class to make this specification pass@ConcordionFixture public class PartialMatchesFixture { private Set<String> usernamesInSystem = new HashSet<String>(); public void setUpUser(String username) { usernamesInSystem.add(username); } public Iterable<String> getSearchResultsFor(String searchString) { SortedSet<String> matches = new TreeSet<String>(); for (String username : usernamesInSystem) { if (username.contains(searchString)) { matches.add(username); } } return matches; } }
-
We can build test suites by running a specification from another specification. In the
marketing.mailshots
package, create a new specification calledMailshots.md
with the following contents:# Mailshots Mailshots are produced on-demand. To help personalise the mailshots we [split names](SplittingNames.md "c:run") into constituent parts.
In the
marketing.mailshots
package, create an empty fixture class, called MailshotsFixture.java:package marketing.mailshots; import org.concordion.api.ConcordionFixture; @ConcordionFixture public class MailshotsFixture { }
Running this fixture will run the linked
SplittingNames
fixture.Using links, we can create a test suite, with breadcrumbs making it easier to navigate the results.
You’ve now seen all of the Concordion commands you’re likely to need on a day-to-day basis. Feel free to browse through the rest of this documentation, learn good practices in the Hints and Tips section, try out our Integrations or Extensions, browse the FAQ or try the Cubano framework which integrates Concordion with a number of Concordion extensions, Selenium WebDriver and other open-source projects to provide a ready-made framework for web and API testing.