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

Leave a reply:

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Site Footer

Sliding Sidebar

About Me

About Me

Для кого этот блог?

Для тех кого интересуют современные интернет технологи, IT бизнес, стартапы, менеджмент, контроль качества, личная эффективность, мотивация. Здесь я буду писать о том, что в первую очередь будет интересно мне, о проблемах и решениях. О том что пригодилось мне, и возможно будет интересно Вам.

Что заставило меня создать его?

Желание совершенствоваться. Достигать успеха. Находить людей со схожими проблемами и задачами, вместе искать выходы и решения.

Немного о себе.

Мой первый серьезный опыт в IT это работа над desktop приложениями в компании «Эксперт-Софт». У истоков её стояли несколько амбициозных и талантливых молодых людей, с огнем в глазах и желанием работать «как майкрософт». То чем мы там занимались вполне могли бы сегодня назвать «стартапом». Рук было откровенно мало, поэтому приходилось заниматься всем: кодированием на Delphi, написанием скриптов на VBA, дизайном, вёрсткой и поддержкой вебсайта, работой над рекламной полиграфией, проектированием интерфейсов и БД. Работы было много, но запал был велик, команда очень разношерстная, гармонично дополняя друг-друга в решении нетривиальных задач. Благодаря тому что пришлось попробовать многое, постепенно вырисовалось понимание того чем хочется заниматься, и как. Софтверным программированием я был сыт по горло. Массы проблем десктопного софта в вебе просто не было, по определению. Зато был четкий фокус на дизайне, юзабилити, скорости. Поэтому когда пришла пора уходить из «Эксперт-Софт», я без всякого сожаления стал искать работу как разработчик для web. Поскольку городишко у нас не очень большой, выбор был практически предопределен. Так я стал работать в «Оникс-Системз», где и продолжаю работать поныне. За время работы в компании я как разработчик принимал участие в работе над несколькими десятками проектов. Несколько десятков проектов было сделано мною как фрилансером. Самым большим проектом в котором я сыграл роль менеджера, считаю свою семью. Также довольно большой проект мы сейчас поднимаем с командой разработчиков (на данный момент команда состоит из четырех php разработчиков, одного flex кодера и тестировщика). Отсюда, большой интерес к современным практикам и методологиям, разным подходам в управлении командой, повышению эффективности и качества работы. По мере сил, вдохновения и свободного времени, я буду писать об этом.

Если у Вас возникли какие-то вопросы ко мне лично, буду рад если Вы свяжетесь со мной:

e-mail:
skype: denis.sheremetov
Старый сайт, с музычкой и флешом

Прочая онлайновая деятельность: