Написание модульных тестов в моем компиляторе (который генерирует IL) - PullRequest
9 голосов
/ 05 марта 2012

Я пишу Tiger компилятор в C# и собираюсь перевести код Tiger в IL.

При реализации семантической проверки каждого узла в моем ASTЯ создал много модульных тестов для этого.Это довольно просто, потому что мой CheckSemantic метод выглядит следующим образом:

public override void CheckSemantics(Scope scope, IList<Error> errors) {
...
}

, поэтому, если я хочу написать какой-то модульный тест для семантической проверки какого-либо узла, все, что мне нужно сделать, это построитьАСТ, и вызвать этот метод.Тогда я могу сделать что-то вроде:

Assert.That(errors.Count == 0);

или

Assert.That(errors.Count == 1);
Assert.That(errors[0] is UnexpectedTypeError);
Assert.That(scope.ExistsType("some_declared_type"));

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

Я использую класс ILGenerator.Я думал о следующем:

  • Создайте код примера программы, которую я хочу протестировать
  • Сохраните этот исполняемый файл
  • Выполните этот файл и сохранитевывод в файл
  • Утверждение против этого файла

но мне интересно, есть ли лучший способ сделать это?

Ответы [ 3 ]

14 голосов
/ 05 марта 2012

Это именно то, что мы делаем в команде компилятора C # для тестирования нашего генератора IL.

Мы также запускаем сгенерированный исполняемый файл через ILDASM и проверяем, что IL генерируется должным образом, и запускаем его через PEVERIFY, чтобы гарантироватьчто мы генерируем проверяемый код.(За исключением, конечно, тех случаев, когда мы намеренно генерируем непроверяемый код.)

1 голос
/ 06 марта 2012

Я создал посткомпилятор в C # и использовал этот подход для проверки мутированного CIL:

  1. Сохраните сборку в временном файле , который будет удален после того, как я покончу с ним.
  2. Используйте PEVerify до проверьте сборку ; если есть проблема, я копирую ее в известное место для дальнейшего анализа ошибок.
  3. Проверка содержимого сборки. В моем случае я в основном загружаю сборку динамически в отдельном домене приложений (так что я могу разбить его позже) и использую класс там (так что это как самопроверка сборка: вот пример реализации ).

Я также дал несколько идей о том, как масштабировать интеграционные тесты в этом ответе .

0 голосов
/ 05 марта 2012

Вы можете думать о тестировании как о двух вещах:

  1. сообщая, изменился ли вывод
  2. сообщая, если вывод неправильный

Определение того, что что-то изменилось, часто значительно быстрее, чем определение того, что что-то не так, поэтому может быть хорошей стратегией запускать тесты, обнаруживающие изменения, чаще, чем тесты, обнаруживающие неправильность.

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

Как правило, вам нужно выполнить небольшое количество манипуляций с выводом, который вы тестируете, чтобы устранить ожидаемые различия (например, установить для встроенных дат фиксированное значение), но как только вы это сделаете, тесты для обнаружения изменений легко написать, потому что валидация - это, в основном, сравнение файлов: вывод совпадает с последним известным хорошим выводом? Да: Пройдите, Нет: Не удалось.

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

...