Skip to content

Commit 6a643ea

Browse files
authored
Merge pull request #194 from marklogic-community/feature/554-docs
DEVEXP-554 Added docs for debugging tests
2 parents 70ca872 + b3fb197 commit 6a643ea

File tree

5 files changed

+138
-6
lines changed

5 files changed

+138
-6
lines changed

docs/assertion-functions.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
layout: default
33
title: Assertion functions
4-
nav_order: 6
4+
nav_order: 7
55
permalink: /docs/assertions
66
---
77

docs/debugging-tests.md

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
---
2+
layout: default
3+
title: Debugging tests
4+
nav_order: 5
5+
permalink: /docs/debugging
6+
---
7+
8+
Like all unit testing frameworks, marklogic-unit-test is intended to speed up the cycle of developing, testing, and
9+
fixing code. A critical aspect of that is understanding why a test failed and how to fix it. This page provides
10+
guidance on how you can write your tests to ensure they can be easily debugged by any member of your development
11+
team.
12+
13+
## Development setup
14+
15+
Before looking at how to write tests, you should ensure that you can change your application code and test the
16+
changes as quickly as possible. As mentioned in the [Getting started guide](/docs), ml-gradle supports
17+
[watching for module changes](https://github.com/marklogic/ml-gradle/wiki/Watching-for-module-changes) so that when
18+
you modify either an application module file or test module file, the file will be immediately loaded into your
19+
application's modules database. This allows you to test your changes as quickly as possible.
20+
21+
To enable this, simply run the following Gradle task in its own terminal window:
22+
23+
./gradlew -i mlWatch
24+
25+
The Gradle "-i" flag - for info-level logging - results in each modified file being logged when it is loaded into
26+
MarkLogic.
27+
28+
## Test assertion messages
29+
30+
Each [assertion function](/docs/assertions) in marklogic-unit-test supports an assertion message as its final argument.
31+
These are recommended for when the intent of an assertion is not readily apparent from the two values being compared.
32+
33+
For example, consider the following assertion:
34+
35+
const actual = someLib.something();
36+
test.assertEqual(3, actual);
37+
38+
If that assertion fails because "actual" has a value of 2, marklogic-unit-test will throw the following error:
39+
40+
expected: 3 actual: 2 (ASSERT-EQUAL-FAILED)
41+
42+
However, this doesn't explain why "3" is the expected value. The optional 3rd argument in `test.assertEqual`
43+
provides a test developer with a chance to explain why the value is expected - e.g.:
44+
45+
const actual = someLib.something();
46+
test.assertEqual(3, actual, "Due to such-and-such reason, 3 should be returned");
47+
48+
The "such-and-such reason" would of course be replaced with a meaningful explanation in your test.
49+
marklogic-unit-test will then include this message in the failure message:
50+
51+
Due to such-and-such reason, 3 should be returned; expected: 3 actual: 2 (ASSERT-EQUAL-FAILED)
52+
53+
## Using log statements
54+
55+
While tools such as [mlxprs](https://github.com/marklogic/mlxprs) exist to leverage the debugging capabilities within
56+
MarkLogic, you may often find it helpful to include log statements both in your application module and in your test
57+
module. These can log the value of certain variables that are not returned by the function being tested but whose
58+
values provide insight into how the application code is behaving.
59+
60+
For JavaScript code, use the following:
61+
62+
const value = someLib.something();
63+
console.log("The value", value);
64+
65+
And for XQuery code, use the following:
66+
67+
let $value := someLib:something()
68+
let $_ := xdmp:log(("The value", $value))
69+
70+
You can add these statements to your tests as well.
71+
72+
Log messages will then appear in the MarkLogic log file named "PORT_ErrorLog.txt", where "PORT" is the port number
73+
of the MarkLogic app server that you are running the tests against.
74+
75+
## Examining the Gradle test report
76+
77+
When running tests via Gradle - either via `./gradlew test` or `./gradlew mlUnitTest` - one or more test failures will
78+
result in the task failing with the location of the test report being logged. The test report allows you to see details
79+
on each test, including the failed assertion message and stacktrace for each failed test.
80+
81+
When running `./gradlew test`, the test report will be available at "build/reports/tests/tests/index.html". You can
82+
open this file in a web browser to see the results; it is recommended to keep that window open and simply refresh it
83+
after re-running the Gradle `test` task.
84+
85+
When running `./gradlew mlUnitTest`, the test report is a set of JUnit-formatted XML files available at
86+
"build/test-results/marklogic-unit-test". This approach does not result in an HTML web page that can be viewed in a web
87+
browser, but each XML file will contain the results for the tests in a particular suite, including the failed assertion
88+
messages and stacktrace for each failed test.
89+

docs/loading-test-data.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
layout: default
33
title: Loading test data
4-
nav_order: 5
4+
nav_order: 6
55
permalink: /docs/loading
66
---
77

marklogic-unit-test-client/src/main/java/com/marklogic/test/unit/JaxpServiceResponseUnmarshaller.java

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
import javax.xml.transform.TransformerFactory;
1616
import javax.xml.transform.dom.DOMSource;
1717
import javax.xml.transform.stream.StreamResult;
18+
import java.io.ByteArrayOutputStream;
19+
import java.io.OutputStream;
20+
import java.io.OutputStreamWriter;
1821
import java.io.StringReader;
1922
import java.io.StringWriter;
2023
import java.util.ArrayList;
@@ -28,6 +31,7 @@ public class JaxpServiceResponseUnmarshaller implements ServiceResponseUnmarshal
2831

2932
protected final Logger logger = LoggerFactory.getLogger(getClass());
3033
private DocumentBuilder documentBuilder;
34+
private TransformerFactory transformerFactory;
3135
private static int ELEMENT_TYPE = 1;
3236

3337
@Override
@@ -91,14 +95,23 @@ public TestSuiteResult parseTestSuiteResult(String xml) {
9195

9296
@Override
9397
public JUnitTestSuite parseJUnitTestSuiteResult(String xml) {
94-
Element root = parse(xml).getDocumentElement();
98+
Document doc = parse(xml);
99+
Element root = doc.getDocumentElement();
95100
int errors = Integer.parseInt(root.getAttribute("errors"));
96101
int failures = Integer.parseInt(root.getAttribute("failures"));
97102
String hostname = root.getAttribute("hostname");
98103
String name = root.getAttribute("name");
99104
int tests = Integer.parseInt(root.getAttribute("tests"));
100105
double time = Double.parseDouble(root.getAttribute("time"));
101-
JUnitTestSuite suite = new JUnitTestSuite(xml, errors, failures, hostname, name, tests, time);
106+
107+
String prettyXml = xml;
108+
try {
109+
prettyXml = prettyPrintXml(doc);
110+
} catch (Exception ex) {
111+
logger.warn("Unable to pretty-print XML; cause: " + ex.getMessage());
112+
}
113+
114+
JUnitTestSuite suite = new JUnitTestSuite(prettyXml, errors, failures, hostname, name, tests, time);
102115

103116
NodeList testCases = root.getChildNodes();
104117
for (int i = 0; i < testCases.getLength(); i++) {
@@ -124,8 +137,10 @@ public JUnitTestSuite parseJUnitTestSuiteResult(String xml) {
124137

125138
protected String toXml(Node node) {
126139
try {
127-
TransformerFactory transFactory = TransformerFactory.newInstance();
128-
Transformer transformer = transFactory.newTransformer();
140+
if (transformerFactory == null) {
141+
transformerFactory = TransformerFactory.newInstance();
142+
}
143+
Transformer transformer = transformerFactory.newTransformer();
129144
StringWriter buffer = new StringWriter();
130145
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
131146
transformer.transform(new DOMSource(node), new StreamResult(buffer));
@@ -156,4 +171,25 @@ protected Document parse(String xml) {
156171
}
157172
}
158173

174+
/**
175+
* Added this in 1.4.0 to handle pretty-printing the entire XML document, while {@code toXml} is only for the
176+
* embedded failure XML. Did not want to try reusing that for the whole XML document.
177+
*
178+
* @param doc
179+
* @return
180+
* @throws Exception
181+
*/
182+
private String prettyPrintXml(Document doc) throws Exception {
183+
if (transformerFactory == null) {
184+
transformerFactory = TransformerFactory.newInstance();
185+
}
186+
Transformer transformer = transformerFactory.newTransformer();
187+
transformer.setOutputProperty(OutputKeys.METHOD, "xml");
188+
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
189+
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
190+
191+
ByteArrayOutputStream baos = new ByteArrayOutputStream();
192+
transformer.transform(new DOMSource(doc), new StreamResult(new OutputStreamWriter(baos)));
193+
return new String(baos.toByteArray());
194+
}
159195
}

marklogic-unit-test-client/src/test/java/com/marklogic/test/unit/SelectTestsToRunTest.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import static org.junit.jupiter.api.Assertions.assertEquals;
99
import static org.junit.jupiter.api.Assertions.assertFalse;
10+
import static org.junit.jupiter.api.Assertions.assertTrue;
1011

1112
public class SelectTestsToRunTest {
1213

@@ -15,6 +16,12 @@ void selectTestsInSuite() {
1516
JUnitTestSuite suite = new TestManager(ClientUtil.getClient())
1617
.runSuite("Assertions", new TestManager.RunParameters("assert-all-exist.xqy", "assert-equal.xqy"));
1718

19+
// Simple check to verify that the XML is pretty-printed for easier manual inspection.
20+
final String xml = suite.getXml();
21+
final String message = "Expecting each testcase to be indented with 2 spaces; actual XML: " + xml;
22+
assertTrue(xml.contains(" <testcase classname=\"assert-all-exist.xqy\""), message);
23+
assertTrue(xml.contains(" <testcase classname=\"assert-equal.xqy\""), message);
24+
1825
assertEquals("Assertions", suite.getName());
1926
assertEquals(2, suite.getTestCases().size());
2027
assertFalse(suite.hasTestFailures());

0 commit comments

Comments
 (0)