Как протестировать интерпретатор с помощью JUnit?

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

String output = run("let a := 21;\n" +
                    "let b := 21;\n" +
                    "print a + b;");
assertEquals(output, "42");

В идеале я хотел бы что-то вроде:

String output = run("""
    let a := 21;
    let b := 21;
    print a + b;
""");
assertEquals(output, "42");

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

Другим решением является использование другого языка JVM, такого как Scala или Jython, которые поддерживают многострочные строковые литералы, для написания тестов. Это добавит в проект новую зависимость и потребует переноса существующих тестов.

Есть ли другой способ сохранить ясность фрагментов тестового кода, не добавляя при этом слишком много обслуживания?


person vitaut    schedule 09.10.2011    source источник
comment
Пожалуйста, отредактируйте этот вопрос, так как речь идет не об интерпретаторах, а о многострочных строках.   -  person rightfold    schedule 22.06.2014


Ответы (3)


Раньше у меня работало перемещение тестовых случаев в файл, это тоже был интерпретатор:

  1. создал файл XML, содержащий фрагменты для интерпретации, а также ожидаемый результат. Это было довольно простое XML-определение, список тестовых элементов, в основном содержащий testID, value, expected result, type и description.
  2. реализовали ровно один тест JUnit, который читал файл и перебирал его содержимое, в случае сбоя мы использовали testID и description для регистрации неудачных тестов.

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

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

person home    schedule 09.10.2011
comment
Большое спасибо за то, что поделились своим опытом. Я думал о подобном подходе, но кажется, что наличие всего одного теста JUnit, который загружает и выполняет все, усложнит отладку. Когда у вас много тестовых случаев JUnit, вы можете поставить точку останова в неудачном и легко повторно выполнить его с помощью IDE. Но как это сделать с одним тестовым случаем? - person vitaut; 09.10.2011
comment
@vitaut: Вы правы, это было немного неуклюже. Мы решили это программно, определив 1 или 2 внутренних метода, которые вызывались методом цикла. Если я правильно помню, у нас также было более одного XML-файла и переключатель для каждого тестового примера внутри XML для включения/отключения определенных тестов, поэтому вы могли вручную сократить выполнение теста до одного случая или просто скопировать необходимые тестовые значения. в другой тестовый класс и вручную выполнить его из вашей IDE, если вам нужен отладчик. Это было 3 или 4 года назад, поэтому возможности использования другого языка еще не было. - person home; 09.10.2011
comment
@vitaut, небольшой трюк, который вы могли бы использовать, - это добавить символ маркера (например, восклицательный знак) в качестве первого символа имени / названия тестов, которые вы хотите отлаживать, и добавить несколько строк в свой цикл, например if (name.startsWith("!")) { log.debug("Debugging " + name); }, и поместить точку останова в строке журнала. Таким образом, вы можете выбрать тест для отладки, добавив/удалив один !. - person rsp; 09.10.2011

Поскольку вы говорите о других языках JVM, рассматривали ли вы Groovy? Вам нужно будет добавить внешнюю зависимость, но только во время компиляции/тестирования (вам не нужно помещать ее в свой производственный пакет), и она предоставляет многострочные строки. И одно большое преимущество в вашем случае: его синтаксис обратно совместим с Java (это означает, что вам не придется переписывать свои тесты)!

person Vivien Barousse    schedule 09.10.2011

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

@RunWith(Parameterized.class)
public class ParameterTest {
    @Parameters
    public static List<Object[]> data() {
        List<Object[]> list = new LinkedList<Object[]>();
        for (File file : new File("/temp").listFiles()) {
            list.add(new Object[]{file.getAbsolutePath(), readFile(file)});
        }

        return list;
    }

    private static String readFile(File file) {
        // read file 
        return "file contents";
    }

    private String filename;
    private String contents;

    public ParameterTest(String filename, String contents) {
        this.filename = filename;
        this.contents = contents;
    }

    @Test
    public void test1() {
        // here we test something
    }

    @Test
    public void test2() {
        // here we test something
    }
}

Здесь мы запускаем test1() и test2() по одному разу для каждого файла в /temp с параметрами имени файла и содержимым файла. Тестовый класс создается и вызывается для каждого элемента, который вы добавляете в список в методе, аннотированном с помощью @Parameters.

С помощью этого средства запуска тестов вы можете перезапустить конкретный файл, если он не работает; большинство IDE поддерживают повторное выполнение одного неудачного теста. Недостаток @Parameterized заключается в том, что нет никакого способа разумно идентифицировать тесты, чтобы имена отображались в подключаемом модуле Eclipse JUnit. Все, что вы получаете, это 0, 1, 2 и т. д. Но, по крайней мере, вы можете повторно запустить неудачные тесты.

Как говорит Хоум, хорошее ведение журнала важно для правильного определения неудачных тестов и для облегчения отладки, особенно при работе вне среды IDE.

person Matthew Farwell    schedule 10.10.2011