Как мне лучше всего написать модульные тесты для анализатора? - PullRequest
6 голосов
/ 13 января 2009

Я пишу парсер, который генерирует 32-битный код операции для каждой команды. Например, для следующего оператора:

set lcl_var = 2

мой парсер генерирует следующие коды операций:

// load immdshort 2 (loads the value 2)
0x10000010
// strlocal lclvar (lcl_var is converted to an index to identify the var)
0x01000002

Обратите внимание, что lcl_var может быть чем угодно, т.е. может быть задана любая переменная. Как я могу написать примеры модульных тестов для этого? Можем ли мы избежать жесткого кодирования значений? Есть ли способ сделать его родовым?

Ответы [ 7 ]

2 голосов
/ 14 января 2009

Это зависит от того, как вы структурировали свой парсер. Юнит-тест тестирует один блок.

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

set lcl_var = 2

Результат:

0x10000010 0x01000002

И то же самое для 0, -1, MAX_INT-1, MAX_INT + 1, ...

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

1 голос
/ 04 февраля 2009

Если ваш вопрос «Как запустить один и тот же тест с разными входами и ожидаемыми значениями без написания одного теста xUnit для каждой комбинации ввода-вывода?»

Тогда ответом будет использование чего-то вроде расширения RowTest NUnit. Я недавно написал сообщение о быстрой загрузке в своем блоге. Примером этого будет

[TestFixture]
    public class TestExpression
    {
        [RowTest]
        [Row(" 2 + 3 ", "2 3 +")]
        [Row(" 2 + (30 + 50 ) ", "2 30 50 + +")]
        [Row("  ( (10+20) + 30 ) * 20-8/4 ", "10 20 + 30 + 20 * 8 4 / -")]
        [Row("0-12000-(16*4)-20", "0 12000 - 16 4 * - 20 -")]
        public void TestConvertInfixToPostfix(string sInfixExpr, string sExpectedPostfixExpr)
        {
            Expression converter = new Expression();
            List<object> postfixExpr = converter.ConvertInfixToPostfix(sInfixExpr);

            StringBuilder sb = new StringBuilder();
            foreach(object term in postfixExpr)
            {
                sb.AppendFormat("{0} ", term.ToString());
            }
            Assert.AreEqual(sExpectedPostfixExpr, sb.ToString().Trim());
        }
1 голос
/ 14 января 2009
int[] opcodes = Parser.GetOpcodes("set lcl_var = 2");
Assert.AreEqual(2, opcodes.Length);
Assert.AreEqual(0x10000010, opcodes[0]);
Assert.AreEqual(0x01000002, opcodes[1]);
0 голосов
/ 24 февраля 2009

Что вы хотите проверить? Вы хотите знать, создана ли правильная инструкция "store"? Подобрана ли правильная переменная? Решите, что вы хотите знать, и тест будет очевидным. Пока вы не знаете, чего хотите достичь, вы не будете знать, как проверить неизвестное.

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

Сегодня не пытайтесь быть тем, кем вы будете завтра.

0 голосов
/ 06 февраля 2009

Не ясно, ищете ли вы методологию или конкретную технологию для тестирования.

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

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

set lcl_var = 2

set lcl_var = 3

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

Хотя я не использовал cppunit, я использовал собственный инструмент, очень похожий на cppunit. Было легко реализовать модульные тесты с использованием cppunit.

0 голосов
/ 14 января 2009

Насколько я понимаю, вы сначала написали бы тест для вашего конкретного примера, то есть, где входные данные для вашего парсера:

set lcl_var = 2

и вывод:

0x10000010 // load immdshort 2
0x01000002 // strlocal lclvar 

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

set lcl_var2 = 2

И напишите свой новый тест, чтобы ожидать другого результата, который вы хотите. Продолжайте делать это, пока не убедитесь, что ваш производственный код достаточно надежен.

0 голосов
/ 13 января 2009

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

Если это так, то здесь вам может помочь внедрение зависимостей. Если назначение отправленных кодов операций является экземпляром класса (например, File), попробуйте дать классу эмиттера конструктор, который принимает объект этого типа для использования в качестве назначения для излучаемого кода. Затем из модульного теста вы можете передать фиктивный объект, который является экземпляром подкласса вашего класса назначения, захватить испущенные коды операций для определенных операторов и подтвердить, что они верны.

Если ваш целевой класс не является легко расширяемым, вы можете создать на его основе интерфейс, который может реализовать и целевой класс, и ваш фиктивный класс.

...