Tuesday, 28 September 2010

Harnessing the Drive in Test Driven Development

I had an interesting exchange with someone on twitter last night - he'd been working on some math, that kind of looked right to him now, but had no way of knowing whether it was 'right' or not.

I teased him that he should have done TDD, but he felt that TDD meant you had to know your API ahead of coding, and his situation was evolving, so that ruled out TDD.

I was arguing that actually TDD is ideal for when you're not quite sure where you're headed - a view point that didn't fly with his experience - so this is an attempt to further explain that sentiment.


Your brain and body will try to resist TDD

A common barrier to adopting TDD (this is what my colleagues and peers come back with over and over) is "I don't know enough about what I'm doing to write the test yet."

My response is that if you don't know enough about what you're doing to write the test yet, you sure as hell don't know enough to write the code yet!

Test Driven Development shifts a ton of that 'wtf am I trying to do?' pain to the front of the process. And that's hard to get used to. It exposes what you don't have clarity about - when you really just want to get on and pretend you do know where you're headed.

So - how can TDD possibly help when you don't have a clear idea of your direction?


TDD means more than one kind of test

I do 3 kinds of tests. End-to-end tests, integration tests and unit tests. Combining all 3 test varieties is the key to driving evolving development. (Which, IMHO, is the only kind there really is.)

I write the end-to-end tests first. An end-to-end test describes a user story. If you don't know what your user stories are then you need to get those together before you take another step. User stories will be numerous, even in the simplest app, so don't overwhelm yourself - just start with the shortest, simplest user story in your requirements.


User story / end-to-end tests

In a complex app, the user stories are rarely actually end-to-end (startup to shutdown) but they capture a unit of meaningful interaction with the application.

There is only one user story end-to-end test in each test case. Some examples from my current app include:

LibrarySimpleSearchReturnsInvalid
LibrarySimpleSearchProducesNoResults
LibrarySimpleSearchProducesResults
LibraryShowAllShowsAllItems
LibraryAdvancedSearchProducesResultsByType
LibraryAdvancedSearchProducesResultsByExclusion

... you get the idea.

In each case, the test recreates the user experience from the moment of opening the library (which is a searchable, browsable set of resources of different types - jpg, document, video etc) until they receive their search results.

This means that it's a UI driven test. I have the test code enter text, push buttons etc, and I delve into the display list to verify that the correct items / text etc have appeared on screen at the end, and usually this is asynchronous to allow time for transitions.


Integration / functional area tests

These test a component of functionality. For example the search window, or the results viewer.

Unlike unit tests they make use of real concrete instances of the classes needed to fully instantiate and work with the components being tested. If the functional area depends on the framework to wire it together, the framework is instantiated (robotlegs in my case) in order to wire up the component.

In my current app I have an integration tests for the main menu:

NestedMenuTest

This menu has multiple layers of nested items and has to collapse all / expand all / auto-collapse and so on in response to checkbox clicks. My integration tests check that the scrolling behaves itself when the items are being expanded/collapsed.

test_max_scroll_then_collapseAll_resolves
test_mid_scroll_then_expandAll_keeps_top_visible_item_in_place_and_scales_scroller

and so on...


Usually, integration tests are event driven - I kick it all off by manually firing an event. Often, but not always, they require you to use the display list to verify the results.


Unit / API tests

These test that a specific class does what it is supposed to. They test all the public (API) functions of a class, sometimes multiple times if there are errors to be thrown or alternative paths through the class itself.

There is a wisdom that says test all API except for property accessors. I tend to test my property accessors as well, because there is no limit to what I can screw up and it's faster to get them right them at this point than when the error emerges later.

Instead of using concrete instances of their complex dependencies (eg services), my unit tests make use of mocks (using Drew Bourne's Mockolate) to verify that they've acted upon those classes correctly.

If I was testing that a command pulled values from the event that triggered it, did some jiggery pokery with these values and then used the results in calling a method on the appropriate service, I would mock the services to verify that call, rather than try to test against the results of the call.

Here, I've mocked the two services, lessonLoaderService / joinedLessonLoaderService:
public function testLoadsLessonIfRequestEventNotJoinedLesson():void{
var lessonLoadRequestData:ILessonLoadRequestData = new LessonLoadRequestDTO("test","testSwfPath", true, '', false);
var testEvent:LessonDownloadEvent = new LessonDownloadEvent(LessonDownloadEvent.LESSON_DOWNLOAD_READY, lessonLoadRequestData);
instance.event = testEvent;
instance.execute();

verify(instance.lessonLoaderService).method("loadLesson").args(equalTo('testSwfPath'));
verify(instance.lessonLoaderService).method("loadLesson").once();
}

public function testLoadsJoinedIfRequestEventIsJoinedLesson():void{
var lessonLoadRequestData:ILessonLoadRequestData = new LessonLoadRequestDTO("test","testSwfPath", true, '', true);
var testEvent:LessonDownloadEvent = new LessonDownloadEvent(LessonDownloadEvent.LESSON_DOWNLOAD_READY, lessonLoadRequestData);
instance.event = testEvent;
instance.execute();

verify(instance.joinedLessonLoaderService).method("loadLesson").args(equalTo('testSwfPath'));
verify(instance.joinedLessonLoaderService).method("loadLesson").once();
}


Putting it all together

Often, we put applications together from the bottom up. With half an eye on the requirements, we start thinking about interfaces and event types and functional areas. This works, but it can also result in some YAGNI code, as well as throwing code out that seemed like it was going to be relevant until you realised that the requirements weren't complete.

I think there's more sanity in a work flow that runs this way:

1) User story
2) End-to-end test that verifies this user story (or part of it - this can evolve)
3) Integration tests for the functional areas required to fulfil the end-to-end-test
4) Unit tests for the classes required to provide the functional areas
5) Code to pass 4, to pass 3, to pass 2, to verify against 1

... and when you have no fails, then add to 2, add to 3, add to 4, do 5, rinse and repeat etc.

Doing it this way, the consequences are:
  1. A lot of head scratching early in each cycle (creating end-to-end tests is hard).
  2. Usually having failing tests in your test suite, until you're done adding a feature/user story.
  3. Always being able to tell what the hell you were doing when you last stopped ... because your failing tests make that obvious.
  4. Never writing code that doesn't actually add value to the project by contributing to the implementation of a user story.
  5. Always working towards a 'shippable' goal, which is good for the client (and your cash flow if you bill against features) and also allows real user feedback to improve the work still to be done.
  6. Reduced cognitive load for you at a micro level - you fix problems in the code while that part of the code is what you're focussed on.
  7. Reduced cognitive load for you at a macro level - you don't have to hold the 'where am I going' part in your head, or remember to test manually, because your user story tests have that covered.


I would argue that as a consequence of those last two there's a bigger reward: being able to show up more fully for the rest of your life. A bug, or a concern about whether I've really implemented feature X correctly, impacts on my ability to be present for my family. I'm kind of not-really-there at dinner, because 90% of my brain is background processing code stuff. This still happens with TDD, but it happens a lot less.

So, if you don't find that TDD is improving your code and your process - not just your output but also your enjoyment - then my (cheeky) suggestion is that you've not discovered what it's really about yet.

In my experience, TDD is fun. Chemicals in my brain fun.


PS. If you haven't already got it, this book is essential reading on harnessing the Drive in TDD: http://www.growing-object-oriented-software.com/ Props to Joel Hooks for recommending it.

Friday, 3 September 2010

Air Swf loading anomolies

The application I do most of my work on (An AIR 1.5 app) loads swfs in 2 ways.

There are modules of functionality that are loaded into the application sandbox using the secure loading method that I've blogged about here before.

There are also 'lessons' which consist of multiple swfs that are loaded into the non-application sandbox. If a lesson consists of 4 swfs then they are all loaded, and then they are added to / removed from the stage as the user browses through the lesson. When another lesson is loaded the previous lesson swfs are first unloaded.

These swfs are having code injected into their timelines, but they also contain code at timeline depth and in nested MovieClips. This is generally just simple 'show this label on MouseOver' functionality. Nothing fancy.

I found a few months ago that loading the swfs serial - as you would expect to do [by waiting for one to INIT before loading the next] - caused the code in the nested MovieClips to *often* fail - on Mac or PC. I tried to track down the cause of this, and through my experiments I discovered that loading the swfs parallel seemed to solve the problem. So, unable to discover a reason I went with the approach that worked.

Now the problem has reared its ugly head again - but only on PC. On Mac, all code works fine all the time.

On PC (Windows XP), the timeline code, and the injected code, are always fine. But MovieClip code - whether done in a separate class file or just put into the MC timeline, is borked in SOME swfs, depending on what order the swfs INIT in.

So far I've discovered the following:

1) Loading the swfs serial, MC code is frequently borked on Mac or PC.

2) Loading the swfs parallel, code borking is dependent on the INIT order. [Might this indicate that the swf isn't fully loading when it INITs first?]

3) Whether the code is directly in the MC, or just in the MC timeline, it's just not there - even a stop() doesn't work.

4) I've replicated the problem with simple assets (a 2 frame MC with diff coloured squares and a stop() on frame 1), guaranteed not to clash with any other assets in any other swf being loaded.

5) The swfs most likely to be borked are those with the largest file size. BUT removing some assets to reduce the file size doesn't appear to help.

6) The swfs are currently being exported from Flash Pro CS4. I'm downloading CS5 trial just incase it helps. They're FP10.

7) Some swfs are only borked if they INIT first. Some are able to tolerate being the first to INIT without problems. Some are borked if they're first OR second (of six in that case) to load.

8) Adding a pause after all the swfs have loaded, before I do anything else, makes no difference.

9) Air runtime is 2.0.3


--- EDIT - conclusion

I think I've found the source of the problem.

The load order and the broken movieclip code are both symptoms of the same problem: the swf has not loaded into memory correctly.

The broken swfs are usually the largest, and when they INIT ahead of much smaller swfs it's probably a sign of incomplete loading, hence the correlation with Level-1 code not running. (Level-0 is timeline, level-1 is code inside MCs on that timeline etc).

Having run the tests hundreds of times I've found that this problem occurs approximately 50% of the time in a Parallels-XP virtual PC, and occasionally on a Windows 7 machine that has been down-versioned to windows XP (and hiccups in many other areas). So far it does not happen on a Mac book pro bootcamp XP install, or a stable Vista install.

I guess it's a genuine thread crash in the Air app, most likely some sort of memory issue?

I have not been able to find a way to prevent the problem from happening. Instead I have a work around:

Each swf contains a MC on the timeline on frame 1 with a line of code with the sole purpose of notifying the application (via the sandbox bridge) that level-1 code has been loaded correctly. If this notification is not received for a particular swf by the time the 'COMPLETE' event fires, the swf is deemed to have loaded incorrectly, and is unloaded from memory and loaded again.

This process is repeated until the load is correct. The files are local so the delay to the user is minimal.

Tuesday, 13 July 2010

Using actionscript 3 to inject code into the timeline

Two undocumented handy features from AS3 for working with the MovieClip timeline:

frame1();

Which runs the code on frame 1 of the timeline, with corresponding functions for any frame that contains code - frame99(), frame354() etc.

addFrameScript(frameNumber:int, newFrameActions:Function):void

Which adds the code specified in newFrameActions at the frameNumber. Note that this is a zero-based frame number, yet frame1() is one-based. To add code to the first frame of an MC and then call it, you would do:

mc.addFrameScript(0, firstFrameActions);
mc.frame1();

addFrameScript will overwrite any existing actions, so if you want to augment and not just replace the code that is there, you'll need to first grab the existing frame code and then include that in your new function like this:
var oldFrameActions:Function = function():void {};

if((mc['frame23'] != null) && (mc['frame23'] is Function))
{
oldFrameActions = mc['frame23'];
}

var newFrameActionsFrame23:Function = function():void {
oldFrameActions();
// add new code here...
}
Of course all of that is much better wrapped up in some portability that doesn't care whether it's frame 23 or frame 99 etc. I'm using a class for that - http://gist.github.com/474159

You feed it a MovieClip and you'd probably want to hold a reference inside that MovieClip as well because one of the other uses is in overriding gotoAndPlay() so that it will rerun the code in the current frame without having to move the playhead (assuming frameScriptManager is a reference to an instance of the class in the gist):

override public function gotoAndPlay(frame:Object, scene:String = null):void
{
var frameNo:int = this.currentFrame;

super.gotoAndPlay(frame, scene);

if(this.currentFrame == frameNo)
{
var frameFunction:Function = frameScriptManager.getFrameScriptAtFrame(frameNo);
try {
frameFunction();
}
catch(e:Error)
{
trace(e.getStackTrace());
}

}

}

The class, and in particular the getFrameScriptAtFrame() function also deals with the major gotcha: even once you've added additional code to the timeline at frame 1, the frame1() function continues to only run the actions that were present in the MovieClip (or Fla) timeline when it was published.

An alternative to rerun the current frame actions is to call Stage.invalidate() before the gotoAndPlay() - which is OK unless your content, like ours, is loaded into a sandbox that doesn't have access to Stage.

--- ADDENDUM ---

I have now also discovered that the visibility of the frame1() function is internal - so unless the FrameScriptManager is compiled into the same package as the SWF / MovieClip base class, these will show up as undefined (annoyingly no more helpful error is thrown).

So - the workaround is that your MC to be injected should implement the following function:
    public function getFrameScriptAt(frameNumber:int):Function
{
return this['frame'+frameNumber];
}

This is also specified in the IInjectableTimeline interface I've added to the Gist file. However, the FrameScriptManager class itself doesn't require an IInjectableTimeline, but will accept a vanilla MovieClip because the complications of ApplicationDomains for content loaded at runtime, plus variation in where the FrameScriptManager is complied (in the parent swf or in the child swf), make it tricky to roll a one-size-fits-all solution with strong typing.

So - the interface is there to help you keep yourself honest. Unfortunately there's no compiler enforcement in this case.

--------


What's the point of all this? Well, in our case the swfs being targeted for timeline code injection are animated lessons. These lessons also need some code in them to address the application they run in - so that they can ask the main message window to show a title, or offer a download, or some instructions, or set the status of the play / pause button at the end of a section. Previously we did this using frame code, but this was hard to test and maintain. Instead we now specify most titles, instructions, download paths etc in an external xml file which is quick to edit.

At runtime the xml file for each lesson is processed and the relevant actions are added to the timeline of the lesson swfs, meaning that as far as the application is concerned these new, easier to manage (for us and our client) lessons behave exactly like the old ones.


Friday, 26 February 2010

Programming for the slowest processor

Last week, er... no, actually it was this week, I had a bang on the head. Black out, concussion, skull x-rays, the works.

Everything is intact, but my short term memory is temporarily a bit rubbish. My concentration span is minutes at most.

But it has brought into focus something I've been working on for a while - changing my processes to really concentrate on minimising the load on the slowest processor: my brain.

The AS3 application I'm currently building is not a fast-firing game or a web-delivered viral. File size and the speed of event firing aren't a critical success factor, so I'm taking the opportunity to experiment with how I programme, to make it easier for me to do my job as well as I can.

Over the next couple of weeks When I get a chance I'll be posting a series of entries on:

1. Using the [robotlegs] framework to cut cognitive boilerplate (it's about thinking, not typing!)

2. Reducing cognitive overhead through ultra-verbose programming.

3. Getting the compiler to pull its weight - including AS3 signals.

4. Using end-to-end testing and the roboteyes tools to get more sleep.

5. Employing project sprouts to make true TDD (unit, integration and end-to-end testing) easier than falling off a log.



The emphasis is on making sure that I can do my job well, and enjoy doing it, even on days when I am much less than my best. Not because I foresee a lot of head injuries in my future, but because as a team leader, business owner, parent, partner and dog owner, there turn out to be a lot of days when I'm interrupted.

I could pretend that all of this is in the interests of my clients. Sure, they benefit. But this new way of working is about the advantages to me today, and the future developers on my project (often also me) - whether that future is 10 minutes, 10 days or 10 months away.

Part 1 coming this weekend.

Tuesday, 20 October 2009

robotLegs meets as3-signals?

The flash event model is a bit... sucky.

Robert Penner wrote these 3 blog posts which summarise the limitations:

He's been working on an alternative known as as3-signals, which you'll find on git-hub.

Essentially signals seem to provide the following enhancements: (correct me if I'm wrong here)
  1. A type based rather than string based registration for an event.
  2. Compile time checking that you only register for relevant signals. If SomeClass doesn't have a .clicked signal then you won't be able to listen for it.
  3. Centralised and handy functions for removing all listeners from an event.
  4. The ability to add a listener for one time only (and have it automatically removed).
  5. Useful runtime errors (if you use 'addOnce' after you've already used 'add' or the reverse you get a sensible error).
  6. The option to prioritise listeners - enforcing a calling order.
I'm currently working on this modularised rebuild of an existing piece of complex software. Inter-module communication is the thing I'm thinking about most.

I've also been checking out the interesting stuff over at robotlegs - a DI driven framework which looks light and really focussed... except that Flash CS4 won't let you use the metadata required, so it seems to be Flex only for now.

However, it has a nice command and event mapping model - allowing for type checking, 'one shot only' and so on.

So... I'm considering the benefits of a hybrid. Of creating a command mapped version of as3-signals.

Questions:
1) How to replicate the nice compile time checking that Signals provides while also supporting the decoupling of the modules via the centralised command map - so you can't register for an event that will never exist.

2) Where to put the events? Which package? Do they need to reside in a centralised area?

3) Is this a complete waste of time? Does the command map already provide sufficient decoupling? Should I simply use signals within a module and the command map between modules?

4) Could a module create a new signal 'channel' dynamically, or does that make no sense at all?

Questions... questions...

Wednesday, 16 September 2009

A framework for Modular AIR Applications

I've finished building a framework for securely loading signed, packaged swfs into the application sandbox at runtime, while leaving others in the non-application sandbox and rejecting modules without matched signatures.


The framework fairly neatly packages the whole process.

The origin of the signature testing process is the Adobe article here, where you can also download a compiled code swc of the necessary flex-only classes if you're working in Flash.

The air application I've built as proof-of-principle achieves the following:
  • Modules with matching signatures are loaded to the app sandbox (if requested).
  • Modules without matching signatures requesting app sandbox loading are rejected.
  • Modules can also be loaded to the non-application sandbox.

It demonstrates:
  • That app sandbox loaded modules can write to the file system (they put a directory on your desktop).
  • That non-app sandbox loaded modules are prevented from writing to the file system.
  • That both app and non-app sandbox loaded modules can pass library assets to the main application to be added to the display list.

It has a lot of user feedback / developer feedback built in. In the absence of the ability to trace in the air application itself I'm listening for this feedback and displaying it in a textarea in the main application.

Currently the information about what to load and how to load it, and where to find the .zip assets on a server to install modules, is all contained in moduleData.xml inside the applicationFlasAndCode folder. When you start the application you must browse to this file.

To use the framework:
  1. Use ModuleXMLLoader to load an xml document containing your module information.
  2. This creates a strongly typed iterator: ModuleDescriptionIterator.
  3. Instantiate ModuleChainLoader, passing it that iterator.
  4. Listen for the ModuleEvent.MODULE_LOADING_COMPLETE event.
  5. Ask the moduleChainLoader to startLoadingModules().
  6. When you handle the loading complete event, run getModuleDictionary().
  7. Access your modules from the ModuleDictionary using getModule() and getSandboxedModule(). You pass the module's unique name to those functions. They pass back the required module.

Any questions at all, post them here or email me.


How secure is secure?

I'm building an enterprise training application. It already exists in AIR, but we're moving to a modular system because our users don't have the permissions required to run the automatic updates.

Secure, for me, is secure enough not to be the preferred target for a malicious attack. Nothing is impossible, but I want to make it sufficiently difficult / tedious that anyone intending to cause trouble looks elsewhere to do it.


Notes:

In my own final application the xml will be a secure data stream coming from a server.

If you want to keep my xml structure you can use the ModuleXMLLoader class as is, but I've kept it as a separate stage in the process so that you can make changes to this.


Some possible gotchas to avoid:
  • The example module flas have classes which extend ITestableModule. You'll need to point flash to the com folder in order for this interface to be found.
  • Remember that flash keeps signing with the last certificate you used. So when creating good and bad modules, and compiling the app itself, keep an eye on which certificate you're using.
  • Module .air packages need to be renamed .zip
  • You cannot test secure loading in the flash test player. You have to create the application .air file, install it and run it to test it.
  • Don't forget to grab that SignatureUtils.swc from the Adobe link.

Tuesday, 1 September 2009

Creating modular applications in Adobe AIR

Identity is on hold at the moment, work still being too hectic. I'm hoping to really get down to it next year - I've got 3 O'Reilly books on the way out by March next year, and they're eating all my time.

In the meantime, the main work project I've got going on is a nice fit with what Identity needs to achieve in terms of modularity. It's an e-learning app, already running successfully as an Air application, but we're rebuilding it to achieve the following:

1) Dynamically built modular environment. Other than the AIR shell itself, all the functionality will be loaded by the app at runtime. This allows for non-admin updates, and for user A and user B to have different logging systems / quiz engine etc.

2) Reskinned at runtime. Different departments and sub companies of the corporate client can have a different look and feel, maybe even a different screen size requirement.

I've already grappled with the Air security sandbox stuff within this app. The lessons themselves are dynamically loaded and using sandbox bridge for communication. The current quiz apps are also decoupled, and only simple data is passing around - mostly I'm using strings of xml for more structured data requirements.

I've been playing with the two variations of loading content into flash:

A) The light side: use a normal loader, put your downloaded content into a sandbox where it can only access other content via the safe and secure bridge.

B) The dark side: read the file into a ByteArray and use loadBytes() with allowLoadBytesCodeExecution = true on the loader context, to give your loaded content the run of the place... including the fileSystem.

The pros and cons of breaking the Air security model

The light side:

Events
Events must be passed using loaderInfo.sharedEvents. Only built-in events keep their type, but that's ok. The actual Event.type property is just a plain string anyway, so you can still make use of a custom event for compile time checking to avoid typos, as the Air shell doesn't care that it didn't know what MyCustomEvent.DOG_BARKED was - it just sees "dogBarked". On the down side you can't pass useful extra data with your custom events.

Security
You can use sensible functions like storeAsset(assetPath:String) to pull in things and read / write to the file system, rather than opening up your user's computer for an attack by rogue dynamically loaded modules.

Loading grandchildren
Loading a grandchild asset such as a png or jpg seems to be possible using loadBytes() with code execution set to false. You have to pass the byteArray as a normal array, reconstruct it to be a byteArray, and then load the content in the child module. I've only part tested this. More to follow. And it's pretty gross as a process.

Decoupling logic and events is ok
Modularising the logic of the application doesn't actually seem to be too hard. The code was pretty nicely decoupled already, and we think we can reduce inter-module communication to a combination of vanilla events and xml (passed as string). We'd use the Air shell as a postman to deliver messages from one module to another. There would be an unnerving amount of dynamic stuff which isn't checkable at compile time, but it's doable.

Major hurdle: providing skin assets at runtime
I simply can't find any way to load the actual graphics for the modules at runtime. All I want is to load a specific (dynamically selected) swf with a bunch of MCs in the library, and to be able to do stuff like "new LoginButtonUpSkin()" and whack the requested graphic into the right place. I guess we could export all of our graphics as pngs and use the loadBytes() trick to feed them to the individual modules, but how grim is that?

The other option would be to compile variations on the modules themselves, with different look and feel. "MainMenuGreen.swf" "MainMenuBlue.swf". That feels like a huge pain in the ass when it comes to creating new skins. The designers would need access to the code library, they always ring me up wondering how to link their files to the com folder... you can see the potential headaches I'm sure...

The dark side:

Events: Module to module event traffic is possible. Events don't lose their typing when passing from module to module.

Security: This is dangerous stuff. But all my modules are coming from a trusted server and there's a technique for verifying signatures. Basically any module that wants to be loaded into the application sandbox using the loadBytes() method would have to be an Air app in itself. Then that air package gets loaded and ripped open, the signature verified and the module loaded or rejected. I've no doubt that it's not failsafe.

Will adobe continue to provide this work-around?
The adobe official documentation on working securely with untrusted content end with the following fear-inducing statement:

Note: In a future release of Adobe AIR, this API may change. When that occurs, you may need to recompile content that uses theallowLoadBytesCodeExecution property of the LoaderContext class.

A future release WHEN? What? And will I need to recompile content even if it's happily running. Will Air 2.0 break my 1.5 application if I've used this? Presumably Adobe would provide a better work around for achieving this, but my client doesn't want to do a thousand admin-requiring reinstalls... which is why we're going to use a module based system in the first place...

At the moment I'm still testing, still lurching from favouring one side to the other. More to follow as it unfolds. Hopefully I can save someone the three days of googling and testing I've just been through.