Я участвую в проекте, где Java AST переведен на другой язык, OpenCL, с использованием компилятора Eclipse, и у меня есть похожие проблемы.
У меня нет волшебных решений для вас, но я поделюсь своим опытом, если это поможет.
Ваша методика тестирования с ожидаемым выходным значением (с output.txt) - это то, с чего я и начинал, но это стало абсолютным кошмаром обслуживания для тестов. Когда по какой-то причине мне пришлось сменить генератор или выход (что происходило несколько раз), мне пришлось переписать все ожидаемые выходные файлы - и их было огромное количество. Я начал вообще не хотеть менять вывод из-за страха сломать все тесты (что было плохо), но в итоге я их отбросил и вместо этого провел тестирование на полученном AST. Это означало, что я мог «свободно» проверить вывод. Например, если я хотел протестировать генерацию операторов if, я мог бы просто найти единственный-единственный оператор if в сгенерированном классе (я написал вспомогательные методы для выполнения всего этого общего AST), проверить несколько вещей об этом и быть сделано Этот тест не заботился о том, как был назван класс или были ли дополнительные аннотации или комментарии. В итоге все получилось довольно хорошо, так как тесты были более целенаправленными. Недостатком является то, что тесты были более тесно связаны с кодом, поэтому, если я когда-нибудь захочу вырвать компилятор Eclipse / библиотеку AST и использовать что-то еще, мне нужно будет переписать все мои тесты. В конце концов, поскольку генерация кода со временем менялась, я был готов заплатить эту цену.
Я также сильно полагаюсь на интеграционные тесты - тесты, которые фактически компилируют и запускают сгенерированный код на целевом языке. У меня было гораздо больше таких тестов, чем модульных, просто потому, что они казались более полезными и улавливали больше проблем.
Что касается тестирования посетителей, я снова провожу с ними больше тестов в стиле интеграции - получаю действительно маленький / специфический исходный файл Java, загружаю его с помощью компилятора Eclipse, запускаю одного из моих посетителей и проверяю результаты. Единственный другой способ тестирования без вызова компилятора Eclipse - это макетирование всего AST, что было просто неосуществимо - большинство посетителей были нетривиальными и нуждались в полностью построенном / действительном Java AST, так как они читали аннотации из основного класса. , Большинство посетителей прошли тестирование таким образом, потому что они либо генерировали небольшие фрагменты кода OpenCL, либо создавали структуру данных, которую могли проверить модульные тесты.
Да, все мои тесты очень тесно связаны с компилятором Eclipse. Но так же актуально и программное обеспечение, которое мы пишем. Использование чего-либо еще означало бы, что мы все равно должны были бы переписать всю программу, так что это цена, которую мы очень рады заплатить. Я думаю, что нет единого решения - вам нужно взвесить стоимость жесткой связи в сравнении с ремонтопригодностью / простотой.
У нас также есть достаточное количество кода для тестирования, например, настройка компилятора Eclipse с настройками по умолчанию, код для извлечения узлов тела деревьев методов и т. Д. Мы стараемся сделать тесты как можно меньше (я знаю, что это, вероятно, здравый смысл, но, возможно, стоит упомянуть).
(правки / добавления ниже в ответах на комментарии - легче читать / форматировать, чем комментарии)
«Я также очень полагаюсь на интеграционные тесты - тесты, которые фактически компилируют и запускают сгенерированный код на целевом языке» Что на самом деле делали эти тесты? Чем они отличаются от тестов output.txt?
(Снова отредактируйте: после перечитывания вопроса я понимаю, что наши подходы одинаковы, поэтому проигнорируйте это)
Вместо того, чтобы просто генерировать исходный код и сравнивать его с ожидаемым результатом, который я делал изначально, интеграционные тесты генерируют код OpenCL, компилируют его и запускают. Весь сгенерированный код производит вывод, и этот вывод затем сравнивается.
Например, у меня есть класс Java, который, если генератор работает должным образом, должен генерировать код OpenCL, который суммирует значения в двух буферах и помещает значение в третий буфер.Сначала я написал бы текстовый файл с ожидаемым кодом OpenCL и сравнил его в моем тесте.Теперь интеграционный тест генерирует код, запускает его через компилятор OpenCL, запускает его, а затем проверяет значения.
"Что касается тестирования посетителей, я снова провожу больше тестов в стиле интеграции сих - получите действительно маленький / специфический исходный файл Java, загрузите его с помощью компилятора Eclipse, запустите с ним одного из моих посетителей и проверьте результаты. "Вы имеете в виду, что вы работаете с одним из ваших посетителей или доведите всех посетителей до посетителя?Вы хотите проверить?
Большинство посетителей могут работать независимо друг от друга.По возможности, я мог работать только с посетителем, которого я тестирую, или, если есть зависимость от других, требовался минимальный набор посетителей (обычно требовался только один другой).Посетители не общаются друг с другом напрямую, а используют объекты контекста, которые передаются.Они могут быть созданы искусственно в тестах, чтобы привести вещи в известное состояние.
Другой вопрос, вы используете макеты - вообще, в этом проекте?Кроме того, вы регулярно используете насмешки в других проектах?Я просто пытаюсь получить четкое представление о человеке, с которым разговариваю: P
В этом проекте мы используем макеты примерно в 5% тестов, возможно, даже меньше.И я не издеваюсь над компилятором Eclipse
Дело в том, что с mocks мне нужно понять, что я хорошо копирую, а с компилятором Eclipse дело обстоит иначе.Есть много методов посетителя, которые вызываются, и иногда я не уверен, какой из них должен быть вызван (например, вызывается ли визит ExtendedStringLiteral или визит StringLiteral для строковых литералов?) Если бы я это сделал и предположил, один или другойэто может не соответствовать действительности, и программа не сможет работать, даже если тесты пройдут - не желательно.Единственные насмешки, которые мы делаем, - это пара для API процессора аннотаций, пара адаптеров компилятора Eclipse и некоторые из наших собственных базовых классов.
В других проектах, таких как Java EE, использовалось больше насмешек, ноЯ все еще не заядлый пользователь их.Чем более определенным, понятным и предсказуемым является API, тем больше вероятность того, что я рассмотрю использование mocks.
Первые этапы нашей программы аналогичны обычному компилятору.Мы извлекаем информацию из исходных файлов и заполняем (большую и сложную!) Таблицу символов.Как бы вы пошли о тестировании системы это?Теоретически, я мог бы создать тест с исходными файлами, а также symbolTable.txt (или .xml или любой другой), который содержит всю информацию о symbolTable, но это, я думаю, было бы немного сложнее сделать.Каждый из этих интеграционных тестов будет сложным для выполнения!
Я бы попробовал использовать метод тестирования маленьких битов таблицы символов, а не всей партии за один раз.Если бы я проверял, правильно ли построено дерево Java, у меня было бы что-то вроде:
Этот подход представляет собой интеграционное тестирование, но каждый интеграционный тест тестирует только небольшую частьсистема.
По сути, я бы старался сделать тесты как можно меньше.Большую часть тестового кода для извлечения кусочков дерева можно перенести в служебные методы, чтобы сделать тестовые классы небольшими.Обозначьте таблицу символов и выведите соответствующие исходные файлы (которые, если бы все было в порядке, были бы такими же, как исходные файлы).Проблема в том, что исходные файлы могут иметь вещи в другом порядке, чем то, что печатает мой симпатичный принтер.Боюсь, что при таком подходе я могу просто открыть еще одну банку с червями.Я неустанно рефакторинг частей кода, и ошибки начинают хвастаться.Мне действительно нужно несколько интеграционных тестов, чтобы держать меня в курсе.
Именно такой подход я выбрал.Однако в моей системе порядок вещей не сильно меняется.У меня есть генераторы, которые по сути выводят код в ответ на узлы Java AST, но есть некоторая свобода в том, что генераторы могут называть себя рекурсивно.Например, генератор 'if', который запускается в ответ на Java-оператор узла AST, может выписать 'if (', затем попросить другие генераторы отобразить условие, а затем написать ') {', попросить другие генераторы написатьиз тела, затем напишите '}'.