Author: Ian Bicking <ianb@colorstudy.com>
This library is licensed under the GPL.
You can download it from the Mercurial repository at http://bitbucket.org/ianb/doctestjs/
with hg clone http://bitbucket.org/ianb/doctestjs/
or download a
zip
Bugs may be reported on the issue tracker. Patches are best provided by forking the repository through bitbucket.org, and then submitting a pull request. If you are using doctest/js, or have written about it, consider noting this on the wiki.
Doctest/JS is a port of a widely used testing module doctest
from the Python world. The original doctest is by Tim Peters.
The idea behind doctest is to demonstrate code usage in such a way that the demonstrations themselves can be tested. But it was soon found that the technique used was more compact and readable than the xUnit style of testing that had become the defacto standard for testing. (In my opinion, an unfortunate defactor standard.)
The basic idea for a doctest is very simple: run an expression and check that its output is what you expected. For example:
$ function factorial(n) { > if (n == 0) { > return 1; > } else { > return n * factorial(n-1); > } > } ... $ factorial(3) 6
Output:Here we define a function and test it in-place. Typically of course you'd define the functions in your normal
.js
files
(which you'd include as normal with <script>
), and
then simply use them in the examples.
The format is fairly simple:
<pre
class="doctest">
.$
.>
at the beginning of each new line. Note that you can
put >
directly in the HTML, you don't have to quote it
as >
(you only have to quote
<
in HTML)....
to match all or part of the text. For
instance, when you define a function you don't care about the value
of the expression (which is the function object itself). <pre
id="doctestOutput">
where the output will go. <button
onclick="doctest()">test</button>
Doctest tries to act like a REPL: read an expression or statement, evaluate it, print the result. The basic loop is:
null
, do nothing and show
nothing.writeln('Error: '+exception)
writeln(repr(result))
. Specifically, writeln(str, ...)
writes strings. You
can use this in your own code if you want, or use something like
writeln(someFunction())
to avoid repr
.
repr(obj)
gets the programmer's
representation of an object. The representation of a string is
the Javascript literal for that string. The representation of an
Array [repr(item0), repr(item1), ...]
. (The code for
repr
is taken from MochiKit.) You can customize what
repr
does to your own objects by implementing a method
__repr__
or repr
, for example:
MyClass.prototype.__repr__ = function () { return '[MyClass name='+repr(this.name)+']'; };
It's encouraged that you test exception cases in your code. You do this like:
$ function countTag(parent, tag) { > return parent.getElementsByTagName(tag).length; > } ... $ countTag(document.getElementById('dt-exception'), 'pre'); 0 $ countTag({}, 'pre'); Error: TypeError: parent.getElementsByTagName is not a function
Output:
Unfortunately this hides the traceback. Doctest will try to print the traceback to the logs; it's not as good a traceback, but it might be helpful anyway. If you install Firebug the traceback will be in the console.
As mentioned before, pages must contain certain structure. You
can have all your tests run together, or you can run pieces
individually. You must of course include doctest.js
in
your file, and then call doctest()
. There are optional
arguments: doctest(verbosity, elementId, outputId)
.
verbosity
defaults to 0. elementId
is the
id of the <pre>
element you want to test. If you
don't provide it then all pre elements with
class="doctest"
are selected. Lastly the output is
placed in a pre with the id outputId
, or
id="doctestOutput
if none is given.
Typically you run this code with <button
onclick="doctest(...)">test</button>
. It can be
helpful to include a reload button next to that, since usually when
you re-test you want to reload the page first. You can use
<button
onclick="doctest.reload(this)">reload</button>
for
this. You can include as many buttons as you want on the page; for
instance, each individual pre test block can have a button to test
just that block (as do the examples in this document), plus one button
to test everything at once.
The file template.html can be copied to setup new tests.
One problem with this implementation of Doctest is that everything
must be synchronous. That is, code using things like
XMLHttpRequest
will have problems, because Javascript
will never stop executing until the test is done, and generally
requests like that only start running once Javascript has started
running.
It's possible this will be fixed in the future, but until then
you'll have to write your code in a synchronous style. You can also
set up synchronous mock objects that act similarly to how the
asynchronous objects work. For instance, imagine you have a
sendRequest(uri, data, callback)
routine; in your test
you could implement this like:
// Set this variable in your tests to control the mock: nextRequestResponse = null; MyModule.sendRequest = function (uri, data, callback) { writeln('Requesting '+uri); if (data) { writeln('Data: '+repr(data)); } callback(nextRequestResponse); };This can call code in a different order than in a real situation, but it is usually a good simulation.
For the YUI a mock
version of YAHOO.util.Connect.asyncRequest
is provided.
If you include doctest-yui.js in your
test then that function will be replaced with something more suitable
for use with Doctest.
pause(func)
, where a timer would call
func over and over until it returned true, then restart the tests at
that time.