Использование 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 [путь-куда-вы-хотите-экспортировать]

Comments

comments


Bookmark and Share