Автоматическая генерация классов из модульных тестов?

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

Вопрос

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

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 с нуля, используя какие библиотеки)?

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

Решение

Похоже, вам нужен синтаксический анализатор для вашего языка (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 вероятно, я мог бы это сделать.DMS - это инструмент для разработчиков инструментов, предназначенный для людей, которые хотят создавать инструменты, обрабатывающие компьютерные языки произвольными способами.(Подумайте о "вычислениях с использованием фрагментов программы, а не чисел").

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

research.microsoft.com/en-us/projects/Pex/

Я думаю, что вы ищете-это инструментальный метод Монте-Карло (https://en.wikipedia.org/wiki/Fuzz_testing).

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

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

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

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

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

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

Фрагменты.Небольшие шаблоны кода, которые позволяют легко генерировать фрагменты общего кода.Некоторые из них просты (попробуйте "if[TAB] [ВКЛАДКА]").Некоторые из них сложны ('switch' сгенерирует варианты для перечисления).Вы также можете написать свой собственный.В вашем случае попробуйте "класс" и "опора".

Смотрите также "Как изменить “Сгенерировать заглушку метода”, чтобы вызвать исключение NotImplementedException в VS?" для получения фрагментов информации в контексте GMS.

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

public string Name { get; set; }

создать класс.В обозревателе решений щелкните по названию проекта или вложенной папке, выберите Добавить-> Класс.Введите название вашего нового класса.Попал ВОЙТИ.Вы получите объявление класса в нужном пространстве имен и т.д.

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

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

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

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

Кстати, возможно, вы захотите взглянуть на Иметь?

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

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

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