28.05.2009Тестирование в ActionScript с помощью AsUnit
Введение
Сейчас занимаюсь тестированием и отладкой достаточно большого проекта на флексе. При каждом обновлении продукт проходит длительный и подробный этап тестирования. Как нельзя более актуальной становится проблема «исправив одну ошибку не сделать новых».
Один из инструментов, который мог бы мне помочь, если бы я его использовал сразу — это тестирование, а точнее Unit Tests (изолированное тестирование).
Понятно, что ActionScript не самый удобный язык для изолированного тестирования. Сложно сделать связанные объекты независимыми друг от друга, чтобы тестировать их по отдельности. Да и в целом продукты на Flash и Flex имеют большую интерфейсную составляющую. И тестировать зачастую нужно впечатления пользователя. Но тем не менее, покрытие тестами, пусть и небольшое, — это удобно.
Задача
Протестировать объект, который получает данные из xml и выдает их в качестве собственных параметров. Попробуем сделать это в лучших традициях TDD
Ресурсы
Нам понадобится AsUnit — прекрасная библиотека с открытыми исходниками для изолированного тестирования (unit testing).
Так же используем лучшие традиции TDD:
- Никакого кода, пока нет провалившегося теста.
- Тест пишется до тех пор, пока он не начнет проваливаться.
- Кода нужно писать ровно столько, чтобы проваливающийся тест прошёл.
Для решения будем использовать Adobe Flash CS3 в качестве редактора.
Решение
Итак, скачиваем AsUnit с сайта по ссылке выше. Создаем среду для тестирования. ConfigTest.as:
package {
import asunit.framework.TestCase;
public class ConfigTest extends TestCase {
private var instance:Config;
/**
* Запускается перед каждый тестом
*/
protected override function setUp():void {
instance = new Config();
}
/**
* Запускается после каждого теста
*/
protected override function tearDown():void {
instance = null;
}
/**
* Тест-проверка, что созданный объект нужного класса
*/
public function testIsConfig():void {
assertTrue("Example is Config", instance is Config);
}
}
}
Это набор тестов для нашего будущего класса, который будет называться Config. Теперь, если бы у нас было несколько классов и несколько наборов тестов, их нужно было бы собрать воедино. AllTests.as:
package {
import asunit.framework.TestSuite;
public class AllTests extends TestSuite {
public function AllTests() {
super();
addTest(new ConfigTest());
}
}
}
Теперь нам нужен, собственно, тестировщик. AsUnitRunner.as:
package {
import asunit.textui.TestRunner;
public class AsUnitRunner extends TestRunner {
public function AsUnitRunner() {
start(AllTests);
}
}
}
Теперь создаем AsUnitRunner.fla, в настройках File->Publish Settings->Flash->Settings прописываем в Document class базовый класс AsUnitRunner и добавляем путь к asunnit/as3/src в Classpath.
Попробуем запустить (ctrl+enter) — неудача! :) Можно сказать, провалившийся тест. Чтобы тест прошёл достаточно создать описание класса. Config.as:
package {
public class Config {
}
}
И теперь когда мы запускаем наш тестировщик мы видим:
AsUnit 3.0 by Luke Bayes and Ali Mills
Flash Player version: WIN 9,0,115,0
.
Time: 0.024
OK (1 test)
Time Summary:
23ms : ConfigTest
Все тесты проходят. Пора закончить писать код и снова перейти к написанию тестов. Добавим тестирование желаемого функционала. Я хочу загружать в Config xml и получать значения из него в виде заданных параметров. В описание ConfigTest.as добавим метод:
/**
* Тест-проверка, что из xml получаются параметры x и y
*/
public function testParsesCoordinates():void {
var xml:XML = <root>
<point>
<x>10</x>
<y>20</y>
</point>
</root>
instance.fromXML(xml);
assertEquals("X property should equal 10", 10, instance.x);
assertEquals("Y property should equal 20", 20, instance.y);
}
Попробуем запустить тестировщик — не компилируется, говоря, что у класса отсутствуют методы. Создаем описания методов в Config.as. Наша задача исправить только те ошибки, о которых нам сообщили. Теперь он выглядит так:
package {
public class Config {
public function fromXML(xml:XML):void {
}
public function get x():int {
return 0;
}
public function get y():int {
return 0;
}
}
}
Результат тестирования теперь выглядит так:
AsUnit 3.0 by Luke Bayes and Ali Mills
Flash Player version: WIN 9,0,115,0
..F
Time: 0.037
There was 1 failure:
0) ConfigTest.testParsesCoordinates
AssertionFailedError: X property should equal 10 expected:<10> but was:<0>
at asunit.framework::Assert$/fail()
at asunit.framework::Assert$/failNotEquals()
at asunit.framework::Assert$/assertEquals()
at ConfigTest/testParsesCoordinates()
at asunit.framework::TestCase/runMethod()
at asunit.framework::TestCase/runBare()
at Function/http://adobe.com/AS3/2006/builtin::apply()
at <anonymous>()
at SetIntervalTimer/onTimer()
at flash.utils::Timer/_timerDispatch()
at flash.utils::Timer/tick()
FAILURES!!!
Tests run: 2, Failures: 1, Errors: 0
Time Summary:
36ms : ConfigTest
Заметьте, что после первой ошибки тестирование прекращается. Теперь мы можем написать код, чтобы пройти этот тест. Наш класс выглядит теперь так:
package {
public class Config {
private var _x:int;
private var _y:int;
public function fromXML(xml:XML):void {
_x = int(xml.point.x);
_y = int(xml.point.y);
}
public function get x():int {
return _x;
}
public function get y():int {
return _y;
}
}
}
А результат теста так:
AsUnit 3.0 by Luke Bayes and Ali Mills
Flash Player version: WIN 9,0,115,0
..
Time: 0.037
OK (2 tests)
Time Summary:
36ms : ConfigTest
Ураа!! Можно перестать писать код и написать ещё тесты! :)
Выводы
Понятно, что с непривычки это выглядит дико. Вместо одной программы нужно писать две. И вроде бы кажется, что я же знаю, как дальше писать, я четко вижу функционал. И в простых проектах это действительно так. Но...
В более сложных системах, когда множество одних объектов опираются на функционал других, не так легко удержать в голове все связи и все последствия исправлений. Возможно, именно то, что я только что исправил, использовалось другим объектом в том виде, в каком оно было.
Так же покрытие кода тестами обеспечивает более легкий вход изменений. Чисто даже психологически проще начать что-то менять.
Естественно, изолированное тестирование не оставляет без работы тестеров-людей. Но позволяет им сосредоточиться на тестировании именно того, что нельзя протестировать автоматически.
Материалы для изучения
Прекрасное видео про tdd в руби и просто прекрасное видео Документация и примеры AsUnit