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

Site Footer

Sliding Sidebar

About Me

About Me

For whom this blog for?

For those who are interested in modern Internet technologies, IT business, startups, management, quality control, personal effectiveness, motivation. Here I write about what is interesting, about problems I faced and solutions I found. I hope it will be interesting to you either.

What motivates me to write?

The desire to improve, to study deeper topics that interest me. Find people with similar problems and tasks, together look for ways out and solutions.

Feel free to contact if you have anything to say to me

Old Flash site with my artistic works and misuc.