ASUnit : Unit Testing in Actionscript download
DDW Framework Library

What is Unit Testing?

This is not a new concept for anybody who has ever programmed. In a perfect world, you would make something, compile it (test movie in Flash), and if there are no errors in the trace window, you ship it off. Of course in reality you still have to test things out, make sure stuff works, and that is what unit testing is all about.

If you are new to 'formal' testing, it is probably best to take a few small steps to get there conceptually. When you write a small program, how do you make sure everything works as expected? Probably run it and try to make it break, and probably write a few 'tests' to see if you can jam things up with different bits of code. That is pretty much what you do with Unit Testing too, just you go about it a bit more methodically. It isn't a silver bullet however - there are limits to how much you can test, and certainly there are types of bugs that can't be caught with this type of testing. In spite of that, it is still a very useful tool to add to your debugging arsenal (which will now include a fly swatter, a sheet of sticky flypaper, and a pair of 747s loaded with Malathion).

So how do you "make sure things are working as expected"? The key words there are 'make sure' and 'as expected'. The way you 'make sure' of something is by using the word 'Assert'. You can Assert things are true, or that two things are equal, that two objects point to the same reference, or Assert that something is or isn't null. The 'expected' part comes next. It can be anything, a value, expression, whatever you like, as long as it results in what you are expecting. So for example you can say:

Assert( 5 == 6 );

If you are uncomfortable with big technical words, that can also be read as "Make sure five is equal to six" . In fact, formal testing is pretty strict about things, so maybe it would be better to read that as "Make damn sure five is equal to six, and no bull!". Obviously something is expecting that to be true, which shows that the '5 == 6' part is the 'expected' part (you may only be 'hoping', or even 'claiming', but 'that something' is certainly 'expecting'). You can cover a lot of ground with just this, but there is another item that is often useful to bring into your debugging world - reality. Of course we are expecting all things we 'Assert' to be true, but then again we are also expecting our program to run without blowing up, yet here we are writing test code. What you can do then, is bring in the value of reality - called 'actual'. This way you can get a little more information when things go wrong. The format for this is always Assertion(expected, actual), so like this:

AssertEquals( 5, 6 );
AssertSame( ManOfDreams, Husband );

Which again can be read as "Make sure the following are equal: I'm expecting a 5 it is actually a 6 (Shit)". The second example, AssertSame, checks that two references refer to the same object, so in this case, that the two descriptions refer to the same person - the expected and the actual. I believe this example would throw an 'UnrealisticExpectations error' if such a thing were possible in Actionscript, though I'm not certain. Note that the 'expected' parameter comes before the 'actual' parameter - so your code will often look like AssertEquals(5, x). This is not a bad way to write code in general, it helps you avoid the dreaded if(x=5){...} bug (you get an error if you say 5=x, rather than the correct 5==x).

The other asserts are pretty much self explanatory, one thing to note though is you should use AssertTrue rather than the deprecated Assert - it's always good to state your intentions clearly. Here are the others:

AssertTrue( bool ); // expressions in these are fine too
AssertNull( job );
AssertNotNull( debt );

Ok, one last thing you can optionally bring in - yourself. You can add a personal message to any assertion, perhaps a grocery list, a reminder to change the water in the fish bowl, or more likely a simple "SOS". Messages can help you spot where your code is going wrong, or allow tracing of other values, etc. They are just tacked on to the output to be displayed when things go wrong. If you wish to add a message, it is always the first parameter in an assert (no idea why its first, but there you go). So you can say:

AssertTrue( "From instance: "+instanceName, 42 == answer );
AssertEquals( "When smoking crack.", 5, 6 );

Dealing With Failure

So with all that, you are probably wondering what happens when these things fail. Fair enough. Right now there is no GUI for this, however all traces are saved in a TestResults instance in the Tested property, and all Failures are stored in the same instance's Failures property. These are arrays, and each array item is a TestResult (no 's') instance. The TestResult instance has four properties: Success, Message, TestMethod (name), and TestClass (name). It's a safe bet that you don't give a rat's monkey about all that. No problem, for now the results are just traced to the output window. So the above example will look like:

**- TEST FAILED -**
     Class:     DDW.TestCode.TestASUnit.AssertTests
     Method:    TestAssertEquals
     Message:   SOS!
     -expected: <5>
     -actual:   <6>

One last thing that has to be said - you want to make your code fail. The whole point of this is to find bugs after all, so don't take it easy on yourself. You can, and usually should, write more than one test per method. A juicy spot to find bugs is often on the boundaries. For example if you are expecting a number between 0 and 100, test -1, 0, 1, 99, 100, 101. Of course you can't write out every possible test - even a simple routine would run into billions of tests, so it is important to write tests for where you think the weak points are. The greatest thing about unit testing, is when you accidentally screw up a working routine, either by leaning on the keyboard, or modifying a related class, it will tell you right away. So don't delete working tests by all means, in fact run them every time (until they are really slowing things down anyway).

Ok, one tiny more really obvious thing to say, don't include the tests, or the testing framework in your shipping code - they add file size to your SWF : ).

Well that is all for writing tests, the rest of this document will deal with how to set things up so it is as painless as possible to do lots of testing.

A Single Testing Class

First thing you will need is import the code for the testing framework. You can do this with a few #include statements:

#include "DDW/OO.as"
#include "DDW/Debug.as"
#include "DDW/ASUnit.as"

Note: If you copy the DDW directory to the <flash>/Configuration/Include directory, you will not have to copy these files into every project you create - they will be picked up automatically from there.

For your final SWF, you can comment out Debug.as, and ASUnit.as as they are only used for debugging purposes.

Generally you write your tests in methods of a testing class. This class must be a subclass of TestCase. If this all sounds fuzzy to you, fear not, it is no more difficult than filling in a template as you will see. We will test a Rectangle class for this example, and though we could call it anything, we'll call it TestRectangle. To set up the class, you can use pseudo Java or C# syntax, like this (more info on the ClassRegistry here):

DefineClass("ProjectTests.TestRectangle:TestCase");
// or if you prefer speaking Java-ish:
// DefineClass("ProjectTests.TestRectangle Extends TestCase");

// TestCase has an empty constructor, so you can forget the super if you like.
TestRectangle = function(){ super(); }

// Now define a method that will run some tests -
// in this case it tests the Location property.
// These test methods must start with the word 'Test'.
TestRectangle.prototype.TestLocation = function()
{
// run any tests you like in here...
var rect = new Rectangle(10,20,30,40);
var pt = new Point(10, 20); AssertEquals(pt, rect.Location); // First Test rect.Location = new Point(50, 60);
var pt = new Point(50, 60);
AssertEquals(pt, rect.Location);
// Second Test
}
// etc - more test methods go here...

To test this manually, you would make an instance of TestRectangle, and invoke its Run method (which it gets automatically from TestCase). So like:

t = new TestRectangle();
t.Run();

Invoking run like this causes all the methods that start with 'Test' to be tested. If you only wanted to test a single method, you could pass the name of that method as a string, like t.Run("TestLocation");. Normally though, you would want to run them all. What you can do if you want to run all methods minus a few, is change those method names to start with _Test rather than Test, then they will be skipped.

Ok, that may seem a bit complex, but there really are only two rules: a) your test classes must inherit from TestCase, and b) test methods have to start with the word 'Test'. There are two other methods to know, Setup and TearDown, though they are only in there for your convenience, so no requirements are involved. You probably will end up using them though, they can save a fair bit of typing if nothing else.

So what do Setup and TearDown do? As you can see in the above, you might spend a lot of time creating the same old rectangle, once to test Location, once to test Size, once to test Width, and so on. It gets worse if you are testing something more complex, like 20 interacting objects. To solve this problem, the two methods, Setup and TearDown are run before and after each test method is run (can you guess which one runs before?). So the order goes:

Setup > TestMethod_0 > TearDown > Setup > TestMethod_1 >TearDown >...> Setup > TestMethod_n >TearDown

As you see, you can setup a few standard objects, or load an XML file, or open a connection in the Setup method. Then when a method is finished running, you can clean up, close the connection, and do whatever else you wish to do. To use this functionality you override these methods in your test class, which is done by simply defining a method with the name Setup or TearDown. So for example in the above you could define a default Rectangle for all tests in the class by saying:

TestRectangle.prototype.Setup = function()
{
    this.rect = new Rectangle(100, 100, 200, 200);
}

Like this a default this.rect property is available for all the tests, and reset to its original value before each one is run. (Note: Thanks to Alon Salant for pointing out to me how this actually works! : )

Running Multiple Tests

Ok, we are almost there! So now you have a test class that tests the Rectangle class. You want to write another that tests the Point class, another that tests ShapeCollections... Hmm, so for each one you create an instance, and then invoke its Run method - sounds tedious and error prone. After all, the test won't throw an error if you forget to run it! Happily there is a solution to this as well - TestSuite.

Before getting in to TestSuite (which is pretty simple) you should know of a simple shortcut (not a JUnit thing). You can just type RunAllTests(); and every class that derives from TestCase will be found, all their methods (that start with 'Test') will be run, and all the failures/details will be traced to the output window. Given the relatively small size of an average swf project, this is probably all you will really need, however you can create your own TestSuites if you desire greater control..

TestSuite is a class that controls the running of all the test classes (which are sometimes called 'fixtures' - maybe because they fix things, not sure). All you do with this is create an instance, add the tests you want to run with AddTest( ClassToTest ), and then run them all with instance.Run(). Like the test classes, there are two methods that run before and after all the action, StartTest and EndTest. EndTest is where the results are traced out, so you would modify or override this method if you wanted to do something else with the results. For example there is a commented out line in there that will trace all the results, whether they failed or not. If you wish to use a GUI, this is where you would send the results to it. To see how this looks in code:

ts = new TestSuite();
ts.AddTest( TestRectangle ); // note, add ref, not string
ts.AddTest( TestSize ); // etc
ts.Run();

Ok, that is about all there is to it, thanks for bearing with me. If you have any questions, just raise your hand and I'll be happy to answer them. .. No? Ok, well thanks for coming out today, and see you next week. Boy, now that's funny. Ok, if you are really stuck, find a bug, feel things should be different, want to add a bit, etc. just post to the boards in the comment section and I'll see you there...

Example

See the full TestRectangle class in the download folder DDW > Drawing > TestRectangle folder. Use the root level testFramework.fla to run the tests.

About ASUnit

This testing framework is based loosely on JUnit and NUnit (JUnit for .Net), as far as it is practical in Actionscript anyway. Credit to them, blame to me ; )

Comments

Submit Comment | View Comments (there are currently <TODO> comments for this page)

 

© 2002 Debreuil Digital Works. All rights reserved.