Как мне выполнить модульное тестирование генератора кода?

StackOverflow https://stackoverflow.com/questions/11060

Вопрос

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

Я разработал генератор кода, который переносит наш интерфейс Python в наш код C++ (сгенерированный через SWIG) и генерирует код, необходимый для представления его как WebServices.Когда я разрабатывал этот код, я использовал TDD, но обнаружил, что мои тесты чертовски хрупкие.Поскольку каждый тест, по сути, хотел проверить, что для данного бита входного кода (который является заголовком C++) я получу заданный бит выходного кода, я написал небольшой механизм, который считывает определения тестов из входных XML-файлов и генерирует тесты. случаи от этих ожиданий.

Проблема в том, что я вообще боюсь менять код.Это, а также тот факт, что сами модульные тесты:комплекс, и б:хрупкий.

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

Есть ли у кого-нибудь опыт чего-то подобного, которым они хотели бы поделиться?

Это было полезно?

Решение

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

Проблема в том, что это сложно протестировать, сгенерированный код может быть непригоден для фактического запуска в среде системы модульного тестирования, и как вы кодируете ожидаемые результаты?

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

Другие советы

Напомним, что «модульное тестирование» — это только один из видов тестирования.Вы должны иметь возможность провести модульное тестирование внутренний части вашего генератора кода.На самом деле вы здесь рассматриваете тестирование на уровне системы (т.регрессионное тестирование).Это не просто семантика...существуют разные образы мышления, подходы, ожидания и т. д.Это, конечно, больше работы, но вам, вероятно, придется стиснуть зубы и настроить комплексный набор регрессионных тестов:исправленные файлы C++ -> интерфейсы SWIG -> модули Python -> известный вывод.Вы действительно хотите сравнить известные входные данные (фиксированный код C++) с ожидаемыми выходными данными (то, что получается из окончательной программы Python).Непосредственная проверка результатов генератора кода аналогична проверке объектных файлов...

Да, результат – единственное, что имеет значение.Настоящая работа — это написать структуру, которая позволит сгенерированному коду работать независимо...проводите там время.

Если вы работаете на *nux, вы можете рассмотреть возможность отказа от фреймворка unittest в пользу сценария bash или make-файла.в Windows вы можете рассмотреть возможность создания приложения/функции оболочки, которая запускает генератор, а затем использует код (как еще один процесс) и выполняет модульное тестирование.

Третий вариант — сгенерировать код, а затем построить на его основе приложение, которое не содержит ничего, кроме модульного теста.Опять же, вам понадобится сценарий оболочки или что-то еще, чтобы запускать это для каждого ввода.Что касается того, как закодировать ожидаемое поведение, мне пришло в голову, что это можно сделать почти так же, как и для кода C++, просто используя сгенерированный интерфейс, а не интерфейс C++.

Просто хотел отметить, что вы все равно можете провести детальное тестирование, одновременно проверяя результаты:вы можете протестировать отдельные фрагменты кода, вложив их в код настройки и проверки:

int x = 0;
GENERATED_CODE
assert(x == 100);

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

Модульное тестирование — это тестирование конкретного модуля.Поэтому, если вы пишете спецификацию для класса A, идеально, если класс A не имеет реальных конкретных версий классов B и C.

Хорошо, позже я заметил, что тег для этого вопроса включает C++/Python, но принципы те же:

    public class A : InterfaceA 
    {   
      InterfaceB b;

      InterfaceC c;

      public A(InterfaceB b, InterfaceC c)   {
          this._b = b;
          this._c = c;   }

      public string SomeOperation(string input)   
      {
          return this._b.SomeOtherOperation(input) 
               + this._c.EvenAnotherOperation(input); 
      } 
    }

Поскольку вышеупомянутая система A внедряет интерфейсы в системы B и C, вы можете выполнить модульное тестирование только системы A, не выполняя реальную функциональность какой-либо другой системы.Это модульное тестирование.

Вот умный подход к созданию Системы от создания до завершения, с различной спецификацией When для каждой части поведения:

public class When_system_A_has_some_operation_called_with_valid_input : SystemASpecification
{
    private string _actualString;

    private string _expectedString;

    private string _input;

    private string _returnB;

    private string _returnC;

    [It]
    public void Should_return_the_expected_string()
    {
        _actualString.Should().Be.EqualTo(this._expectedString);
    }

    public override void GivenThat()
    {
        var randomGenerator = new RandomGenerator();
        this._input = randomGenerator.Generate<string>();
        this._returnB = randomGenerator.Generate<string>();
        this._returnC = randomGenerator.Generate<string>();

        Dep<InterfaceB>().Stub(b => b.SomeOtherOperation(_input))
                         .Return(this._returnB);
        Dep<InterfaceC>().Stub(c => c.EvenAnotherOperation(_input))
                         .Return(this._returnC);

        this._expectedString = this._returnB + this._returnC;
    }

    public override void WhenIRun()
    {
        this._actualString = Sut.SomeOperation(this._input);
    }
}

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

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

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

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

Я считаю, что вам нужно больше проверять то, что вы генерируете, а не то, как вы это генерируете.

В моем случае программа генерирует множество типов кода (C#, HTML, SCSS, JS и т. д.), которые компилируются в веб-приложение.Лучший способ уменьшить количество регрессионных ошибок в целом — это протестировать само веб-приложение, а не тестирование генератора.

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

Поскольку мы его генерируем, мы также генерируем красивую абстракцию в JS, которую можем использовать для программного тестирования приложения.Мы следовали некоторым идеям, изложенным здесь: http://code.tutsplus.com/articles/maintainable-automated-ui-tests--net-35089

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

Это очень мило.

Удачи!

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top