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.

Выкл, чтобы написать несколько модульных тестов и выяснить, почему этот парсер продолжает ломаться.