LE Blog

Инженер с поэтической душой

28.05.2009 firtree_right Тестирование в ActionScript с помощью AsUnit

Введение

Сейчас занимаюсь тестированием и отладкой достаточно большого проекта на флексе. При каждом обновлении продукт проходит длительный и подробный этап тестирования. Как нельзя более актуальной становится проблема «исправив одну ошибку не сделать новых».

Один из инструментов, который мог бы мне помочь, если бы я его использовал сразу — это тестирование, а точнее Unit Tests (изолированное тестирование).

Понятно, что ActionScript не самый удобный язык для изолированного тестирования. Сложно сделать связанные объекты независимыми друг от друга, чтобы тестировать их по отдельности. Да и в целом продукты на Flash и Flex имеют большую интерфейсную составляющую. И тестировать зачастую нужно впечатления пользователя. Но тем не менее, покрытие тестами, пусть и небольшое, — это удобно.

Задача

Протестировать объект, который получает данные из xml и выдает их в качестве собственных параметров. Попробуем сделать это в лучших традициях TDD

Ресурсы

Нам понадобится AsUnit — прекрасная библиотека с открытыми исходниками для изолированного тестирования (unit testing).

Так же используем лучшие традиции TDD:

  1. Никакого кода, пока нет провалившегося теста.
  2. Тест пишется до тех пор, пока он не начнет проваливаться.
  3. Кода нужно писать ровно столько, чтобы проваливающийся тест прошёл.

Для решения будем использовать 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