Генерация методов в дизайне или во время сборки (C #) - PullRequest
3 голосов
/ 15 декабря 2011

У меня есть решение для тестирования интеграции.Мои тесты описаны в файлах XML.Чтобы извлечь выгоду из инфраструктуры тестирования Visual Studio 2010, у меня есть класс C #, где у каждого тестового XML-файла есть связанный метод, который загружает XML-файл и выполняет его содержимое.Это выглядит так:

[TestClass]
public class SampleTests
{

    [TestMethod]
    public void Test1()
    {
        XamlTestManager.ConductTest();
    }

    [TestMethod]
    public void Test2()
    {
        XamlTestManager.ConductTest();
    }

    ...

    [TestMethod]
    public void TestN()
    {
        XamlTestManager.ConductTest();
    }
}

Каждое имя метода соответствует имени файла XML.Следовательно, в моем тестовом каталоге должны быть следующие файлы:

  • Test1.xml
  • Test2.xml
  • ...
  • TestN.xml

XamlTestManager.ConductTest() использует класс StackTrace, чтобы получить имя вызывающего метода, и таким образом он может найти правильный файл теста XML для загрузки.

Я бы хотел избавиться от дополнительного администрирования добавления / удаления / переименования тестовых методов всякий раз, когда я меняю свои тесты, добавляя / удаляя / переименовывая XML-файл теста. Как я могу автоматически сгенерировать этот класс или его методы во время процесса компиляции на основе фактических файлов XML в моей тестовой директории?

Вариант 1. Я рассмотрел PostSharp, но он не позволяетмне посмотреть файлы XML и сгенерировать методы на лету (или я был поверхностным?).
Вариант 2. Другая идея заключалась в создании пользовательского инструмента Visual Studio, который генерирует мой код при каждом его выполнении.Недостатком здесь является размещение.Пользовательский инструмент должен быть зарегистрирован в VS.Мне нужно решение, которое можно поместить в репозиторий, проверить его на другом компьютере и сразу использовать. (я верю в простоту. «Проверьте и запустите» просто значительно упрощает жизнь новых разработчиков, если им не нужно просматривать список вещей для установки, прежде чем они смогут скомпилировать, запустить приложение.)

Есть ли у вас какие-либо рекомендации, как избавиться от ненужной проблемы обслуживания?

РЕДАКТИРОВАТЬ:
По просьбе Джастина, я добавляю более подробную информацию,Мы используем Bizunit (потрясающе !!!) в качестве основы нашей платформы с большим количеством пользовательских этапов испытаний высокого уровня.Из этих шагов мы можем построить наш тест как из блоков lego декларативным способом.Нашими шагами являются, например, FileDrop, вызов WebService или даже опрос, запуск полноценного веб-сервера для имитации партнерского веб-приложения, генератор случайных данных, этапы сравнения данных и т. Д. Вот пример тестового XML (фактически XAML):

<TestCase BizUnitVersion="4.0.154.0" Name="StackOverflowSample" xmlns="clr-namespace:BizUnit.Xaml;assembly=BizUnit" xmlns:nib="clr-namespace:MyCompany.IntegrationTest;assembly=BizUnit.MyCustomSteps" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <TestCase.SetupSteps>
    <nib:ClearStep FailOnError="True" RunConcurrently="False" />
    <nib:LaunchSimulatedApp AppKernelCacheKey="provider" FailOnError="True" FireWakeUpCall="False" PortNumber="4000" RepresentedSystem="MyProviderService" RunConcurrently="False" />
    <nib:HttpGetStep FailOnError="True" RunConcurrently="False" Url="http://localhost:10000/Home/StartSvgPolling">
      <nib:HttpGetStep.Parameters>
        <x:String x:Key="PolledAddress">http://localhost:4000/SvgOutputPort.asmx</x:String>
        <x:String x:Key="PollingInterval">10</x:String>
        <x:String x:Key="FilterFile"></x:String>
      </nib:HttpGetStep.Parameters>
    </nib:HttpGetStep>
  </TestCase.SetupSteps>

  <TestCase.ExecutionSteps>
    <nib:DocumentMergeStep FailOnError="True" OutputCacheKey="inputDocument" RunConcurrently="False">
      <nib:DocumentMergeStep.InputDocuments>
        <nib:RandomLoader BoundingBox="Europe" LinkbackUrlPattern="http://MyProviderService/id={0}" MaxAmount="10" MaxID="100" MinAmount="10" MinID="0" NamePattern="EuropeanObject_{0}" NativeFormat="Svg" RepeatableRandomness="False" UriPrefix="European" />
        <nib:RandomLoader BoundingBox="PacificIslands" LinkbackUrlPattern="http://MyProviderService/id={0}" MaxAmount="10" MaxID="100" MinAmount="10" MinID="0" NamePattern="PacificObject_{0}" NativeFormat="Svg" RepeatableRandomness="False" UriPrefix="Pacific" />
      </nib:DocumentMergeStep.InputDocuments>
    </nib:DocumentMergeStep>
    <nib:PushToSimulatedApp AppKernelCacheKey="provider" ContentFormat="Svg" FailOnError="True" RunConcurrently="False">
      <nib:PushToSimulatedApp.InputDocument>
        <nib:CacheLoader SourceCacheKey="inputDocument" />
      </nib:PushToSimulatedApp.InputDocument>
    </nib:PushToSimulatedApp>
    <nib:GeoFilterStep FailOnError="True" OutputCacheKey="filteredDocument" RunConcurrently="False" SelectionBox="Europe">
      <nib:GeoFilterStep.InputDocument>
        <nib:CacheLoader SourceCacheKey="inputDocument" />
      </nib:GeoFilterStep.InputDocument>
    </nib:GeoFilterStep>
    <nib:DeepCompareStep DepthOfComparision="ID, Geo_2MeterAccuracy, PropertyBag, LinkbackUrl" FailOnError="True" RunConcurrently="False" Timeout="30000" TolerateAdditionalItems="False">
      <nib:DeepCompareStep.ReferenceSource>
        <nib:CacheLoader SourceCacheKey="filteredDocument" />
      </nib:DeepCompareStep.ReferenceSource>
      <nib:DeepCompareStep.InvestigatedSource>
        <nib:SvgWebServiceLoader GeoFilter="Europe" NvgServiceUrl="http://localhost:10000/SvgOutputPort.asmx"/>
      </nib:DeepCompareStep.InvestigatedSource>
    </nib:DeepCompareStep>
  </TestCase.ExecutionSteps>

  <TestCase.CleanupSteps>
    <nib:HttpGetStep FailOnError="True" RunConcurrently="False" Url="http://localhost:10000/Home/StopSvgPolling">
      <nib:HttpGetStep.Parameters>
        <x:String x:Key="PolledAddress">http://localhost:4000/SvgOutputPort.asmx</x:String>
      </nib:HttpGetStep.Parameters>
    </nib:HttpGetStep>
    <nib:KillSimulatedApp AppKernelCacheKey="provider" FailOnError="True" PortNumber="4000" RunConcurrently="False" />
  </TestCase.CleanupSteps>
</TestCase>

Это то, что он делает:

  1. Вызывает операцию очистки для объекта тестирования
  2. Запускает веб-сервер на порту 4000 в качестве приложения-имитатора для партнеров под именем MyProviderService
  3. Вызывает испытуемого через HTTP Get для опроса смоделированного партнера
  4. Создает новый документ, содержащий геоинформацию из двух случайно сгенерированных материалов
  5. Отправляет документ смоделированному партнеру -следовательно, испытуемый выберет его с помощью опроса
  6. . Тест применяет к документу геофильтр
  7. Этап глубокого сравнения загружает отфильтрованный документ в качестве базы сравнения и загружает содержимоеиспытуемый через веб-сервис
  8. В качестве очистки он останавливает опрос с помощью шага HTTP GETи убивает веб-сервер симулируемого партнера.

Сила Bizunit заключается в том, что объединяет простоту создания тестов в C # с intellisense и простоту поддержки / дублирования его в файлах XAML.Для быстрого ознакомления с принципами работы: http://kevinsmi.wordpress.com/2011/03/22/bizunit-4-0-overview/

Ответы [ 4 ]

1 голос
/ 15 декабря 2011

Как сказал @GeorgeDuckett, шаблоны T4 - это, вероятно, путь.В приложении, над которым я работаю, мы используем их много, включая генерацию репозиториев, сервисов, ViewModels, Enums и недавних модульных тестов.

В основном это скрипты генерации кода, написанные либо на VB, либо на C #.каталог для файлов XML не будет проблемой для такого рода шаблонов.

Если вы решите пойти по маршруту T4, Tangible T4 Editor , безусловно, должен быть, этобесплатная загрузка.

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

<#@ template language="C#" debug="true" hostspecific="true"#>
<#@ output extension="g.cs"#>
[TestClass]
public class SampleTests
{
<#
string[] files = Directory.GetFiles(@"C:\TestFiles", "*.xml");
foreach(string filePath in files)
{
    string fileName = Path.GetFileNameWithoutExtension(filePath);
#>
    [TestMethod]
    public void <#=fileName#>()
    {
        XamlTestManager.ConductTest();
    }
<#
}
#>
}

Убедитесь, что он помещен в файл с.Расширение tt, затем в окнах свойств этого файла убедитесь, что для параметра «Действие построения» задано значение None, для пользовательского инструмента - TextTemplatingFileGenerator.

Редактирование: доступ к выходному каталогу из шаблона T4

Добавьтеследующие две строки вверху вашего шаблона T4, под строкой <# @ template ... #>:

<#@ assembly name="EnvDTE" #>
<#@ import namespace="EnvDTE" #>

Затем внутри вашего шаблона вы можете получить доступ и использовать API Visual Studio, например:Итак:

IServiceProvider serviceProvider = this.Host as IServiceProvider;
DTE dte = serviceProvider.GetService(typeof(DTE)) as DTE;
object[] activeSolutionProjects = dte.ActiveSolutionProjects as object[];

if(activeSolutionProjects != null)
{
    Project project = activeSolutionProjects[0] as Project;
    if(project != null)
    {
        Properties projectProperties = project.Properties;
        Properties configurationProperties = project.ConfigurationManager.ActiveConfiguration.Properties;
        string projectDirectory = Path.GetDirectoryName(project.FullName);  
        string outputPath = configurationProperties.Item("OutputPath").Value.ToString();
        string outputFile = projectProperties.Item("OutputFileName").Value.ToString();

        string outDir = Path.Combine(projectDirectory, outputPath);
        string targetPath = Path.Combine(outDir, outputFile);
    }
}

outDir и targetPath содержат выходной каталог и полный путь к выходному файлу.

1 голос
/ 15 декабря 2011

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

[TestClass]
public class SampleTests
{
    [TestMethod]
    public void Test()
    {
        for (var i = 0; i < 10; ++i)
            XamlTestManager.ConductTest(i); 
    }
}

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

[TestClass]
public class SampleTests
{
    public TestContext Context { get; set; }

    [TestMethod]
    [DataSource(...)]
    public void Test()
    {
        var someData = Context.DataRow["SomeColumnName"].ToString();
        ...
    }
}
0 голосов
/ 19 декабря 2011

Только что ответил "вопрос генерации кода из XML с T4".

https://stackoverflow.com/a/8554949/753110

Ваше требование в точности соответствует тому, что мы делали изначально (и что привело к открытию ADM, описанному в этом ответе).

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

Добавлена ​​пользовательская демонстрация генерации на основе XML для этого другого примера, если вы хотите увидеть:

https://github.com/abstractiondev/DemoSOCase8552428ABS

0 голосов
/ 15 декабря 2011

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

Если бы вы использовали xunit, вы могли бы сделать это так:

public class SampleTests
{
    [Theory]
    [InlineData(1)]
    [InlineData(2)]
    [InlineData(...)]
    [InlineData(N)]
    public void Test(int x)
    {
        XamlTestManager.ConductTest(x);
    }
}

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

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

...