Как мне unit test генерировать код?

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

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

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

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

У кого-нибудь есть какой-то опыт чего-то подобного, который они хотели бы разделить?

Ответ 1

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

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

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

Ответ 2

Напомним, что "модульное тестирование" - это только один вид тестирования. Вы должны иметь unit test внутренние части вашего генератора кода. То, что вы на самом деле смотрите здесь, - это тестирование уровня системы (регрессионное тестирование a.k.a.). Это не просто семантика... есть разные мысли, подходы, ожидания и т.д. Это, безусловно, больше работает, но вам, вероятно, придется укусить пулю и настроить комплексный набор тестов регрессии: исправлены файлы С++ → SWIG интерфейсы → модули python → известный выход. Вы действительно хотите проверить известный вход (фиксированный код на С++) по сравнению с ожидаемым выходом (что выходит из финальной программы Python). Проверка результатов генерации кода напрямую будет похожа на файлы с различными объектами...

Ответ 3

Да, результаты - это ТОЛЬКО важная вещь. Настоящая работа требует создания фреймворка, позволяющего независимо запускать ваш сгенерированный код... тратить там время.

Ответ 4

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

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

Ответ 5

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

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

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

Ответ 6

Единичное тестирование - это просто тестирование определенного устройства. Поэтому, если вы пишете спецификацию для класса A, это идеально, если класс A не имеет реальных конкретных версий классов B и 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, вы можете unit test просто систему 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);
    }
}

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

Ответ 7

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

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

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

Ответ 8

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

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

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

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

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

Это довольно мило.

Удачи!