flexunit 4 for contributors
Post on 12-Sep-2014
1.893 views
DESCRIPTION
Architecture overview of the FlexUnit 4 projectTRANSCRIPT
FlexUnit 4for Contributors
Michael LabriolaDigital Primates
twitter.com/mlabriolablogs.digitalprimates.net/codeSlinger/
Who are you?
Michael LabriolaFlexUnit 4 LeadSenior Consultant at Digital PrimatesFlex GeekTeam MentorFan of Working Code
What is this session about?
FlexUnit 4 Architecture
This session is designed to give a crash course in the FlexUnit 4 architecture for those we hope will contribute to the project
A few things to Note
FlexUnit 4 is an open-source project hosted by Adobe but driven by the community
FlexUnit 4 is named with the 4 suffix as it has approximate feature parity with JUnit 4. It can be used with any version of Flex
FlexUnit 4 can be compiled without Flex dependencies making it viable for AS only
projects as well
Starts with a Test[Test]
public function testMe():void {
}
Test
FlexUnit 4 Test Casepackage {
public class SomeTestCase {
[Test]
public function testMe():void {
}
}
}
Test Case
TestTest
FlexUnit 4 Test Casepackage {
public class SomeTestCase {
[Test]
public function testOne():void {
}
[Test]
public function testTwo():void {
}
[Test]
public function testThree():void {
}
}
}
Test Case
TestTest TestTest
TestTest TestTest
FlexUnit 4 Test Suitepackage {
[Suite]
[RunWith("org.flexunit.runners.Suite")]
public class SomeTestSuite {
public var someTestCase:SomeTestCase;
}
}
Test Suite
Test Case
TestTest TestTest
TestTest TestTest
FlexUnit 4 Test Suitepackage {
[Suite]
[RunWith("org.flexunit.runners.Suite")]
public class SomeTestSuite {
public var someTestCase:SomeTestCase;
public var someTestCase2:SomeOtherTest2;
public var thirdTestCase:ThidTestsCase;
public var anotherSuite:AnotherTestSuite;
}
}
Test Suite
Test Case
TestTest TestTest
TestTest TestTest
Test Case
TestTest TestTest
TestTest TestTest
Test Case
TestTest TestTest
TestTest TestTest
Test Suite
Test CaseTest Case
Test CaseTest Case
Flex Unit 4 Core core = new FlexUnitCore();
core.run( SomeTestSuite );
FlexUnitCoreFlexUnitCore
Test Suite
Test Case
TestTest TestTest
TestTest TestTest
Test Case
TestTest TestTest
TestTest TestTest
Test Case
TestTest TestTest
TestTest TestTest
Test Suite
Test CaseTest Case
Test CaseTest Case
Flex Unit 4 CoreThe FlexUnit core is responsible for executing
objects that implement an IRequest interface
You can pass it an IRequest directly or, if you pass it another type of class, it will attempt to create a Request on your behalf
Flex Unit 4 RequestFor right now, imagine a
request as an object that wraps your tests when they are presented to the core
Requests can be filtered and sorted to control the subset and order of tests executed
Request Runner:Suite
Test Suite
Test Case
TestTest TestTest
TestTest TestTest
Test Case
TestTest TestTest
TestTest TestTest
Test Case
TestTest TestTest
TestTest TestTest
Test Suite
Test CaseTest Case
Test CaseTest Case
Flex Unit 4 CoreWhen creating your own
requests, you generally use the methods of the static Request class
Common methods include:
aClass()classes()runner()method()sortWith()filterWith()
Request.method( TestAssert, "testAssertTrue” )
Request.classes( TestAssert, TestAsynchronous)
.filterWith( someDescription )
.sortWith( mySortFunction );
Flex Unit 4 CoreIf you pass FlexUnit 4’s core anything other than
an IRequest, the core uses these methods to generate a Request object before processing continues
Ultimately, FlexUnit4 runs 1 and only 1 request per run. So, if you pass it multiple classes, etc. these are all wrapped in a single Request before execution begins
Flex Unit 4 RequestThe key property of the IRequest that the FlexUnit
core needs is the IRunner.
IRunner is an interface implemented by any object capable of executing a specific type of test
For example, there is a runner that understands the work needed to execute a suite. A separate runner understands how to execute test cases and methods.
Flex Unit 4 RequestTo determine the correct
IRunner for the request, the Request object kicks off a recursive process of introspecting each class
Identification ProcessIntrospect the classIdentify the runner for the classIdentify any childrenRun this process on each child to
identify their correct runnerReturn the top level runner for
the Request
The Correct RunnerFlexUnit 4 determines the
correct runner for each class using builders
Builders are objects responsible for building the correct runner for a class
We try multiple possible builders until a compatible one is identified
Default PossibilitiesIgnored BuilderMetaData BuilderSuite Method BuilderFlex Unit 1 BuilderFluint 1 BuilderFlexUnit 4 Builder
Ignored BuilderLooks for the [Ignore] metadata on top of the class
If this metadata is found, it creates an instance of the IgnoredClassRunner, which simply knows how to report that this class has been ignored back to the core
MetaData BuilderLooks for the [RunWith] metadata on top of the class
(remember our suite example?)
If this metadata is found and the runner specified can be found, the builder checks that it implements the IRunner interface. If so, it creates an instance of the specified runner to run this class
This provides a huge hook for extensibility
Suite Method BuilderChecks if the given class specifies its tests with a static
suite() method
If this method is found, it creates an instance of the SuiteMethodBuilder which will later decipher the contained tests
FlexUnit 1 BuilderChecks if the given class descends from
flexunit.framework.TestCase in the FlexUnit .9 library
If this is a subclass of TestCase, it creates an instance of the FlexUnit1ClassRunner.
Fluint 1 BuilderChecks if the given class descends from
net.digitalprimates.fluint.tests.TestCase or net.digitalprimates.fluint.tests.TestSuite in the Fluint (presently 1.2) library
If this is a subclass of either fluint class, it creates an instance of the Flunit1ClassRunner.
Note, possibility is only checked *if* we are compiled for Flex compatibility. Fluint has Flex dependencies
FlexUnit 4 BuilderA catch-all for any remaining classes not identified by
one of the previous builders
Creates an instance of the BlockFlexUnit4ClassRunner()
CoreCore
Corrected View with Core
The core executes the runner contained within the Request.
In this example, the outer most runner would be a Suite class. It would then create a new Suite runner for each of the suites inside. Each of those suites would create a BlockRunner for their test cases and another Suite runner for their contained suite.. Etc.
Request Runner:Suite
Test Suite A
Test Case 1
TestTest TestTest
TestTest TestTest
Test Case 2
TestTest TestTest
TestTest TestTest
Test Case 3
TestTest TestTest
TestTest TestTest
Test Suite C
Test Case 7Test Case 7
Test Case 8Test Case 8
Test Suite B
Test Case 4
TestTest TestTest
TestTest TestTest
Test Case 5
TestTest TestTest
TestTest TestTest
Test Case 6
TestTest TestTest
TestTest TestTest
Test Suite D
Test Case 9Test Case 9
Test Case 10Test Case 10
Request.iRunner(Suite)
Request.iRunner(Suite)
Suite ASuite A Suite BSuite B
BlockRunner 1BlockRunner 1
BlockRunner 2BlockRunner 2
BlockRunner 3BlockRunner 3
Suite CSuite C
BlockRunner 7BlockRunner 7
BlockRunner 8BlockRunner 8
TestsTests
TestsTests
TestsTests
TestsTests
TestsTests
OutputAs test run within the FlexUnit 4 runners, we need
some way to monitor progress
This is accomplished via two types of classes. An implementation of IRunNotifierAn implementation of IRunListener
IRunNotifierIRunNotifiers are a type of class that FlexUnit 4 uses to
notify others of an event that occurred during testing. You can draw parallels with IEventDispatcher
IRunNotifiers are used by the IRunner classes to notify others of the following conditions:
TestRun: Started/FinishedTest: Started/Failed/Ignored/Finished/
AssumptionFailures
IRunNotifierUnlike an event dispatcher IRunNotifiers have
limitations on what types of objects can listen to their events.
IRunNotifiers have addListener() and removeListener() methods that accept an IRunListener as an argument
IRunListenerWhile there is generally only one IRunNotifier present
in the system at any given time, there can be multiple IRunListeners
IRunListeners listen to these messages from the notifier and optionally perform some action. Example of IRunNotifiers built into FlexUnit include TraceListener, TextListener, CIListener, XMLListener and others for FlashBuilder integration
CoreCore
With ListenersRequest Runner:Suite
Test Suite A
Test Case 1
TestTest TestTest
TestTest TestTest
Test Case 2
TestTest TestTest
TestTest TestTest
Test Case 3
TestTest TestTest
TestTest TestTest
Test Suite C
Test Case 7Test Case 7
Test Case 8Test Case 8
Test Suite B
Test Case 4
TestTest TestTest
TestTest TestTest
Test Case 5
TestTest TestTest
TestTest TestTest
Test Case 6
TestTest TestTest
TestTest TestTest
Test Suite D
Test Case 9Test Case 9
Test Case 10Test Case 10
Request.iRunner(Suite)
Request.iRunner(Suite)
Suite ASuite A Suite BSuite B
BlockRunner 1BlockRunner 1
BlockRunner 2BlockRunner 2
BlockRunner 3BlockRunner 3
Suite CSuite C
BlockRunner 7BlockRunner 7
BlockRunner 8BlockRunner 8
TestsTests
TestsTests
TestsTests
TestsTests
TestsTests
UIListenerUIListener
XMLListenerXMLListener
CIListenerCIListener
RunNotifierRunNotifier
Update a UIUpdate a UI
Send Data to ServerSend Data to Server
Send Data to Flash BuilderSend Data to Flash Builder
Running TestsThe BlockFlexUnit4ClassRunner is the heart of running
FlexUnit 4 tests.
The block runner is responsible for iterating through tests in a given class and executing them
While doing this it uses two very important concepts: recursive sequences and decoration
Before/Test/AfterJust to be sure everyone is on the same page FlexUnit 4
has several pieces of Metadata which, when present, control the flow of test execution.
These pieces of metadata include BeforeClass, Before, Test, After and AfterClass
Multiple methods can be marked by any of these pieces of metadata
Before/Test/AfterBeforeClass
Beforetest1
AfterBefore
test2After
AfterClass
[BeforeClass]
public static function meFirst():void {
}
[Before]
public function beforeEachTest():void {
}
[Test]
public function test1():void {
}
[Test]
public function test2():void {
}
[After]
public function afterEachTest():void {
}
[AfterClass]
public static function afterEverything():void {
}
First SequenceBeforeClassActual Test ClassAfterClass
Sequences are managed by a class called a StatementSequencer internal to FlexUnit 4
Sequences are responsible for ensuring that step 1 finishes before step 2, etc.
The first sequence in this example is BeforeClass, ActualTestClass, AfterClass
Second Sequencetest1test2
Sequences can also have entire additional sequence as steps
For example, in our last set of steps, we listed Actual Test Class as a step.
Running the Actual Test Class, however, means sequencing through each child to execute it
Third SequenceBefore Sequence
test1After Sequence
Executing an actual test, requires that we run any methods marked Before, the test, then any methods marked After
However, multiple methods can actually be marked Before or After
So, in reality, the Before and After calls are actually sequences themselves with a test in the middle
RealityBefore Class Sequence
BeforeClass 1..nTest Class Sequence
Test 1 SequenceBefore Sequence
Before 1..nTestAfter Sequence
After 1..nTest 2 Sequence
Before SequenceBefore 1..n
TestAfter Sequence
After 1..nAfter Class Sequence
AfterClass 1..n
Sequences (and the StatementSequencer) are so important because there are thousands of sequences in even a moderately complication test suite
In our simple two test example here, we use at least 10
Why?The reason we go through all of this trouble with
sequencer is that we simply can’t run methods in order
We have two things going against us, frames and lack of threads.
If anything async has to happen… EVER.. We can no longer rely on method completion to act as an indication that we can move forward
TokensTo handle some of this async nature, we use a
concept called an AsyncTestToken
A token is simply a small object that is passed around the testing system. Object A passes a token to Object B when Object B has work to do. When Object B is done with its work, it notifies Object A by calling a method on this token
TokensThis allows every operation and every runner to
be potentially asynchronous. Further, it keeps very recursive items decoupled
It also has great implications for future support of multiple security domains, etc.
DecorationSequencers are used for every operation that
might be potentially asynchronous
Decoration are the second concept I mentioned; Decorations are used to ‘wrap’ the invocation of a method with code that should be executed before or after the method, synchronously
Wrappingvar statement:IAsyncStatement = methodInvoker( method, test );
statement = withPotentialAsync( method, test, statement );
statement = withPotentialTimeout( method, test, statement );
statement = possiblyExpectingExceptions( method, test, statement );
statement = withStackManagement( method, test, statement );
Stack Management
Expecting Exception
Potential Timeout
Potential Async
Method Invoker
Stack Management Expecting Exception Potential Timeout Potential Async Method Invoker Potential Async Potential Timeout Expecting ExceptionStack Management
Why?This actually enhances performance and
extensibility. Each of these level independently checks the test to see if it is needed
If it is not, it acts as a noop. If the layer is needed, for example, we specify async, the logic for that layer is instantiated and enabled to ensure the test is handled properly
Stack And FrameThe stack and frame management decorator
implements green threading to deal with flash frames
It tracks how much time in each frame we have used up. When we get close to the end of a frame, we defer the test until the next frame to ensure we never timeout or lock the player during testing
ExceptionThe expecting exception decorator implements
logic to catch exceptions thrown during the test.
If the user indicates that they are expecting an exception then this code compares any thrown exception against the specified one. If they match, all is good. If they don’t or the test doesn’t throw an exception, then this decorator causes the test to fail
Potential TimeoutThe potential timeout decorator implements a
timer to ensure the test fails if it exceeds the specified number of milliseconds.
Potential AsyncThe potential async decorator creates the
infrastructure to monitor an asynchronous test.
As this infrastructure carries overhead, we only create it when the user specifics a given test is asynchronous
Method InvokerThe method invoker actually executes the test
method and captures any errors that occur during the method invocation
ResourcesFlex Unit 4!http://opensource.adobe.com/wiki/display/flexunit/FlexUnit
Labriola’s Bloghttp://blogs.digitalprimates.net/codeSlinger/
Follow us on twittermlabriola and flexunit