Сведения о вопросе

FELL

18:49, 9th August, 2020

Теги

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

Просмотров: 487   Ответов: 8

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

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

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

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

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



  Сведения об ответе

lats

22:21, 14th August, 2020

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

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

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


  Сведения об ответе

lats

20:18, 29th August, 2020

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


  Сведения об ответе

P_S_S

09:20, 10th August, 2020

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

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

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


  Сведения об ответе

baggs

04:01, 18th August, 2020

Модульное тестирование - это всего лишь тестирование конкретной единицы измерения. Поэтому, если вы пишете спецификацию для класса 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);
    }
}

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


  Сведения об ответе

P_S_S

08:31, 11th August, 2020

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


  Сведения об ответе

lool

20:58, 17th August, 2020

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

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


  Сведения об ответе

SEEYOU

13:07, 2nd August, 2020

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

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

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


  Сведения об ответе

DINO

16:43, 24th August, 2020

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

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

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

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

Большая часть заключается в том, что он действительно проверяет вашу систему end-to-end, от генерации кода до того, что вы на самом деле генерируете. После провала теста его легко отследить до того места, где сломался генератор.

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

Удачи вам!


Ответить на вопрос

Чтобы ответить на вопрос вам нужно войти в систему или зарегистрироваться