Я немного писал уже о важности написания тестов, правда для php программистов, но написанное в равной степени касается любого программирования, если в разработку вовлечены более одного человека, релизы довольно часты и логика приложения не может быть протестирована вручную очень быстро. В таком случае, мы рано или поздно захотим автоматизировать процесс, собственно об этом и пойдет речь. Тестировать приложения написанные на php мы научились, хотя конечно постиигать эту науку можно всю жизнь, но те задачи которые перед нами стояли мы с успехом решаем, благодаря UnitTest’ам, а вот с автоматизированным тестированием Flex/Flash, пока не складывалось. Появилось немного времени и было решено взглянуть на FlexUnit, и вот что из этого вышло.
Поскольку на flash/flex мы чаще всего делаем интерфейсы, мне в первую очередь было интересно как тестировать асинхронные вызовы, элементы UI, и проч. Я написал небольшой класс, представляющий из себя сильно упрощенную версию конвеера Жени Потапенко. Вот код двух классов:
// Task.as
package com.sheremetov.manager {
public class Task {
private var _func: Function;
private var _time: int;
private var _params: Object;
public function Task(func: Function, time: int, params: Object = null) {
_func = func;
_time = time;
_params = params;
}
public function get caller(): Object {
return caller;
}
public function get params(): Object {
return _params;
}
public function get time(): int {
return _time;
}
public function get func(): Function {
return _func;
}
}
}
// Conveyor.as
package com.sheremetov.manager
{
import flash.events.TimerEvent;
import flash.utils.Timer;
public class Conveyor {
private var _queue: Array;
private var _timer: Timer;
private var _paused: Boolean = false;
public function Conveyor() {
_queue = new Array();
_timer = new Timer(1,1);
_timer.addEventListener(TimerEvent.TIMER, onTaskFinished);
}
public function add(task:Task): void {
_queue.push(task);
}
public function play(): void {
runCurrent();
}
public function stop(): void {
_timer.stop();
}
private function runCurrent(): void {
if(_queue.length > 0) {
var task: Task = _queue.shift();
if(task.params != null) {
task.func(task.params);
} else {
task.func(null);
}
_timer.delay = task.time;
_timer.start();
}
}
private function onTaskFinished(event: TimerEvent): void {
runCurrent();
}
}
}
Первое с чего надо начать для тестирования этого кода это TestSuite и TestRunner, который будет его запускать:
// ConveyorTestSuite.as
package com.sheremetov.manager.tests {
[Suite]
[RunWith("org.flexunit.runners.Suite")]
public class ConveyorTestSuite {
public var conveyorTest: ConveyorTest;
public var taskTest: TaskTest;
public function ConveyorTestSuite() {
}
}
}
Runner.mxml
< ?xml version="1.0" encoding="utf-8"?>
< ![CDATA[
import com.sheremetov.manager.tests.ConveyorTestSuite;
import mx.events.FlexEvent;
import org.flexunit.listeners.UIListener;
import org.flexunit.runner.FlexUnitCore;
private var flexUnitCore:FlexUnitCore;
protected function creationCompleteHandler(event:FlexEvent):void {
flexUnitCore = new FlexUnitCore();
flexUnitCore.addListener( new UIListener( testRunner ));
flexUnitCore.run( ConveyorTestSuite );
}
]]>
< /mx:Application>
Обратите внимание на теги метаданных, которыми могут конфигурироваться классы TestSuite и TestCase. Вот полный список, поддерживаемых метатегов:
- [Suite] – указывает на класс Suite
- [Test] – указывает на метод теста, заменяет префикс test для метода. Поддерживает аттрибуты expected, async, order, timeout, ui
- [RunWith] – указывает на Runner с которым должен быть запущен TestSuite
- [Ignore] – Вместо комментирования метода теста достаточно указать этот метатег
- [Before] – заменяет setup() во FlexUnit и позволяет использовать несколько методов. Поддерживает аттрибуты: async, timeout, order, ui.
- [After] – заменяет teardown(), поддерживает: async, timeout, order and ui аттрибуты.
- [BeforeClass] – позволяет определить метод, вызываемый перед запуском класса теста, поддерживает аттрибут order
- [AfterClass] – помечает метод выполняемый, после запуска всех тестов в классе, поддерживает аттрибут order
Тест класса Task представляет из себя классический unit-test:
package com.sheremetov.manager.tests {
import com.sheremetov.manager.Task;
import flexunit.framework.TestCase;
public class TaskTest extends TestCase {
public function TaskTest(methodName:String=null) {
super(methodName);
}
public function testNew(): void {
var param:Object = {test:"passed"};
var func:Function = testNew;
var task: Task = new Task(func, 100, param);
assertTrue(task.func == func);
assertEquals(task.time, 100);
assertEquals(task.params, param);
task = new Task(func, 100);
assertNull(task.params);
assertEquals(task.caller, this);
}
}
}
А вот класс для тестирования асинхронных вызовов выглядит интереснее:
package com.sheremetov.manager.tests {
import com.sheremetov.manager.Conveyor;
import com.sheremetov.manager.Task;
import flash.events.Event;
import flash.events.TimerEvent;
import flash.utils.Timer;
import org.flexunit.asserts.assertEquals;
import org.flexunit.async.Async;
public class ConveyorTest {
private var conveyor: Conveyor;
private var counter: int;
public function ConveyorTest() {
}
[Before]
public function runBeforeEveryTest():void {
conveyor = new Conveyor();
counter = 0;
}
[After]
public function runAfterEveryTest():void {
conveyor = null;
}
private function dummy(param: Object): void {
counter++;
}
[Test(async,timeout="250")]
public function testPlay():void {
assertEquals(counter, 0);
conveyor.add(new Task(dummy, 100, "one"));
var timer1: Timer = new Timer(50, 1);
timer1.addEventListener(TimerEvent.TIMER, Async.asyncHandler( this, onAfterFirst, 120));
timer1.start();
conveyor.add(new Task(dummy, 100, "two"));
var timer2: Timer = new Timer(150, 1);
timer2.addEventListener(TimerEvent.TIMER, Async.asyncHandler( this, onAfterSecond, 220 ));
timer2.start();
conveyor.play();
}
private function onAfterSecond(event: Event, params: Object): void {
assertEquals(counter, 2);
}
private function onAfterFirst(event: Event, params: Object): void {
assertEquals(counter, 1);
}
}
}
В этом классе вызовы типа Async.asyncHandler( this, onAfterFirst, 120) возвращают callback, этот вызов в случае если вызов не произойдет в течении 120 миллисекунд, сработает таймаут, и тест будет считаться проваленным.
Надеюсь код достаточно красноречив и понятен. А для тех кто захочет глубже разобраться с фреймворком, я рекомендую прочитать очень хорошую статью о использовании FlexUnit, неисчерпаемым источником информации являются 220 мегабайт кода репозитория проекта, вы легко можете экспотрировать его скачав svn, и сделав:
svn checkout http://opensource.adobe.com/svn/opensource/flexunit [путь-куда-вы-хотите-экспортировать]