Генерация классов автоматически из модульных тестов? - PullRequest
19 голосов
/ 18 сентября 2008

Я ищу инструмент, который может пройти модульный тест, например

IPerson p = new Person();
p.Name = "Sklivvz";
Assert.AreEqual("Sklivvz", p.Name);

и автоматически сгенерировать соответствующий класс заглушки и интерфейс

interface IPerson         // inferred from IPerson p = new Person();
{
    string Name 
    { 
        get;              // inferred from Assert.AreEqual("Sklivvz", p.Name);
        set;              // inferred from p.Name = "Sklivvz";
    }
}

class Person: IPerson     // inferred from IPerson p = new Person();
{
    private string name;  // inferred from p.Name = "Sklivvz";

    public string Name    // inferred from p.Name = "Sklivvz";
    {
        get
        {
            return name;  // inferred from Assert.AreEqual("Sklivvz", p.Name);
        }
        set
        {
            name = value; // inferred from p.Name = "Sklivvz";
        }
    }

    public Person()       // inferred from IPerson p = new Person();
    {
    }
}

Я знаю, что ReSharper и Visual Studio делают некоторые из них, но мне нужен полный инструмент - командная строка или еще много чего - который автоматически определяет, что нужно сделать. Если такого инструмента нет, как бы вы его написали (например, расширение ReSharper, с нуля, с использованием каких библиотек)?

Ответы [ 11 ]

4 голосов
/ 02 апреля 2012

То, что вам нужно, - это анализатор вашего языка (Java) и средство определения имен и типов. («Конструктор таблиц символов»).

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

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

Для

 IPerson p = new Person();

распознаватель имен знает, что «Person» и «IPerson» не определены. Если бы это было

 Foo  p =  new Bar();

не будет никакой подсказки, что вам нужен интерфейс, просто что Foo является неким абстрактным родителем Bar (например, класс или интерфейс). Таким образом, решение о том, каково это, должно быть известно инструменту («всякий раз, когда вы найдете такую ​​конструкцию, предположите, что Foo является интерфейсом ...»). Вы можете использовать эвристику: IFoo и Foo означают, что IFoo должен быть интерфейсом, и где-то кто-то должен определить Foo как класс, реализующий этот интерфейс. Однажды инструмент принял это решение, ему необходимо обновить свои таблицы символов, чтобы он мог перейти к другим утверждениям:

Для

 p.Name = "Sklivvz";

учитывая, что p должен быть интерфейсом (согласно предыдущему выводу), тогда Name должно быть элементом поля, и, похоже, его тип - String из присваивания.

С этим утверждение:

 Assert.AreEqual("Sklivvz", p.Name);

имена и типы разрешаются без дальнейшей проблемы.

Содержание сущностей IFoo и Foo зависит от вас; вам не нужно было использовать get и set, но это личный вкус.

Это не будет работать так хорошо, если в одном выражении несколько сущностей:

 x = p.a + p.b ;

Мы знаем, что a и b являются вероятными полями, но вы не можете угадать, какой числовой тип действительно, если они числовые, или если они являются строками (это допустимо для строк в Java, не знаю о C #). Для C ++ вы даже не знаете, что означает «+»; это может быть оператор в классе Bar. Поэтому вам нужно собрать ограничений , например, «a - это какое-то неопределенное число или строка» и т. Д., И, поскольку инструмент собирает доказательства, он сужает набор возможных ограничений. (Это работает, как те проблемы со словом: «У Джо семь сыновей. Джефф выше Сэма. Гарри не может спрятаться за Сэмом ... кто близнец Джеффа?», Где вы должны собрать доказательства и устранить невозможность) Вам также нужно беспокоиться о случае, когда вы столкнетесь с противоречием.

Вы можете исключить вариант p.a + p.b, но тогда вы не сможете безнаказанно писать свои модульные тесты. Существуют стандартные решатели ограничений, если вы хотите безнаказанности. (Что за концепция).

Хорошо, у нас есть идеи, теперь, можно ли это сделать на практике?

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

Наш набор инструментов для реинжиниринга программного обеспечения DMS с его Java Front End мог бы сделать это. DMS - это инструмент для создания инструментов, предназначенный для людей, которые хотят создавать инструменты, которые обрабатывают компьютерные языки произвольным образом. (Подумайте о «вычислениях с фрагментами программы, а не с числами»).

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

Где-то вы должны решить, что все готово. Сколько юнит-тестов должен увидеть инструмент прежде чем он знает весь интерфейс? (Я думаю, это съедает все, что вы предоставляете?). После завершения он собирает фрагменты для различных членов и создает AST для интерфейса; DMS может использовать свой prettyprinter для преобразования этого AST обратно в исходный код, как вы показали.

Я предлагаю Java здесь, потому что наш интерфейс Java имеет разрешение имен и типов. Наш интерфейс C # не делает. Это «простой» вопрос амбиций; кто-то должен написать один, но это довольно много работы (по крайней мере, для Java, и я не могу представить, что C # действительно отличается).

Но идея в принципе отлично работает с использованием DMS.

Вы можете сделать это с другой инфраструктурой, которая предоставит вам доступ к анализатору и гибкому преобразователю имен и типов. Это может быть не так легко получить для C #; Я подозреваю, что MS может предоставить вам анализатор и доступ к разрешению имен и типов, но никак не изменить это. Может быть, моно ответ?

Вам все еще нужно было сгенерировать фрагменты кода и собрать их. Вы можете попытаться сделать это путем взлома строк; Мой (долгий) опыт склеивания программных фрагментов состоит в том, что, если вы делаете это со строками, вы в конечном итоге создаете беспорядок. Вы действительно хотите фрагменты, которые представляют фрагменты кода известного типа, которые могут быть объединены только так, как позволяет грамматика; DMS делает это, таким образом, не беспорядок.

3 голосов
/ 27 сентября 2008

Удивительно, как никто на самом деле ничего не дал на то, что вы просили.

Я не знаю ответа, но я выскажу свои мысли по этому поводу.

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

Теперь я ни в коем случае не знаю, как это сделать, поскольку я никогда ничего не делал для resharper, но я бы попытался это сделать. Логично, что это можно сделать.

И если вы пишете какой-то код, ПОЖАЛУЙСТА, опубликуйте его, так как я мог бы также найти это полезным, имея возможность генерировать весь скелет за один шаг. Очень полезно.

1 голос
/ 08 апреля 2012

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

dynamic p = someFactory.Create("MyNamespace.Person");
p.Name = "Sklivvz";
Assert.AreEqual("Sklivvz", p.Name);

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

Некоторые недостатки, которые я вижу:

  • Нужно запускать код в «двух» режимах, что раздражает.
  • Чтобы прокси-сервер «видел» и записывал вызовы, они должны быть выполнены; например, должен выполняться код в блоке catch.
  • Вы должны изменить способ создания тестируемого объекта.
  • Вы должны использовать dynamic; при последующих запусках вы потеряете безопасность во время компиляции, а производительность снизится.

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

1 голос
/ 07 января 2009

Это выполнимо - по крайней мере, в теории. Я хотел бы использовать что-то вроде csparser , чтобы проанализировать модульный тест (к сожалению, вы не можете его скомпилировать), а затем взять его оттуда. Единственная проблема, которую я вижу, состоит в том, что то, что вы делаете, неверно с точки зрения методологии - более логично создавать модульные тесты из классов сущностей (в действительности, Visual Studio делает именно это), чем делать это наоборот.

1 голос
/ 18 сентября 2008

Если вы планируете написать собственную реализацию, я определенно рекомендую вам взглянуть на NVelocity (C #) или Velocity (Java) шаблонизаторы.

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

0 голосов
/ 08 апреля 2012

Я думаю, что вы ищете набор нечетких инструментов (https://en.wikipedia.org/wiki/Fuzz_testing).

Несмотря на то, что я никогда не использовал, вы могли бы дать Randoop.NET возможность генерировать «модульные тесты» http://randoop.codeplex.com/

0 голосов
/ 22 августа 2009

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

research.microsoft.com / ru-ru / projects / Pex /

0 голосов
/ 20 сентября 2008

Мне нравится CodeRush от DevExpress. У них огромный настраиваемый шаблонизатор. И самое лучшее для меня - это отсутствие диалоговых окон. Они также имеют функциональность для создания методов, интерфейсов и классов из несуществующего интерфейса.

0 голосов
/ 18 сентября 2008

Visual Studio поставляется с некоторыми функциями, которые могут быть вам полезны здесь:

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

Если вы клавишник (я), то сразу после ввода закрывающей скобки вы можете сделать:

  • Ctrl -. (чтобы открыть смарт-тег)
  • ENTER (для создания заглушки)
  • F12 (перейти к определению, чтобы перейти к новому методу)

Смарт-тег появляется только в том случае, если среда IDE считает, что метод не подходит. Если вы хотите сгенерировать, когда смарт-тег не работает, вы можете перейти к Правка-> Intellisense-> Генерировать заглушку метода .

Отрывки . Небольшие шаблоны кода, которые позволяют легко генерировать биты общего кода. Некоторые из них просты (попробуйте «if [TAB] [TAB]»). Некоторые из них сложные («switch» генерирует регистры для перечисления). Вы также можете написать свой собственный. В вашем случае попробуйте «class» и «prop».

См. Также « Как изменить« Генерировать заглушку метода », чтобы генерировать исключение NotImplementedException в VS? » для информационных фрагментов в контексте GMS.

автосвойства . Помните, что свойства могут быть намного проще:

public string Name { get; set; }

создать класс . В обозревателе решений RC кликните по имени проекта или подпапке и выберите Add-> Class . Введите имя вашего нового класса. Нажмите ENTER . Вы получите объявление класса в правильном пространстве имен и т. Д.

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

Это не совсем 100% автоматизированное решение, которое вы ищете, но я думаю, что это хорошее смягчение.

0 голосов
/ 18 сентября 2008

Я использую для этого Rhino Mocks, когда мне просто нужна простая заглушка.

http://www.ayende.com/wiki/Rhino+Mocks+-+Stubs.ashx

...