Использование 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"?>


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

Comments

comments


Bookmark and Share