JDoctest is an implementation of Python's doctest for Java, based on the
ideas in doctestj
and Rhino's doctests.
Unlike doctestj, JDoctest is a javadoc Taglet
, rather than a Doclet
.
It doesn't replace the standard javadoc, it just adds a new @doc.test
tag. We also use a multiline format similar to Python's doctest
(based on Rhino's implementation) rather than insist on a single
evaluated expression, as doctestj does.
Doctest blocks are written in Javascript. Calling from Javascript into Java
is straightforward. An example from the top of the
net.cscott.jdoctest.JDoctest
class:
/**
* A "@doc.test" tag specifies an interactive javascript
* session, in an environment where the class' package has been imported.
* The output of the javascript session should match the output
* provided.
*
* @author C. Scott Ananian
* @doc.test
* This is an example of a test which passes:
* js> "a".equals("a")
* true
* @doc.test
* This is an example of a test which fails:
* js> 1+2
* 5
* @doc.test
* We can write tests which expect exceptions to be thrown, although
* it's a little clunky (implementing doctest.ELLIPSIS or
* doctest.IGNORE_EXCEPTION_DETAIL would make this nicer):
* js> try {
* > java.lang.String("hi").charAt(3);
* > } catch (e) {
* > print(e.javaException.getMessage())
* > }
* String index out of range: 3
* @doc.test
* This demonstrates that the current package has been imported:
* js> JDoctest
* [JavaClass net.cscott.jdoctest.JDoctest]
* js> Version.PACKAGE_NAME
* jdoctest
* @doc.test
* Note that results referencing object hashes are properly handled in
* the output comparison function, even when Rhino gives a different hash
* at runtime:
* js> o = new java.lang.Object()
* java.lang.Object@1d2068d
* js> o
* java.lang.Object@1d2068d
* js> new java.lang.Object()
* java.lang.Object@1ac2f9c
*/
public class JDoctest implements Taglet { ... }
To build:
- You must have a JDK 1.5 or greater. In JDoctest version 1.5 I removed
support for pre-1.5 JDKs.
- In older JDKs, you might need to copy
sample.build.properties
tobuild.properties
and edit it to properly reflect that path to your JDK, so that we can find the JDK'stools.jar
file.
- In older JDKs, you might need to copy
- Run
ant javadoc
, which will build the source code and then run its doctests. - One (ignored) failure is to be expected as the tests are run; it is included to show the results of a failing test in the output javadoc.
To install:
- Move the generated
jdoctest-*.jar
as well aslib/rhino*/js.jar
to a directory in your project. Below, we will assume that you've placed both these files inlib/jdoctest
in your project. - If you intend to use the JUnit test support, you wil also need
lib/junit-4.6.jar
and thetools.jar
from your JDK release in your classpath (see junit rules for ant below).
To invoke from the command line:
javadoc -taglet net.cscott.jdoctest.JDoctest \
-tagletpath lib/jdoctest/jdoctest.jar:lib/jdoctest/js.jar:bin \
-J-ea \
<your other javadoc options go here>
The above command-line assumes that your source code has been compiled
into bin
; change that part of the -tagletpath
if your classes are
elsewhere. Your compiled .class
files must be included on the
tagletpath
so that we can run the doctests.
Note that you need to use the -J-ea
option if you want assertions to
be enabled during the evaluation of the doctests. You can use
-J-ea:<your package>...
to only enable assertions in your code (not in
the entire javadoc tool).
Add -J-Dnet.cscott.jdoctest.output=<dir>
to emit the javascript doctests
into files in <dir>
where they can be re-run standalone. This is
helpful for debugging failing tests.
Ant rule:
<javadoc failonerror="true" ...>
<taglet name="net.cscott.jdoctest.JDoctest"
path="lib/jdoctest/jdoctest.jar:lib/jdoctest/js.jar:bin" />
<arg value="-J-ea" />
<classpath> ...your classpath here... </classpath>
</javadoc>
As before, replace bin
with the appropriate path to your compiled
project classes. The -J-ea
option ensures assertions are enabled
in your code when doctests are evaluated. Add:
<arg value="-J-Dnet.cscott.jdoctest.output=your/dir/here" />
to dump the discovered doctests into your/dir/here
for re-running
standalone.
You may want to add css rules to make the output prettier. JDoctest has
hooks to allow the use of google-code-prettify,
which will do syntax highlighting on the client side. The build.xml
file for JDoctest shows how this might be hooked up. The files in
src/doc-files
should be copied to a doc-files
subdirectory of the top-level
source directory of your project.
There are two ways to hook up doctests to JUnit.
The simplest uses the
net.cscott.jdoctest.output
option described above: running
net.cscott.jdoctest.RerunJDoctests
as a JUnit test will rerun all
emitted standalone tests. It assumes these tests were emitted into
api/tests
; if you prefer them someplace else, subclass RerunJDoctests
and reimplement the listTests()
method to call listTests(String dirName)
with the appropriate alternate directory name.
The following ant rule implements this option:
<target name="retest" ...>
<junit>
<formatter type="plain"/>
<test name="net.cscott.jdoctest.RerunJDoctests" />
<classpath refid="classpath.path" />
<assertions>
<enable/>
</assertions>
</junit>
</target>
A more elegant (but slower) mechanism invokes Javadoc on each of your source files to extract and execute the doctests. Simply annotate each of your doctest-containing classes with the JUnit annotation:
@RunWith(value=JDoctestRunner.class)
The src/net/cscott/jdoctest/JDoctest.java
file demonstrates how this is
done. JUnit can then directly execute the doctests for your class.
JDoctestRunner
assumes that your sources are found below a directory
named src
; if your sources are elsewhere you can use the annotation:
@SrcRoot(value="src/dir/here")
If you need to use the @SrcRoot
annotation extensively, you might find it
easier to subclass JDoctestRunner
and override its defaultSrcRoot()
method.
The following ant rule implements this option:
<target name="test" ...>
<junit printsummary="yes" fork="yes" forkmode="once" haltonfailure="yes">
<formatter type="plain"/>
<test name="...your source files here..." />
<classpath refid="classpath.path" />
<assertions>
<enable />
</assertions>
</junit>
</target>
The fork="yes"
option is necessary here, or else the embedded javadoc doesn't
get the correct classpath: ant seems to use a classloader which knows the
correct classpath but leaves the system property java.class.path
set
arbitrarily (ie, wrong), and the embedded javadoc looks at java.class.path
,
not its classloader, when resolving source references.
JDoctest, a doctest module for javadoc.
Copyright (C) 2009,2014 C. Scott Ananian <[email protected]>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA