ASCII art as a DSL for unit testing

1

Complicated tree structures are being used a lot within my current project. Tree nodes have elements behind them. An element is unique, but there can be multiple tree nodes for a single element. Tree nodes have different drag and drop behavior based on flags on both the tree nodes and the elements. Also tree nodes can inherit children from other tree nodes etc. This blog will be about how to unit test these trees or more exactly how to setup a unit test so that it is fun to create and easy to maintain.

These trees are rendered on the screen by the richfaces jsf tree component which supports drag and drop. The result of a drag and drop action depends on a set of rules. To guarantee that the set of rules is implemented correctly the code is extensively tested.

Every test needs to start with a certain tree, do some logic (like a drag and drop action) and verify the tree again.
Setting up this initial tree is a precise and hard job. This is something you don’t want writing tests. It should be easy to write a test and it should be easy to understand and maintain (another developers) test.

The test that is used as an example is about dragging treeNode2 and dropping it on treeNode3. After the drag and drop action treeNode3 should have one new child.
The following code snippet is a first attempt to write a drag and drop test for this scenario.

[sourcecode language="Java"] @Test
public void executeDragAndDropInNormaMode() {
Taxonomy tax1 = new Taxonomy();
tax1.setPlaceHolderType(SKL);
// some more properties
Taxonomy tax2 = new Taxonomy();
tax2.setPlaceHolderType(MTA);
// some more properties
Taxonomy tax3 = new Taxonomy();

tax2.setParent(tax1);
tax3.setParent(tax2)
tax1.addChild(tax2);
tax1.addChild(tax3);

TreeNode treeNode1 = TaxonomyTreeNode(tax1);
TreeNode treeNode2 = TaxonomyTreeNode(tax2);
treeNode2.setParent(treeNode2);
treeNode1.addChild(treeNode1);
//…. and so for tax3

taxonomyDragAndDropBean.executeDragAndDrop(treeNode2, treeNode3);

assertThat(treeNode3.getChildren(), hasSize(1));
}
[/sourcecode]

This is horrible! Very error prone to set up the test and not readable at all. Can you see how the initial tree looks like? No developer (should) like(s) to write a test like this. Don’t mention maintaining it.

The next step in getting a more clear unit test was to use factories with a fluent api to construct the individual tax elements and tree nodes. The factory takes care of setting the default values and only the properties that are relevant in the test should be set. This step made it somewhat easier to write and read a test, but it was still no fun to write a test.
To solve the read problem a bit the team agreed that every tree test should have a Javadoc that describes the tree that is being used in the unit test.

The following test is the result.
[sourcecode language="Java"] /**
* <pre>
* [Node1] (placeHolderType : normal)
* |-[Node2] (taxonomyType: SKL)
* |-[Node3]
* </pre>
*/
@Test
public void executeDragAndDropInNormalMode() {
Taxonomy tax1 = new TaxonomyFactory().setPlaceHolderType(NORMAL).newInstance();
Taxonomy tax2 = new TaxonomyFactory().setParent(tax1).setTaxonomyType(SKL).newInstance();
Taxonomy tax3 = new TaxonomyFactory().setParent(tax1).newInstance();

TreeNode treeNode1 = new TaxonomyTreeNodeFactory().setTaxonomy(tax1).newInstance();
TreeNode treeNode2 = new TaxonomyTreeNodeFactory()
.setParent(treeNode1).setTaxonomy(tax2).newInstance();
TreeNode treeNode3 = new TaxonomyTreeNodeFactory()
.setParent(treeNode1).setTaxonomy(tax3).newInstance();

taxonomyDragAndDropBean.executeDragAndDrop(treeNode2, treeNode3);

assertThat(treeNode3.getChildren(), hasSize(1));
}
[/sourcecode]

Much better than the previous version. But still there is more improvement possible. Setting up the tree structure takes 8 lines of code while the actual work and asserts takes 2 lines. This is maybe acceptable in this case but there are only 3 tree nodes involved. There are some tests that require a tree that contains at least 10 tree nodes. The code to setup the tree will explode and again the risk of making mistakes is high.

The Javadoc makes it easier to understand the test, but also smells. There is no guarantee that the javadoc resembles the tree structure build in Java code. The test is copied as a starting point for a new test. The tree structure changes but the javadoc is forgotten to update.

But then again the Javadoc is easy to understand. The idea to use the Javadoc form of writing down the tree was born. If somehow the drawing could be used to build the tree instead of the java code.
A TaxonomyDrawingBuilder class was created that takes a string representing the tree and exposes some getter methods to get the root tree nodes or all the tree nodes of the tree.
This class soon became crucial for all the tree unit tests. So thoroughly unit testing of the class itself was required. If it would contain an error all test based on the new builder could be wrong and so all the tree code.

The following code snippet demonstrates how the drawing builder can be used.
[sourcecode language="Java"] String treeDrawing =
"[Node1]"
+ " |-[Node2]"
+ " |-[Node2]"
+ " |-[ChildOfNode2]"
TaxonomyDrawingBuilder builder = TaxonomyDrawingBuilder.newInstance(treeDrawing);
TreeNode treeNode1 = builder.getRootTreeNode(0);
[/sourcecode]

The taxonomy drawing was initially still missing some details like the place holder type or the taxonomy type. But soon more and more attributes where added to the TaxonomyDrawingBuilder.

At the moment a more complicated tree structure could like this.
[sourcecode language="Java"] String taxDrawing =
"[Node1::ROL!M]\n"
+ " |-[Combo1 > Node2 && Node1]\n"
+ "[Node2::SKL!P]\n"
+ " |-[Combo2 = Node2 && Node1]"
+ " |-[ > Node1]";
[/sourcecode]

This may look a bit complicated but after you get used to the syntax it is very easy to create the tree structure you need for your test. Making mistakes in setting up the tree are minimal. The TaxonomyDrawingBuilder validates the tree and gives informative errors when the drawing is incorrect.
One of the current problems we have is that Java does not support multiline strings. This makes use of the ‘+’ operator and the ‘\n’ character. But the real problem is the Eclipse formatter. It puts all the tree nodes on one line like this:
[sourcecode language="Java"] String taxDrawing = "[Node1]\n" + " |-[Node2]\n" + " |-[Node3]\n" + " |-[Node4]\n";
[/sourcecode]

This again makes the drawing unreadable. For this reason an empty comment is put on each tree node line. This way the Eclipse formatter will not mess up the tree drawing.

With the use of the TaxonomyDrawingBuilder the drag and drop test can now be written as follows:
[sourcecode language="Java"] @Test
public void executeDragAndDropInNormalMode() {
String taxDrawing = "" //
+ "[Node1!N]\n" //
+ " |-[Node2::SKL]" //
+ " |-[Node3]";

TaxonomyDrawingBuilder builder = TaxonomyDrawingBuilder.newInstance(taxDrawing);
TreeNode treeNode2 = builder.getRootNode(0).getChild(0);
TreeNode treeNode3 = builder.getRootNode(0).getChild(1);

taxonomyDragAndDropBean.executeDragAndDrop(treeNode2, treeNode3);

assertThat(treeNode3.getChildren(), hasSize(1));
}
[/sourcecode]

The test is now fun to make and easy to read. Our functional designers can now understand our trees and maybe someday the user stories will also be written using the ASCII DSL.

Share.

About Author

1 Comment

  1. That is interesting stuff Alan. Have you considered JSON as a notation for the tree-structure? Or is that not rich enough? (using JSON your TaxonomyDrawingBuilder could perhaps leverage existing JSON libraries).

     

    Do you by any chance have code to render a tree structure in memory in the drawing syntax that you have adopted – perhaps as output from failed tests? Or to compare expected with actual tree structure?

     

    Sounds like an intreresting and complex project you are working on! Good luck.

     

    Lucas