Introduction

Flash is a lightweight cross-platform runtime for rich media, enterprise applications and mobile applications, as well as an integrated development environment. Flash can be programmed in ActionScript 1/2/3.

Archive for the ‘Flash’ Category

Tuesday, January 4th, 2005

Test Driven Development (TDD) and Unit Testing with Mock Objects in Practice

When your boss confronts you with a task you must handle, the first thing you do is thinking about it. You mediate on the task and try to find the best solution of doing it. You maybe draw some sketches to visualize the whole thing and get a clearer image.
But, what’s next? - Well, when practicing TDD the next thing (actually the first, but I’m not that pragmatic about it) is to write a test for the central class and methods. This helps a lot because, when writing a test you become aware of collaborators, you test all special cases and you do not introduce unnecessary complexity. You basically create the whole api and implementations by working with them, which leads to an easy to use api that is based on a practical ground.
That’s basically all about it.
You first write your tests for a method of a class, and then the actual method based on the test, until the test executes without causing errors. Then the test for the next method and so on. It’s an easy, intuitive (when you are used to it) and safe way of programming which leads to stable and easy to use code. Another bonus is that when you refactor your code, improve its performance or do some other changes you can always run the tests to see if everything is still working.
This article is supposed to be a practical one (at least that’s what the headline says), thus we will jump right into practicing TDD. All you need is the as2lib’s unit testing framework and the mock objects framework from the last article Unit Testing: Mock Objects Framework.
A request came from a bank that needs an application to do their banking transactions like transfering money onto an account. The account seems to be the central problem domain (btw. we are only going to build the domain model). The bank clerk must be able to book money to an account (credit) and to withdraw money from an account (debit). That said we can start writing our tests for the account’s constructor and the methods credit and debit. We will start with the constructor tests.

  1. import org.as2lib.test.unit.TestCase;
  2. import org.as2lib.test.mock.MockControl;
  3. import com.simonwacker.banking.Account;
  4.  
  5. class com.simonwacker.banking.AccountTest extends TestCase {
  6.  
  7.   public function testNew(Void):Void {
  8.     var account:Account = new Account();
  9.     assertSame(account.getBalance(), 0);
  10.   }
  11.  
  12. }

We want the account’s constructor to take no arguments and set the balance to zero. To be able to control that the balance is zero we need the ,getBalance’ method. But the compiler won’t compile. That’s because we have not created the ,Account’ class yet. That’s what we are going to do now, based on our first test.

  1. class com.simonwacker.banking.Account {
  2.  
  3.   public function Account(Void) {
  4.   }
  5.    
  6.   public function getBalance(Void):Number {
  7.     return 0;
  8.   }
  9.  
  10. }

The compiler does not complain anymore and when we execute the test everything seems to work.
To execute the test you need a fla with the following code in the first frame.

  1. import org.as2lib.test.unit.TestSuiteFactory;
  2.  
  3. com.simonwacker.banking.AccountTest;
  4. new TestSuiteFactory().collectAllTestCases().run();

It’s time to write the test for the ,credit’ method.

  1. public function testCreditWithNullAmount(Void):Void {
  2.   var account:Account = new Account();
  3.   account.credit(null);
  4.   assertSame(account.getBalance(), 0);
  5. }

The credit method takes one argument ,amount’. Thus the first thing to test is what happens if we pass-in an ,amount’ of value ,null’. I want the method to just do nothing. That means that after invoking this method the balance should still be zero.
To satisfy the compiler and the test we implement the ,credit’ method.

  1. public function credit(amount:Number):Void {
  2.   balance += amount;
  3. }

Everything works fine now and we are ready for the next test that passes an amount of 40.

  1. public function testCreditWithRealAmount(Void):Void {
  2.   var account:Account = new Account();
  3.   account.credit(40);
  4.   assertSame(account.getBalance(), 40);
  5. }

Executing the test fails. We need to do some major changes. To get it working we must be able to change the balance through the ,credit’ method dynamically.

  1. class com.simonwacker.banking.Account {
  2.  
  3.   private var balance:Number;
  4.  
  5.   public function Account(Void) {
  6.     balance = 0;
  7.   }
  8.  
  9.   public function credit(amount:Number):Void {
  10.     balance += amount;
  11.   }
  12.  
  13.   public function getBalance(Void):Number {
  14.     return balance;
  15.   }
  16.  
  17. }

When we run our test again we get the following output:

  1. ** InfoLevel **
  2. *** TestSuite <Generated TestSuite> (1 Tests) [5ms] ***
  3.   com.simonwacker.banking.AccountTest run in [5ms]. 1 error occured
  4.      testCreditWithNullAmount() [2ms] 1 error occured
  5.        assertSame failed!
  6.          NaN !== 0
  7.      
  8. *******************************************************

Out ,credit’ method does something wrong. It adds the amount to the balance even if it is ,null’. We have to prevent this.

  1. public function credit(amount:Number):Void {
  2.   if (amount != null) {
  3.     balance += amount;
  4.   }
  5. }

We enhance our ,testCreditWithRealAmount’ test and add a new one to be really really sure that everything works as expected.

  1. public function testCreditWithNotZeroBalanceAndNullAmount(Void):Void {
  2.   var account:Account = new Account();
  3.   account.credit(60);
  4.   account.credit(null);
  5.   assertSame(account.getBalance(), 60);
  6. }
  7.  
  8. public function testCreditWithRealAmount(Void):Void {
  9.   var account:Account = new Account();
  10.   account.credit(40);
  11.   assertSame(account.getBalance(), 40);
  12.   account.credit(30);
  13.   assertSame(account.getBalance(), 70);
  14. }

A business rule prescribes that the boss of the bank shall get notified if someone credits an amount that’s bigger or equal than 1 000 000 dollars to thank this guy personally. That notification is an email to the boss.
There already exists support for sending emails. This support is based on the ,EmailSender’ interface.

  1. interface com.simonwacker.banking.EmailSender {
  2.  
  3.   public function sendEmail(message:String):Void;
  4.  
  5. }

We write a test for the ,credit’ method in which we verify that the email sender’s ,sendEmail’ method gets executed when we credit an amount bigger than 1 000 000 dollar.

  1. public function testCreditWithAmountBiggerThanOneMillionDollars(Void):Void {
  2.   var emailSenderControl:MockControl = new MockControl(EmailSender);
  3.   var emailSender:EmailSender = emailSenderControl.getMock();
  4.   emailSender.sendEmail("Someone transfered 1 000 000 dollars.");
  5.   emailSenderControl.replay();
  6.  
  7.   var account:Account = new Account();
  8.   account.setEmailSender(emailSender);
  9.   account.credit(1500000);
  10.  
  11.   emailSenderControl.verify();
  12. }
  13.  
  14. public function testCreditWithAmountEqualToOneMillionDollars(Void):Void {
  15.   var emailSenderControl:MockControl = new MockControl(EmailSender);
  16.   var emailSender:EmailSender = emailSenderControl.getMock();
  17.   emailSender.sendEmail("Someone transfered 1 000 000 dollars.");
  18.   emailSenderControl.replay();
  19.  
  20.   var account:Account = new Account();
  21.   account.setEmailSender(emailSender);
  22.   account.credit(1000000);
  23.  
  24.   emailSenderControl.verify();
  25. }
  26.  
  27. public function testCreditWithAmountLessThanMillionDollars(Void):Void {
  28.   var emailSenderControl:MockControl = new MockControl(EmailSender);
  29.   var emailSender:EmailSender = emailSenderControl.getMock();
  30.   emailSenderControl.replay();
  31.  
  32.   var account:Account = new Account();
  33.   account.setEmailSender(emailSender);
  34.   account.credit(500000);
  35.  
  36.   emailSenderControl.verify();
  37. }

Because we are trained now we can make bigger steps and implement mutliple tests at once. The compiler complains because we have no method called ,setEmailSender’. Thus we introduce this method first.

  1. import com.simonwacker.banking.EmailSender;
  2.  
  3. class com.simonwacker.banking.Account {
  4.  
  5.   private var balance:Number;
  6.   private var emailSender:EmailSender;
  7.  
  8.   public function Account(Void) {
  9.     balance = 0;
  10.   }
  11.  
  12.   public function setEmailSender(emailSender:EmailSender):Void {
  13.     this.emailSender = emailSender;
  14.   }
  15.  
  16.   public function credit(amount:Number):Void {
  17.     if (amount != null) {
  18.       balance += amount;
  19.     }
  20.   }
  21.  
  22.   public function getBalance(Void):Number {
  23.     return balance;
  24.   }
  25.  
  26. }

When we run the test again we get the following output:

  1. ** InfoLevel **
  2. *** TestSuite <Generated TestSuite> (1 Tests) [26ms] ***
  3.   com.simonwacker.banking.AccountTest run in [26ms]. 2 errors occured
  4.      testCreditWithAmountBiggerThanOneMillionDollars() [11ms] 1 error occured
  5.        com.simonwacker.banking.AccountTest.testCreditWithAmountBiggerThanOneMillionDollars() threw a unexpected exception.
  6.          Expectation failure on verify:
  7.            sendEmail(Someone transfered 1 000 000 dollars.): expected: 1, actual: 0
  8.      
  9.      testCreditWithAmountEqualToOneMillionDollars() [5ms] 1 error occured
  10.        com.simonwacker.banking.AccountTest.testCreditWithAmountEqualToOneMillionDollars() threw a unexpected exception.
  11.          Expectation failure on verify:
  12.            sendEmail(Someone transfered 1 000 000 dollars.): expected: 1, actual: 0
  13.      
  14. ********************************************************

There’s still the sending functionality missing. We add it.

  1. public function credit(amount:Number):Void {
  2.   if (amount != null) {
  3.     if (amount >= 1000000) {
  4.       emailSender.sendEmail("Someone transfered 1 000 000 dollars.");
  5.     }
  6.     balance += amount;
  7.   }
  8. }

Executing the tests causes no errors now.

  1. ** InfoLevel **
  2. *** TestSuite <Generated TestSuite> (1 Tests) [18ms] ***
  3.   com.simonwacker.banking.AccountTest run in [18ms]. no error occured
  4. ********************************************************

That’s it. ;)
But there are still some things that we have not considered yet. What is if an email sender of value ,null’ gets set? What is if someone ,credits’ a negative amount?

In real life as you get more experienced writing tests and writing code you will just skip some of the little steps and combine them.

Download the sample code for Unit Testing with Mock Objects in Practice and Test Driven Development (TDD).
Download the as2lib snapshot from the 01.01.2005 which contains the unit testing and mock objects frameworks.

Saturday, January 1st, 2005

Unit Testing: Mock Objects Framework

Unit testing means testing a class in isolation. To be able to do this any collaborator must at least be replaced by some kind of stub implementation of it. While this is definitely a good thing and appropriate in some cases it is really awkward if you have many collaborators and if these collaborators have a rather large number of methods.
Another problem with stubs is that they are not flexible enough to meet every use case, especially the special ones that hardly ever occur (except you spent hours programming the stub).
Thus you need a more general and dynamic approach.

The solution is: Mock Objects.

Mock objects also take unit testing a step further. You do not just create a stub that acts as if it were real, you also verify that the class under test collaborates with the mock object in the expected way. That means for example that the class under test calls certain methods with certain arguments on the mock object.
To work efficiently with mock objects you of course need a framework that gives you the ability to create mock objects and work with them easily.
This article is about such a framework that is based on the approach the EasyMock Framework uses. So, if you have ever worked with easymock (it’s for Java) you should have no problem working with this one. The basic workflow is as follows:
Create a mock control for a specific class or interface, get the mock object from it, set your expectations, set the behavior of the mock object, switch to replay state, use the mock object as if it were a normal instance of your class and verify if all expectations have been met.
While this sounds a little complicated and awkward at first sight it really is not. Just take a look at the following demonstration:

  1. import org.as2lib.test.mock.MockControl;
  2.  
  3. // create mock control for class MyClass
  4. var myMockControl:MockControl = new MockControl(MyClass);
  5. // receive the mock object (it is in record state)
  6. var myMock:MyClass = myMockControl.getMock();
  7. // expect a call to the setStringProperty-method with argument ‘myString’.
  8. myMock.setStringProperty("myString");
  9. // expect calls to the getStringProperty-method
  10. myMock.getStringProperty();
  11. // return ‘myString’ for the first two calls
  12. myMockControl.setReturnValue("myString", 2);
  13. // throw MyException for any further call
  14. myMockControl.setDefaultThrowable(new MyException());
  15. // switch to replay state
  16. myMockControl.replay();
  17.  
  18. // the class under test calls these methods on the mock
  19. myMock.setStringProperty("myString");
  20. myMock.getStringProperty();
  21. myMock.getStringProperty();
  22.  
  23. // verify that alle expectations have been met
  24. myMockControl.verify();

If any expectation has not been met an AssertionFailedError gets thrown that explains what went wrong. In the above case no AssertionFailedError should be thrown because every expectation has been met but it hadn’t if we had called the getStringProperty-method only once on the myMock-mock.

There is much more that can be done with the framework but just refer to the api documentation that is included in the download (I generated it with the old version of as2api because the new does not work and it is thus a little complex. It may be clearer to open the as-file directly and take a look at the documentation there.).

Download the Mock Objects Framework for Flash (It’s actually a snapshot of the as2lib from the 01.01.2005. The mock objects framework is contained in the org.as2lib.test.mock package).

Sunday, November 21st, 2004

Naming a method ’set’

Be careful when you name a method ’set’ because this could cause unexpected behaviour. I stumbled across this when trying to figure out why my setAll-method in the org.as2lib.data.holder.list.PriorityList-class was not working. All I did was looping over a list and calling the set-method for each element. But the set-method strangely never got invoked.

  1. /**
  2. * @author Simon Wacker
  3. */
  4. class PriorityList {
  5.   public function set(index:Number, value):Void {
  6.     trace("About to invoke set(..).");
  7.   }
  8.   public function setAll(Void):Void {
  9.     set(0, "value");
  10.   }
  11. }

As you can see there are only two methods ’setAll’ and ’set’. And the setAll-method calls the set-method. One would expect the output ‘About to invoke set(..).’ when using the following test code.

  1. var list:PriorityList = new PriorityList();
  2. list.setAll();

But what happens is actually - nothing. You must change the call to the set-method as follows.

  1. /**
  2. * @author Simon Wacker
  3. */
  4. class PriorityList {
  5.   public function set(index:Number, value):Void {
  6.     trace("About to invoke set(..).");
  7.   }
  8.   public function setAll(Void):Void {
  9.     this.set(0, "value");
  10.   }
  11. }

Note the added ‘this’.
So, be careful when naming your methods like a keyword.

Friday, September 3rd, 2004

as2lib: 0.1 Beta Release

The as2lib finally goes beta.
So, what has changed?

  • Performance: We have improved the overall performance. Particularly in the data holder, overloading and reflection areas.
  • API: The Unit Testing System has been completely renewed, the restriction in the Output API to allow only string messages has been removed and a few grammatical mistakes have been rectified.
  • New Functionalities: We added methods in the StringUtil, ClassUtil and ArrayUtil classes as well as a SpeedEventBroadcaster, a StopWatch, priority data holders and much more. The aop framework is also included.
  • Fixed Bugs: We fixed a lot of bugs throughout the framework. It wouldn’t be appropriate to list them all here.

For a complete listing (sadly not fully complete because we were a little sloppy) check out the changelog on that is contained in the download of the as2lib 0.1 beta release.
We would like to receive a lot of feedback on this release.

Sunday, July 25th, 2004

AOP: AOP Framework for Flash Version 0.2

Version 0.2 is ready to hit the market. Following are the improvements that have been made:

  • The weaving process is 10 times faster.
  • The overall structure has been improved.
  • You can easily extend the framework with custom Aspects, Advices, Pointcuts and JoinPoints.
  • The weaving algorithm has been sourced out into a Weaver.
  • It is possible to directly weave into an object without affecting the underlying class.
  • Advices do not have to be classes anymore, a method and some further information is all you need (example below).

To show you the differences we are going to compare the Version 0.1 Example with the following example, that does exactly the same.
Our basis is again the class Account.

  1. import org.as2lib.core.BasicClass;
  2. import InsufficientBalanceException;
  3.  
  4. /**
  5. * @author Simon Wacker
  6. */
  7. class Account extends BasicClass {
  8.   private var balance:Number;
  9.  
  10.   public function Account(Void) {
  11.     balance = 0;
  12.   }
  13.  
  14.   public function credit(amount:Number):Number {
  15.     setBalance(balance + amount);
  16.     return balance;
  17.   }
  18.   public function debit(amount:Number):Number {
  19.     if ((balance - amount) < 0) {
  20.       throw new InsufficientBalanceException("Balance [" + balance + "] is not sufficient.", this, arguments);
  21.     }
  22.     setBalance(balance - amount);
  23.     return balance;
  24.   }
  25.   public function getBalance(Void):Number {
  26.     return balance;
  27.   }
  28.   public function setBalance(newBalance:Number):Void {
  29.     balance = newBalance;
  30.   }
  31. }

What this class does is debiting or crediting a specific amount. The debit() operation throws an InsufficientBalanceException in case the balance would be negative after debiting the amount.

  1. import org.as2lib.env.except.Exception;
  2.  
  3. /**
  4. * @author Simon Wacker
  5. */
  6. class InsufficientBalanceException extends Exception {
  7.   public function InsufficientBalanceException(message:String, thrower, args:FunctionArguments) {
  8.     super(message, thrower, args);
  9.   }
  10. }

As you can see these two classes are exactly the same as in the Version 0.1 Example.
The things changed are in the AO code. Following is the aspect.

  1. import org.as2lib.aop.Aspect;
  2. import org.as2lib.aop.aspect.AbstractAspect;
  3. import org.as2lib.aop.advice.AbstractAdvice;
  4. import org.as2lib.aop.JoinPoint;
  5. import org.as2lib.env.except.Throwable;
  6.  
  7. /**
  8. * @author Simon Wacker
  9. */
  10. class LoggingAspect extends AbstractAspect implements Aspect {
  11.   public function LoggingAspect(Void) {
  12.     addAdvice(AbstractAdvice.TYPE_AROUND, getLoggedOperationsPointcut(), aroundLoggedOperationsAdvice);
  13.   }
  14.  
  15.   private function getLoggedOperationsPointcut(Void):String {
  16.     return "execution(Account.debit()) || execution(Account.credit())";
  17.   }
  18.  
  19.   private function aroundLoggedOperationsAdvice(joinPoint:JoinPoint, args:FunctionArguments) {
  20.     trace(joinPoint.getInfo().getDeclaringType().getName() + "." + joinPoint.getInfo().getName() + "(" + args + ")");
  21.     trace("Before: " + Account(joinPoint.getThis()).getBalance());
  22.     var result;
  23.     try {
  24.       result = joinPoint.proceed(args);
  25.     } catch (exception:InsufficientBalanceException) {
  26.       trace(joinPoint.getInfo() + " throwed: " + exception.getClass().getFullName());
  27.     }
  28.     trace("After: " + Account(joinPoint.getThis()).getBalance());
  29.     trace("——————————————");
  30.     return result;
  31.   }
  32. }

When you compare this aspect with the one from Version 0.1 Example the first thing you probably recognize is the new operation aroundLoggedOperationsAdvice(). This operation contains exactly the programming logic that was in the AroundLoggedOperationsAdvice.execute() operation before. The AroundLoggedOperationsAdvice is now redundant and can be removed.
What has also changed is the way the advice is being added to the aspect. The first argument in the addAdvice() operation spcifies what type of advice it is: AbstractAspect.TYPE_AROUND. The second argument is the pointcut represented by a string and the third the method to be executed at a captured join point. It is of cource nevertheless possible to do it the old school way.
One little thing that has also changed is the method calls that have to be made to weave the whole thing. This is because the aspect is not responsible for weaving anymore but a seperate weaver.

  1. import org.as2lib.aop.Aspect;
  2. import org.as2lib.aop.Weaver;
  3. import org.as2lib.aop.weaver.SimpleWeaver;
  4.  
  5. var weaver:Weaver = new SimpleWeaver();
  6. weaver.addAspect(new LoggingAspect(), [Account]);
  7. weaver.weave();

We first create a new weaver and add an aspect to it. The first argument in the Weaver.addAspect() operation is the aspect and the second is an array containing the affected types, that means the types that shall be considered while weaving this aspect. The last call to the Weaver.weave() operation weaves all added aspects.
And that’s it. If you now execute the following example code:

  1. var account:Account = new Account();
  2. account.credit(1000);
  3. account.debit(500);
  4. account.debit(520);
  5. account.debit(480);

you will get the following output:

  1. Account.credit(1000)
  2. Before: 0
  3. After: 1000
  4. ——————————————
  5. Account.debit(500)
  6. Before: 1000
  7. After: 500
  8. ——————————————
  9. Account.debit(520)
  10. Before: 500
  11. Account.debit() throwed: InsufficientBalanceException
  12. After: 500
  13. ——————————————
  14. Account.debit(480)
  15. Before: 500
  16. After: 20
  17. ——————————————

Download the AOP Framework for Flash Version 0.2 (This is actually a CVS snapshot of the as2lib. The AOP framework is in the org.as2lib.aop package.).
Downlaod the Example.