Andrey on .NET | MSTest V2 – Часть 4: Изменяем процесс тестирования

MSTest V2 – Часть 4: Изменяем процесс тестирования

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

Создаем свой процесс тестирования

MSTest V2 позволяет изменить весь процесс тестирования, т.е. то, как запускаются тесты и сколько раз, какой код выполняется до и после них.

Все, что было описано в предыдущих двух частях, можно также реализовать при помощи данного подхода. Но поскольку запуск тестов с разными наборами данных и вызов заданных методов нужны достаточно часто, то разработчики MSTest V2 добавили в библиотеку готовые решения.

Чтобы реализовать свой процесс тестирования необходимо создать атрибут, унаследованный от TestMethodAttribute. Данный класс, кроме конструктора и свойства DisplayName, содержит виртуальный метод TestResult[] Execute(ITestMethod testMethod). MSTest V2 использует его для выполнения каждого теста, информация о котором передается через аргумент testMethod. В частности, будет передано:

  • Имя класса, который содержит тест.
  • Имя метода, который реализует тест.
  • Атрибуты этого метода.
  • Информация о его параметрах.
  • Тип возвращаемого значения.

Кроме того, TestResult ITestMethod.Invoke(object[] arguments) позволяет выполнить сам тест и получить экземпляр TestResult с описанием результата.

Результатом работы метода Execute(…) является массив TestResult[] так как тест может быть запущен больше одного раза (например, для нескольких наборов данных).

Реализация по умолчанию крайне проста: она вызывает тест и возвращает его результат:

[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class TestMethodAttribute : Attribute
{
    public string DisplayName { get; private set; }

    public virtual TestResult[] Execute(ITestMethod testMethod)
        => new TestResult[] { testMethod.Invoke(null) };
}

Для реализации своего процесса тестирования необходимо в созданном наследнике TestMethodAttribute переопределить Execute(…) и разместить в нем нужный код, не забывая вызывать testMethod.Invoke(…):

public class MyTestMethodAttribute : TestMethodAttribute
{
    public override TestResult[] Execute(ITestMethod testMethod)
    {
        …
        TestResult result = testMethod.Invoke(…);
        … 
    }     
}

Теперь можно использовать созданный атрибут [MyTestMethod] вместо [TestMethod], изменяя процесс тестирования там, где это необходимо.

Обратите внимание, что если код внутри Execute(…) выбросит исключение, то тест будет считаться проваленным независимо от результата самого теста.

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

Подменяем атрибут теста

Еще одна точка модификации процесса тестирования это TestClassAttribute. Данный атрибут используется чтобы указания классов содержащих тесты. Но он также содержит метод GetTestMethodAttribute(…), который позволяет подменять атрибут [TestMethod] и его наследников. Реализация по умолчанию выглядит следующим образом:

[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class TestClassAttribute : Attribute
{
    public virtual TestMethodAttribute GetTestMethodAttribute(TestMethodAttribute attr)
        => attr;
}

Рассмотрим пример. Предположим, что процесс тестирования в некоторых случаях зависит от текущей ОС и реализован в атрибутах [WindowsTestMethod], [LinuxTestMethod] и [DefaultTestMethod].

Создадим [OsDependentTestMethod], предназначенный только для того, чтобы отмечать тесты, процесс выполнения которых зависит от текущей ОС. Реализация [OsDependentTestClass] будет подменять указанный выше атрибут на один из вариантов с нужным процессом выполнения теста:

public class OsDependentTestMethodAttribute : TestMethodAttribute { }

public class OsDependentTestClassAttribute : TestClassAttribute
{
    public override TestMethodAttribute GetTestMethodAttribute(TestMethodAttribute attr)
    {
        if (attr is not OsDependentTestMethodAttribute)
            return attr;

        if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
            return new WindowsTestMethodAttribute();

        if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
            return new LinuxTestMethodAttribute();

        return new DefaultTestMethodAttribute();
    }
}

Обратите внимание, что подменяется только [OsDependentTestMethod], а в остальных случаях атрибут теста остается без изменений.

Остается только использовать [OsDependentTestClass] и [OsDependentTestMethod] для классов и тестов, которые зависят от ОС.

Параллельное выполнение тестов

Параллельный запуск тестов может значительно сократить общее время тестирования. Для управления этой возможностью на уровне проекта (сборки) существует специальный атрибут:

[assembly: Parallelize(Workers = 0, Scope = ExecutionScope.MethodLevel)]

Параметр Workers задает число тестов, которое можно выполнять параллельно. При этом 0 соотвествует числу ядер процессора на текущем компьютере.

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

  • ExecutionScope.MethodLevel – любые тесты могут быть запущены параллельно.
  • ExecutionScope.ClassdLevel – только тесты из разных классов могут выполняться параллельно. Тесты внутри класса будут выполняться по очереди.

Эти же настройки можно задать используя файл .runsettings, расположенный в папке с проектом:

<?xml version="1.0" encoding="utf-8"?>
<RunSettings>
  <MSTest>
    <Parallelize>
      <Workers>12</Workers>
      <Scope>MethodLevel</Scope>
    </Parallelize>
  </MSTest>
</RunSettings>

Тесты, которые по какой-то причине нельзя запускать параллельно с остальными или друг с другом, нужно отметить при помощи [DoNotParallelize]. Данный атрибут также может использоваться для классов, оказывая действие на все тесты в них.

Тесты на разных платформах

Зачастую создаваемые библиотеки, а иногда и проекты других типов, поддерживают сборку под различные версии .NET. В этом случае целесообразно запускать тесты отдельно для каждой из них. Для этого в файле проекта тестов (.csproj) необходимо заменить тег TargetFramework на TargetFrameworks с указанием нужных версий. Например:

<TargetFrameworks>net472;netcoreapp2.0;net5.0;net6.0</TargetFrameworks>

При этом стоит учесть один момент: по умолчанию версия C# выбирается исходя из версии .NET. Поэтому лучше всего указать её явно при помощи тега LangVersion. Например:

<TargetFrameworks>net472;netcoreapp2.0;net5.0;net6.0</TargetFrameworks>
<LangVersion>10.0</LangVersion>

Добавить комментарий