Использование FlexUnit для тестирования асинхронных вызовов
Я немного писал уже о важности написания тестов, правда для 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"?> <mx :Application xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:flexunit="flexunit.flexui.*" xmlns:flexUnitUIRunner="http://www.adobe.com/2009/flexUnitUIRunner" creationComplete="creationCompleteHandler(event)" > </mx><mx :Script> < ![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> <flexunituirunner :TestRunnerBase id="testRunner" width="100%" height="100%" /> < /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 [путь-куда-вы-хотите-экспортировать]
О статье
Вы сейчас читаете статью «Использование FlexUnit для тестирования асинхронных вызовов»
- Написанную:
- 05.02.2010
- Категории:
- flash, flex, programming
- Теги:
- action script, flexunit, тестирование
Нет комментариев
Перейти к форме добавления комментария | comments rss[?] | trackback uri [?]