You are viewing blackjml

Test Driven Development with Athena

« previous entry | next entry »
May. 24th, 2006 | 02:15 pm

The boffins at Divmod have produced a fine two-way AJAX toolkit called Athena. To play with it, you'll need to configure Nevow on your coding box. The link should have instructions. If not, Someone should do Something about it.

This post is for people like me who have forgotten how to program without writing unit tests first. Kent Beck's book shows why this is good state to be in.

The Divmod guys have written a unit testing framework called nit that makes it fairly easy to write unit tests for your JavaScript code. This isn't anything new. nit also makes it easy to write unit tests for your Athena widgets, which is awesome. Here's how to do it.

1 Set up files and directories

Create the following files and directories.
  • foo/__init__.py
  • foo/test/__init__.py
  • foo/test/livetest_foo.js
  • foo/test/livetest_foo.py
  • foo/static/js/foo.js
  • nevow/plugins/foopackage.py
We are creating a package called 'foo' for which we are writing tests. The main JavaScript module is in foo/static/js/foo.js and our tests are under foo/test/livetest_*. The 'livetest_' prefix is so that nit will find the tests. The plugin file will be loaded automatically by Nevow using Twisted's plugin system.

2 Set up namespaces

Athena lets you put JavaScript files in namespaces, just like Python. However, unlike Python, you have to explicitly map namespaces to files.

So, in nevow/plugins/foopackage.py:
from twisted.python import util
from nevow import athena
import foo

def _f(*sib):
return util.sibpath(foo.__file__, '/'.join(sib))

fooPkg = athena.JSPackage({
'Foo': _f('static', 'js', 'foo.js'),
'Foo.Tests': _f('test', 'livetest_foo.js')})
This registers foo/static/js/foo.js as the 'Foo' namespace and foo/test/livetest_foo.js as the 'Foo.Tests' namespace.

3 Write A Single, Failing Test

Your test needs to have two parts: a JavaScript part and a Python part.

In foo/test/livetest_foo.py:
from nevow import loaders, tags
from nevow.livetrial import testcase

class OurFirstTest(testcase.TestCase):
    jsClass = u'Foo.Tests.OurFirstTest'
    docFactory = loaders.stan(tags.div(render=tags.directive('liveTest'))['OurFirstTest'])
This is the hook for nit to catch on to your test.   nit will render OurFirstTest as a LiveFragment and bind it to an instance of Foo.Tests.OurFirstTest. 

In foo/test/livetest_foo.js:
// import Nevow.Athena.Test

Foo.Tests.OurFirstTest = Nevow.Athena.Test.TestCase.subclass('OurFirstTest');
Foo.Tests.OurFirstTest.methods(
    function run(self) {
        self.assertEquals('apple', 'orange');
    });
This is a weird way of writing JavaScript.  Emacs hates it – it does the indentation all wrong, and no one at Divmod had a fix on hand.  Anyway, it's all part of the desperate bid to turn JavaScript into Python.   The run method gets called by nit when you click 'Run'.  Observe!

$ nit foo
2006/05/24 11:26 EST [-] Log opened.
2006/05/24 11:26 EST [-] nevow.appserver.NevowSite starting on 8080
2006/05/24 11:26 EST [-] Starting factory <nevow.appserver.NevowSite instance at 0x1427990>

Hit http://localhost:8080/ and you'll see your test and a button.  Hit the button.  You'll see a big red traceback that shows that your test FAILS.

We have a single, failing test.  However, it only does JavaScript stuff, which is boring.

4 Write A Single, Failing Widget Test

To test a widget, we need to create it and render it as a part of the test. Creating a widget requires writing a JavaScript part and a Python part.  We'll also need to modify our test to actually call things on the widget.

So, first, change livetest_foo.py:
from nevow import loaders, tags
from nevow.livetrial import testcase
from foo import widgets

class OurFirstTest(testcase.TestCase):
    jsClass = u'Foo.Tests.OurFirstTest'
    docFactory = loaders.stan(
tags.div(render=tags.directive('liveTest'))[
tags.invisible(render=tags.directive('barwidget'))])

def render_barwidget(self, ctx, data):
widget = widgets.Bar()
widget.setFragmentParent(self)
return widget

This creates the widget Bar and renders it as part of the test.  However, before we implement the widget, let's finish writing the test: that is, the JavaScript part.
// import Foo
// import Nevow.Athena.Test

Foo.Tests.OurFirstTest = Nevow.Athena.Test.TestCase.subclass('First');
Foo.Tests.OurFirstTest.methods(
    function run(self) {
        var bar = self.childWidgets[0];
        self.assertEquals(bar.frotz(), 42);
    });
At this point, 'nit foo' will fail saying it can't import foo/widgets.py.  Creating foo/widgets.py will let it run properly, but the page will render a stack trace indicating that widgets.Bar doesn't exist. Let's create it minimally. Put this in foo/widgets.py
from nevow import loaders, tags, athena

class Bar(athena.LiveFragment):
    jsClass = u'Foo.Bar'
    docFactory = loaders.stan(
        tags.div(render=tags.directive('liveFragment'))['Bar'])
Now we can view the tests. However, running them will produce a failure, saying that 'bar' has no properties, referring to the bar in Foo.Tests.OurFirstTest.run.  This is because we haven't yet written the JavaScript side of the widget.  We've said the class is Foo.Bar, so we'll need to dump it in the Foo namespace which we've configured in foo/static/js/foo.js.  Here it is.
// import Nevow.Athena

Foo.Bar = Nevow.Athena.Widget.subclass('Foo.Bar');
Foo.Bar.methods(
    function frotz(self) {
    });
Now, run 'nit foo' again, hit your localhost and run the tests. You should see "Error: Test Failure: undefined != 42" at the top of your red traceback.  You now have a single failing unit test for an Athena widget.

Link | Leave a comment | Add to Memories | Share

Comments {4}

(no subject)

from: hasyer
date: Apr. 10th, 2008 10:08 pm (UTC)
Link

Thank you.

---------------------
muhabbet | korku | msn nickleri | msn nickleri | netlog

Reply | Thread

good~

from: keji01
date: Jul. 23rd, 2009 03:23 am (UTC)
Link

^_^
---------------------------------------------------------------------------------------------
christian louboutin shoes

Reply | Thread

well done

from: amyfanfan
date: Jan. 22nd, 2010 08:36 am (UTC)
Link

great ... i just learning it now, tks very much....


Reply | Thread

Christian Louboutin

from: amyfanfan
date: Jan. 22nd, 2010 08:37 am (UTC)
Link

http://www.8-mall.com

Reply | Thread