Статьи

Erlang: сборка и тестирование


Мы знаем, как использовать скаляры, атомы, кортежи и списки;
мы знаем, как определять функции посредством рудиментарного сопоставления с образцом. Теперь пришло время принять более строгий подход и разработать некоторые возможности тестирования, прежде чем продолжить.

Разработка через тестирование

TDD не является специфичной для языка техникой, хотя некоторые стратегии (вне разработки, ориентированные на имитации) более применимы к объектно-ориентированному программированию, чем к функциональному языку. Тем не менее, функции пробного вождения — это то, чему обычно учат TDD, поэтому мы начнем изучать EUnit на этом примере.

Строительство

Мы можем добавить одношаговый процесс сборки в наш проект Erlang, способный скомпилировать наши исходные файлы и запустить тесты.

Давайте создадим Emakefile (эквивалент Makefile в проектах C / C ++):

{['src/*'], [{outdir, "bin/"}]}.

Кортеж, определенный в Emakefile, состоит из двух аргументов.

Первый аргумент — это список шаблонов для поиска файлов для компиляции. В этом случае мы можем поместить все, что мы хотим скомпилировать (не скрипты для escript) в src /.

Второй аргумент — это список кортежей, каждый из которых представляет опцию. Каждый параметр начинается с атома, определяющего, что это такое, и добавляет столько значений, сколько требуется для этого параметра; в этом случае мы говорим реализации реализации Erlang, чтобы поместить скомпилированные файлы .beam.

Если мы сейчас запустим:

mkdir bin/
erl -make

у нас будет рудиментарный процесс сборки.

Вы также можете добавить:

bin/*.beam

к вашему .gitignore или svn: игнорируйте свойства, чтобы избежать контроля версий скомпилированных файлов.

тесты

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

-module(numbers_test_03).
-include_lib("eunit/include/eunit.hrl").

simple_test() ->
    ?_assert(1 == 1).

Это самый простой тест, который мы можем написать: мы определяем модуль (который может быть модулем производственного кода, а не просто содержащий тесты) и включаем EUnit. EUnit импортирует серию макросов? _Assert (), в которые мы можем передать выражения, возвращающие логические значения; он также автоматически экспортирует и запускает любую функцию, имя которой заканчивается на ‘test ()’.

Теперь мы готовы запустить наш первый тест:

erl -make
cd bin
erl -noshell -eval 'eunit:test([{dir, "."}], [verbose])' -s init stop

erl -make компилирует файлы, определенные Emakefile; после этого мы переходим в мусорное ведро /. Затем мы запускаем erl с конкретными параметрами:

  • noshell избегает взаимодействия пользователя с системой, что позволяет нам передавать вывод или фильтровать его с помощью других команд.
  • eval заставляет оболочку вычислять выражение, которое является экспортированной функцией EUnit.
  • s заставляет оболочку вызывать init: stop () после завершения оценки. Без этого оболочка останется открытой после завершения тестов, ожидая ввода.

Если вы поместите эти три команды в файл build.sh, теперь вы будете готовы запустить наш пакет одним нажатием кнопки.

Утверждения

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

Вот некоторые примеры:

simple_test() ->
    ?assert(1 == 1),
    ?assertNot(1 == 0),
    ?assertMatch({number, _Var}, {number, 42}),
    ?assertEqual(1, 1),
    ?assertError(undef, lists:append()).

В первой паре методов мы просто утверждаем логическое значение.

С помощью? AssertMatch () мы пытаемся сопоставить второй аргумент с первым, который должен быть шаблоном, подобным тому, который вы используете при определении функций. ? assertEqual () проще написать, чем явные сравнения.

Finally, ?assertError verifies that an error is raised while calling the expression passed as a second argument. In this case, lists:append/0 is undefined, while lists:append/2 wouldn’t be.

Note that assertions in EUnit are not really functions but macros, so they will be substituted with different code during compilation. This means that it is possible for EUnit to show you the expression that generated a boolean (basically everything that you write inside ?assert() variations):

numbers_test_03: simple_test (module 'numbers_test_03')...*failed*
::error:{assertion_failed,[{module,numbers_test_03},
                         {line,5},
                         {expression,"1 == 0"},
                         {expected,true},
                         {value,false}]}

A failing test looks like this:

======================== EUnit ========================
directory "."
  numbers_test_03: simple_test (module 'numbers_test_03')...*failed*
::error:{assertEqual_failed,[{module,numbers_test_03},
                           {line,8},
                           {expression,"0"},
                           {expected,1},
                           {value,0}]}
  in function numbers_test_03:'-simple_test/0-fun-3-'/1


  [done in 0.006 s]
=======================================================
  Failed: 1.  Skipped: 0.  Passed: 0.

While a passing one shows no output other than the green bar (which is not colored here).

======================== EUnit ========================
directory "."
  numbers_test_03: simple_test (module 'numbers_test_03')...ok
  [done in 0.006 s]
=======================================================
  Test passed.

 

Conclusions

I hope I have shown you enough to get from some .erl files to a running test suite, all with the push of a button. If you need a big picture or some code to start from, take a look at the Github repository for this series.