30 строк было бы красивее, но мне нравятся разделители пустых строк.
Итак, я работаю над веб-фреймворком на Lua (да, их много, но это обучающее упражнение). При написании своей библиотеки шаблонов строк я обнаружил некоторые проблемы, которые заставили меня пожалеть о модульных тестах. Я посмотрел на существующие среды тестирования Lua, но, поскольку это обучающее упражнение и у меня аллергия на зависимости, я подумал, что напишу свой собственный.
Оказывается, это займет всего ~ 1 час! Я вставлю приведенный ниже код и пройдусь по нему по разделам, а затем покажу вам, как я его использовал и что он распечатывает. Звучит неплохо? Прохладный.
Давайте разберемся с этим.
local function describe(name, descriptor) local errors = {} local successes = {}
Здесь мы определяем локальную функцию с именем describe
, которая принимает name
и descriptor
. name
- это просто строка, которая идентифицирует набор тестов, в моем случае что-то вроде "Parser"
. descriptor
- это функция, в которой конечный пользователь будет писать свои тесты! Вы увидите, как это используется позже.
function it(spec_line, spec) local status = xpcall(spec, function (err) table.insert( errors, string.format("\t%s\n\t\t%s\n", spec_line, err) ) end)
Затем мы определяем другую функцию внутри описания, называемую it
. it
скрыт от внешнего мира, что означает, что только вещи внутри описать могут его использовать. Мы собираемся использовать it
, чтобы я мог писать свои индивидуальные модульные тесты.
it
принимает spec_line
и spec
. spec_line
- это просто строка, которая идентифицирует модульный тест, я мог бы использовать что-то вроде "should return total new lines in a file"
в качестве spec_line. spec
- это функция, в которой будет написан наш модульный тест!
Давайте немного сконцентрируемся на следующей части. xpcall
- это функция, которая берет другую функцию и запускает ее в «защищенном режиме». Это означает, что ошибки, возникающие внутри функции, не уничтожают программу, а передаются обработчику сообщений. Вы видите анонимную функцию выше, второй параметр xpcall
? Это наш обработчик ошибок. Внутри этого мы фиксируем любые ошибки и table.insert
их в нашу errors
таблицу. Это означает, что мы можем зафиксировать множество ошибок за цикл и распечатать их все в конце, вместо того, чтобы отказываться от первого.
Я передаю xpcall
функцию spec
для выполнения, что означает, что любые ошибки, возникающие в модульных тестах, перехватываются и сохраняются в таблице errors
.
if status then table.insert(successes, string.format("\t%s\n", spec_line)) end end
xpcall
возвращает «статус», который указывает, была ли функция запущена без ошибок. Если он работает без ошибок, я предполагаю, что это был успешный запуск! Итак, мы table.insert
это в successes
таблицу. Потом мы сможем все это распечатать позже.
local status = xpcall(descriptor, function (err) table.insert(errors, err) end, it)
Итак, мы вышли из it
метода и снова используем xpcall
? Это сделано для того, чтобы мы могли отловить любые ошибки, возникающие при настройке нашего набора тестов в функции descriptor
. Сейчас мы просто добавим это к списку ошибок, но, возможно, лучше просто все вывести из строя, если descriptor
выйдет из строя.
Кстати, мы передаем функцию it
в дескриптор в конце. Это означает, что пользователь (I) может получить доступ к функции it
из первого аргумента, передаваемого моей функции descriptor
. Вы увидите это немного позже.
print(name) if #errors > 0 then print('Failures:') print(table.concat(errors)) end if #successes > 0 then print('Successes:') print(table.concat(successes)) end end return describe
Наконец, печатаем ошибки и успехи. Начнем с названия набора тестов (или descriptor
, как я его почему-то называл). Затем мы проверяем: «Есть ли ошибки?» - если да, то распечатайте! Затем мы делаем то же самое для успехов. В итоге это выглядит так:
Test function Failures: should print out this failure lspec/spec.lua:5: 1 should equal 2! Successes: should print out this success
Любой, кто знаком с фреймворками модульного тестирования (я, здесь конечные пользователи), должен понять, что это означает. «Тестовая функция» - это имя дескриптора, «Неудачи:» - это все, что пошло не так, и если они пошли не так, а «Успехи:» - это все, что пошло правильно!
Итак, вот как вы пишете тесты с этим:
local describe = require('lspec') describe('Test function', function (it) it('should print out this failure', function () assert(1 == 2, '1 should equal 2!') end) it('should print out this failure', function () assert(1 == 2, '1 should equal 2!') end) it('should print out this success', function () -- test that do nothing succeed end) end)
Ура, rpsec (вроде) в Lua.
Выкл, чтобы написать несколько модульных тестов и выяснить, почему этот парсер продолжает ломаться.