Requirements
Prerequisite knowledge
Experience building Flex applications is
recommended.
Required products
Flash Builder (Download trial)
Sample files
flashbuilder_tdd_source.zip (6317 KB)
User level
All
elromdesign.com/blog
Content
Test Driven Development overview
Creating a test suite and test case in
Flash Builder 4
Implementing TDD techniques using
Flash Builder 4
FlexUnit 4 metadata
Created
29 November 2009
Page tools
Share on Facebook
Share on Twitter
Share on LinkedIn
Print
Was this helpful?
Yes
No
By clicking Submit, you accept the
Adobe Terms of Use.
Thanks for your feedback.
In this article I present some of the basics for getting started with Test Driven Development (TDD) using Flash Builder 4
and FlexUnit.
As Flash applications become more dynamic and complex, they become more difficult to maintain and scale, particularly
when business requirements change throughout the course of development. These challenges are significant and they are
common in all types of development, including mobile, web, and desktop applications.
Consider a scenario in which you need to make changes to a large application to meet new business requirements. How
do you know if the small changes you made broke other parts of the application? How can you ensure that the code is
bullet-proof, especially if you are not the person who wrote it?
For software engineers, this problem is neither new nor confined to a specific platform. Java and ASP developers have
been challenged with the same issues and have found Test Driven Development (TDD) a useful technique for creating
applications that can be easily maintained.
Flash has grown a long way from a small animation tool. The Adobe Flash Platform now comprises an ECMAScriptcompliant programming language as well as methodologies common to other programming languages that are
necessary for building large dynamic applications. In fact, Adobe and many other companies have found that TDD solves
many of the challenges that developers face in their development cycles every day.
I have noticed that while many developers have heard of TDD, they are reluctant to use TDD because they are unfamiliar
with it and afraid that using TDD will increase development time.
In my personal experience, I have found that using TDD correctly doesn't increase development time when used on
projects for which it is appropriate. In fact, TDD can reduce development time and simplify application maintenance. I've
also found that I can use FlexUnit on existing applications and apply TDD methods to any framework out there in some
way or another.
It is important to note that TDD is applicable even when there is a separate Quality Assurance (QA) department that
performs formal testing. TDD helps developers deliver more solid code, enabling QA to focus on other tasks, including
testing the user interface and creating the use cases that they need to test.
The TDD process consists of six simple steps (see Figure 1):
1. Add test - The first step is to understand the business requirements, think of all possible scenarios, and add tests
based on those scenarios. If the requirements are not clear enough, you can raise questions right away instead of
when the software is completed and will require much more effort to change.
2. Write failed unit test - This phase ensures that the test unit itself is working correctly. It will not pass, since you
haven't written any code.
3. Write code - During this phase you write the code in the simplest, most effective way to ensure that the test passes.
There is no need to include any design patterns, think about the rest of the application, or clean up the code. Your
goal is simply to pass the test.
4. Test Passed - Once you write all the code and the test passes you know that your test meets all the business
requirements and you can share the work with the customer or other members of the team.
5. Refactor - Now that the test is completed and you confirmed that it meets the business requirements, you can ensure
that the code is ready for production by replacing any temporary parameters, adding design patterns, removing
duplicate code, and creating classes to do the job efficiently. Ideally once the refactor phase is completed, the code
undergoes code review, which is essential to ensure that the code is in good shape and complies with the company's
coding standards. Following the refactoring and code review, the test should be run again to ensure that nothing was
broken in the process.
6. Repeat - When the unit test is completed, you can move to the next unit test and share the code with the customer or
other members of the team.
Using FlexUnit for testing Flex and ActionScript projects
FlexUnit is a unit testing framework for Flex and ActionScript 3.0 applications and libraries. It offers functionality similar to
JUnit, a Java unit testing framework. FlexUnit is used in many internal Adobe projects and is open source.
Flash Builder 4 provides integrated FlexUnit support, and allows you to create the scaffolding of the test unit
automatically, saving you time, eliminating the need to create the same classes over and over again, and ensuring the use
of best practices.
There are two versions of FlexUnit: FlexUnit 0.9 (also referred to as FlexUnit 1) and FlexUnit 4 (also referred to as FlexUnit
4). This article covers FlexUnit 4.
To use FlexUnit in previous versions of Flex Builder you had to download the FlexUnit SWC file and include it in your
project. Flash Builder 4 includes five SWCs automatically once you create tests. The following SWCs will be added under
your project's Referenced Libraries:
flexunit_0.9.swc
hamcrest-1.0.2.swc
flexunit-core-flex-4.0.0.2-sdk3.5.0.12783.swc
flexunitextended.swc
FlexUnitTestRunner_rb.swc
These SWCs include all the APIs for FlexUnit 0.9, FlexUnit 4, the test runner, and other libraries. The SWCs are maintained
as part of the Flex 4 SDK so there is no need to download FlexUnit or add them manually. They will be added
automatically once you add the tests.
5. In the New Test Suite Class dialog box, name the class CalculatorTestSuite.
6. Select New FlexUnit 4 Test (see Figure 3).
7. Click Finish.
A test suite is a composite of tests. It runs a collection of test cases. During development you can create a collection of tests
packaged into test suite and once you are done, you can run the test suite to ensure your code is still working correctly
after changes have been made.
Flash Builder 4 added the following class under the flexUnitTests folder:
package flexUnitTests
{
[Suite]
[RunWith("org.flexunit.runners.Suite")]
public class CalculatorTestSuite
{
}
}
The Suite metadata tag indicates that the class is a suite. The RunWith tag instructs the test runner to execute the tests
that follow it using a specific class. FlexUnit 4 is a collection of runners that will run a complete set of tests. You can define
each runner to implement a specific interface. You can, for example, specify a different class to run the tests instead of the
default runner built into FlexUnit 4.
Note: In FlexUnit 1 you can choose to generate setUp() and tearDown() stubs. These stubs are called automatically
when the test case starts (setUp) and ends (tearDown). They can be used to set up information and events before the test
starts and clear information and events to ensure you don't have any memory leaks. In FlexUnit 4 you can define these
methods using the metadata tags [Before] and [After], as you will see later in this article.
package flexUnitTests
{
public class CalculatorLogicTester
{
[Before]
public function setUp():void
{
}
[After]
public function tearDown():void
{
}
[BeforeClass]
public static function setUpBeforeClass():void
{
}
[AfterClass]
public static function tearDownAfterClass():void
{
}
}
}
We can now write our first method. We are creating a calculator so we need to creating methods that the calculator helper
utility will use to perform all the calculations, well start with the add method. Create a test method by starting with the
[Test] metadata, than put code. The code I am creating wont pass the test until I will implement the method, see below:
[Test]
public function testAdditionMethod():void
{
Assert.fail("Test method not yet implemented");
}
Lastly, remember to add the test case you would like to test into the CalculatorTestSuite test suite. Add the line in bold
below to CalculatorTestSuite.as:
package flexUnitTests
{
[Suite]
[RunWith("org.flexunit.runners.Suite")]
public class CalculatorTestSuite
{
public var calculatorLogic:CalculatorLogicTester;
}
}
2. In the Run FlexUnit Tests dialog box, select the test cases (see Figure 7).
3. Click OK.
4. When the application finishes, review the test status results in your browser. (see Figure 8).
We write the test before the actual code. In our case we figure out that we have a static method that will calculate adding
two numbers and than we should be able to assert to ensure the addition calculation was performed. Once you save the
document you get compile time errors. See figure 11. This is a good thing since the compiler gives you instructions of what
you need to do next, which is create the helper class and method.
<!-- This is an auto generated file and is not intended for modification. -->
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx" minWidth="955" minHeig
<fx:Script>
<![CDATA[
import flexUnitTests.CalculatorLogicTester;
public function currentRunTestSuite():Array
{
var testsToRun:Array = new Array();
testsToRun.push(flexUnitTests.CalculatorLogicTester);
return testsToRun;
}
<fx:Declarations>
<!-- Place non-visual elements (e.g., services, value objects) here -->
</fx:Declarations>
<flexui:FlexUnitTestRunnerUI id="testRunner">
</flexui:FlexUnitTestRunnerUI>
</s:Application>
As you can see there is a GUI called FlexUnitTestRunnerUI, which is similar in functionality to the FlexUnit TestRunner
class in FlexUnit 0.9. Essentially, the application adds the entire test and runs the test in the UI.
In FlexUnitApplication.mxml you can see that FlexUnit uses the FlexUnit 4 runner:
However, the framework is flexible, so developers can create and use their own runners, but still use the same UI. In fact,
currently there are runners for FlexUnit 1, FlexUnit 4, Fluint, and SLT.
The test runner in FlexUnit is a UI component that will create an instance of the test suite and allow you to add all the
tests you would like to run. The test runner will also display the test information in your browser.
Note: There is currently a test runner for Flex applications but not for pure ActionScript applications. You can, however,
create a Flex project to test your pure ActionScript code or use Ant tasks. See
http://opensource.adobe.com/wiki/display/flexunit/CI+ReadMe for more information.
In addition to the files you see in the Project Navigator there are more under the surface, including:
_FlexUnitApplication_FlexInit-generated.as
_FlexUnitApplication_mx_managers_SystemManager-generated.as
_FlexUnitApplication-binding-generated.as
_FlexUnitCompilerApplication_FlexInit-generated.as
_FlexUnitCompilerApplication_mx_managers_SystemManager-generated.as
FlexUnitApplication-generated.as
FlexUnitApplication-interface.as
FlexUnitCompilerApplication-generated.astem
FlexUnitCompilerApplication-interface.as
FlexUnitTestRunner_properties.as
These classes handle tasks such as the binding, styles, and definitions for FlexUnit.
Note: The bin-debug/generated package holds all files that get created by the MXMLC compiler and are normally
invisible to you. To see them, select the project, then right-click and select Properties. Under Flex Compiler in Additional
Compiler Arguments add the following option: -keep-generated-actionscript or -keep-generated-actionscript=true.
package com.elad.calculator.utils
{
public final class CalculatorLogicHelper
{
public static function additionMethod(value1:Number, value2:Number):Number
{
var retVal:Number = value1+value2;
return retVal;
}
}
}
Test passed
Now without any changes to the testAdditionMethod test method it passes and you will get a green light, meaning the
test has succeeded (see Figure 12).
Refactor code
Now that your test passed you can refactor the code in order to get it ready for production. For instance, you may add a
design pattern to replace a block of if..else statements.
In this case there is nothing to refactor since the code is so simple.
Rinse and repeat if desired
You can continue to create the unit tests for the subtraction, multiplication, and division methods. The complete code is
below:
package com.elad.calculator.utils
{
public final class CalculatorLogicHelper
{
public static function additionMethod(value1:Number,
value2:Number):Number
{
var retVal:Number = value1+value2;
return retVal;
}
public static function subtractionMethod(value1:Number,
value2:Number):Number
{
var retVal:Number = value1-value2;
return retVal;
}
public static function multiplicationMethod(value1:Number,
value2:Number):Number
{
var retVal:Number = value1*value2;
return retVal;
}
public static function divisionMethod(value1:Number,
value2:Number):Number
{
var retVal:Number = value1/value2;
return retVal;
}
}
}
The complete unit test class code is in the CalculatorLogicTester.as file included with the sample files for this article.
Assertion methods
So far you have used only the assertEquals assertion method in the test cases. There are, however, many other assertion
methods (see Table 1).
Assertion method
Meanings
assertEquals
assertContained
assertNotContained
Asserts that the first string is not contained in the second one.
assertFalse
assertTrue
assertMatch
assertNoMatch
assertNull
assertNotNull
assertNotUndefined
assertUndefined
assertStrictlyEquals
assertObjectEquals
If you omit the message string, you'll get the default message.
In the editor, type Assert. to see code hints for the available assertion methods.
The Hamcrest assertion method
In addition to the standard assertions FlexUnit 4 supports new methods thanks to the Hamcrest library, which is based on
the idea of matchers. Each matcher can be set to match conditions for your assertions.
Here is an example taken from Drew Bourne's project that tests whether one number is close to (within a specified
threshold) of another number:
package org.hamcrest.number
{
import org.hamcrest.AbstractMatcherTestCase;
public class CloseToTest extends AbstractMatcherTestCase
{
[Test]
public function comparesValuesWithinThreshold():void
{
assertMatches("close enough", closeTo(1, 0.5), 1.5);
assertDoesNotMatch("too far", closeTo(1, 0.5), 1.6);
}
[Test]
public function hasAReadableDescription():void
{
assertDescription("a Number within <0.1> of <3>", closeTo(3, 0.1));
}
}
}
FlexUnit 4 metadata
The FlexUnit 4 framework is based on metadata tags. So far you've seen [Suite], [Test], and [RunWith]. Here are some
other common metadata tags:
[Ignore] - Causes the method to be ignored. You can use this tag instead of commenting out a method.
[Before] - Replaces the setup() method in FlexUnit 1 and supports multiple methods.
[After] - Replaces the teardown() method in FlexUnit 1 and supports multiple methods.
[BeforeClass] - Allows you to run methods before a test class.
[AfterClass] - Allows you to run methods after a test class.
Useful metadata you can use in your test cases
The calculator example was intentionally simple to help you get up to speed with TDD. This section covers a practical
overview of FlexUnit metadata that you can use on more complex projects.
To begin, create a new FlexUnit 4 Test Case class and name it FlexUnitTester. Copy the code from the FlexUnitTester.as
sample file into the new class.
The method following the Before metadata will be run before every test, and the method following After tag will be run
after every test:
[Before]
public function runBeforeEveryTest():void
{
// implement
}
[After]
public function runAfterEveryTest():void
{
// implement
}
The following example demonstrates the expected attribute of the Test metadata. The rangeCheck method creates a new
Sprite object. The code will produce a successful test, because the child at index 1 doesn't exist and thus the code causes
an exception during runtime.
[Test(expected="RangeError")]
public function rangeCheck():void
{
var child:Sprite = new Sprite();
child.getChildAt(1);
}
Here is another example that shows an expected assertion error. The testAssertNullNotEqualsNull Test expects an
AssertionFailedError error. The test will fail, because the assertEquals method will succeed (since null equals null).
[Test(expected="flexunit.framework.AssertionFailedError")]
public function testAssertNullNotEqualsNull():void
{
Assert.assertEquals( null, null );
}
In FlexUnit 1 you had to comment out code to ignore a method that you didn't want to test any more. The Ignore
metadata tag makes it easier to skip a method.
If you want to set the order of Test, Before, or After methods, you can add the order attribute, like this:
[Test(order=1)]
public function checkMethod():void
{
Assert.assertTrue( true );
}
Asynchronous tests
If you've worked with FlexUnit 1 in the past, you know that it isn't always easy to create asynchronous tests and test event
driven code. I often found myself modifying existing classes just to accommodate FlexUnit or creating tests in a hackish
way. One of Fluint's biggest advantages is the ability to accommodate multiple asynchronous events. FlexUnit 4
incorporated Fluint functionality to support enhanced asynchronous tests, including asynchronous setup and teardown.
This feature can be used by every test stub. To see this feature in action, create a new Test Case Class, name it
AsynchronousTester, and copy in the code from the AsynchronousTester.as sample file.
This example tests a service call. The testServiceRequest() function makes a service call to retrieve an XML file that is
included with the project. The test sets a timeout to specify the amount of time to wait before it fails. The ResultEvent fires
before the timeout is reached and the test succeeds. In the second test, testFailedServiceRequest(), the service request
asks a file that doesn't exist. Since the check is for the fault event the test passes, as expected.
The last test is for a scenario in which multiple asynchronous calls are made.
Theories
FlexUnit 4 introduces the concept of theories. A theory, as the name suggests, allows you to create a test to check your
assumptions about how a test should behave. This type of test is useful when you have to test a method that can return
large or even unbounded values. The tests take parameters (data points) and these data points can be used in conjunction
with each test. You can use this functionality to check if the results are within a specified range.
To see how it works, create a new Test Suite Class, name it FlexUnit4TheorySuite, and copy in the code from
FlexUnit4TheorySuite.as, which includes the following:
[Theory]
public function testNumber( number:Number ):void
{
assumeThat( number, greaterThan( 0 ) );
assertThat( number, instanceOf(Number) );
}
In this case, the theory checks that the number is greater than zero and verifies the variable type.
Testing user interfaces
Although it may be disputable if UI testing is part of TDD, in FlexUnit 4 you have the ability to create tests that can check
user interfaces and MXML components.
FlexUnit 4 includes the concept of sequences, and you can create sequences that include all the operations you would like
to perform on a UI.
The FlexUnit4CheckUITester.as sample file shows an example of this functionality.
The setUp method creates a component that holds a button. Next, instead of adding the component to the view, it calls
UIImpersonator.addChild().
The testButtonClick method is a simple async test that sets a handler and dispatches a mouse event. The handler
handleClickEvent simply checks the event string type.
The second test, testButtonClickSequence, includes a sequence that sets the button's label name. Next, SequenceWaiter
instructs the sequence to wait until the button click event is dispatched. Before the event is dispatched, the code calls
addAssertHandler( handleButtonClickSqEvent, passThroughData ).
The handleButtonClickSqEvent method handles the event and compares the button label of the test with the pass
through data.
Samples
Twitter Trends
Flex 4.5 reference applications
Mobile Trader Flex app on Android Market