При автоматизации E2E тестирования часто приходится писать много тестов для проверки определённого поведения, например, валидация числового инпута. Один из способов — дублировать метод и изменить значения параметров, но при большом количестве тестовых данных дублирующего кода может быть очень много, а еще такие тесты сложно поддерживать. Но есть способ проще, его идея заключается в создании параметризованного теста таким образом, чтобы один метод теста мог быть использован для выполнения N тестов со всеми тестовыми данными.

Введение

Когда речь идет об автоматизированном тестировании на C#, существует несколько тестовых фреймворков, таких как NUnit, xUnit.net и MSTest. Каждый из них имеет свои преимущества и недостатки, но NUnit и xUnit.net все же имеют огромное преимущество перед MSTest. Хотя MSTest является стандартным фреймворком тестирования, поставляемым с Visual Studio, он не был предпочтительным для автоматизации тестирования до выхода MSTest 2.

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

Параметризация с атрибутом [DataRow]

Атрибут [DataRow] позволяет задавать значения параметра теста, при этом можно установить более одного атрибута [DataRow]. Для преобразования обычного теста в параметризованный достаточно заменить атрибут [TestMethod] на [DataTestMethod] и передать необходимые параметры для теста в [DataRow] атрибут.

В примере ниже рассмотрим абстрактную валидацию числового инпута, передавая первым аргументом само значение, а вторым — соответствующее сообщение об ошибке в тултипе:

[DataTestMethod]
[DataRow(NumberValues.NotNumberValue, Tooltips.NotNumber)]
[DataRow(NumberValues.NotInAcceptableRangePositive, Tooltips.AllowedRange)]
[DataRow(NumberValues.NotInAcceptableRangeNegative, Tooltips.AllowedRange)]
[DataRow(NumberValues.ErrorInExponentialNumberNotation, Tooltips.ErrorInExponentialNumberNotation)]
[DataRow(NumberValues.OnlyOneSeparatorIsAllowed, Tooltips.OnlyOneSeparatorIsAllowed)]
public void NumberInputValidationTooltipShouldExist(string inputValue, string tooltipText)
{
    // Act
    SomeNumberInput
        .EnterValue(inputValue)
        .GetValidatorTooltip(out var currentTooltipText);

    // Assert
    Assert.AreEqual(tooltipText, currentTooltipText);
}

Обратите внимание, что MSTests 2 поддерживает ключевое слово params, поэтому его можно использовать для определения массивов:

[DataTestMethod]
[DataRow(NumberValues.NotNumberValue, new [] { "a", "b" })] // явное определение массива
[DataRow(NumberValues.NotNumberValue, "a", "b")]            // массив будет создан автоматически
public void NumberInputValidationTooltipShouldExist(string inputValue, params string[] b)
{
    // тело теста
}

Параметризация с атрибутом [DynamicData]

Атрибут [DynamicData] используется, когда в качестве параметров передаются неконстантные значения или сложные объекты. Этот атрибут позволяет получить значения параметров из метода или свойства, при этом каждая строка соответствует значениям теста. Метод или свойство должны возвращать IEnumerable<object[]>. Такой подход также позволяет избежать дублирования кода с [DataRow] атрибутами, если один и тот же набор используется в множестве тестов.

В примере ниже рассмотрим тестирование ролевой модели с разным набором пермишенов с получением параметров (группа прав и их тип) из метода и свойства:

[DataTestMethod]
// Получение параметров из метода
[DynamicData(nameof(GetSystemRolePermissions), DynamicDataSourceType.Method)]
// Получение параметров из свойства
[DynamicData(nameof(SystemRolePermissions), DynamicDataSourceType.Property)]
public void IsSystemPermissionSetCorrectlyAfterCreate(string permissionGroupName, string permissionName)
{
    // Act
    RolesPage
        .OpenCreateSystemRoleModal()
        .SetRoleName(RoleName)
        .SetPermission(permissionGroupName, permissionName)
        .SaveRole()
        .SelectRoleByName(RoleName)
        .IsPermissionSet(permissionGroupName, permissionName, out var isPermissionSet);

    // Assert
    Assert.IsTrue(isPermissionSet);
}

public static IEnumerable<object[]> GetSystemRolePermissions()
{
    yield return new object[] { SystemPermissionGroupNames.ProjectCreation, PermissionType.Allowed };
    yield return new object[] { SystemPermissionGroupNames.Workflows, PermissionType.Edit };
    yield return new object[] { SystemPermissionGroupNames.Workflows, PermissionType.Read };
    yield return new object[] { SystemPermissionGroupNames.ObjectModel, PermissionType.Edit };
    // много входных данных с разным набором пермишенов
}

public static IEnumerable<object[]> SystemRolePermissions
{
    get 
    {
        yield return new object[] { SystemPermissionGroupNames.ProjectCreation, PermissionType.Allowed };
        yield return new object[] { SystemPermissionGroupNames.Workflows, PermissionType.Edit };
        yield return new object[] { SystemPermissionGroupNames.Workflows, PermissionType.Read };
        yield return new object[] { SystemPermissionGroupNames.ObjectModel, PermissionType.Edit };
        // много входных данных с разным набором пермишенов
    }
}

Параметризация с кастомным [DataSource] атрибутом

Если два предыдущих атрибута не подходят, можно создать кастомный атрибут, который должен реализовывать интерфейс ITestDataSource. Этот интерфейс имеет два метода: GetData и GetDisplayName. GetData возвращает строки данных. GetDisplayName возвращает имя теста для строки данных. Это имя будет доступно в консоли или Test Explorer.

Пример класса с кастомным атрибутом:

public class CustomDataSource : Attribute, ITestDataSource
{
    public IEnumerable<object[]> GetData(MethodInfo methodInfo)
    {
        yield return new object[] { SystemPermissionGroupNames.ProjectCreation, PermissionType.Allowed };
        yield return new object[] { SystemPermissionGroupNames.Workflows, PermissionType.Edit };
        yield return new object[] { SystemPermissionGroupNames.Workflows, PermissionType.Read };
        yield return new object[] { SystemPermissionGroupNames.ObjectModel, PermissionType.Edit };
    }

    public string GetDisplayName(MethodInfo methodInfo, object[] data)
    {
        return string.Format("DynamicDataTestMethod {0} with {1} parameters", methodInfo.Name, data.Length);
    }
}

Пример использования атрибута в тесте:

[DataTestMethod]
[CustomDataSource]
public void IsSystemPermissionSetCorrectlyAfterCreate(string permissionGroupName, string permissionName)
{
    // тело теста
}

Заключение

Использование [DataRow] и [DynamicData] атрибутов позволит создавать параметризованные тесты в MSTest 2 при разработке тестов на основе данных. Их должно быть достаточно для большинства случаев. Если встроенных атрибутов недостаточно - они могут быть расширены при разработке собственных [DataSource] атрибутов для создания источника данных. Однако если перед вами стоит задача внедрить параллельное выполнение Data Driven тестов, то MSTest 2 по прежнему уступает другим фреймворкам. 

Комментарии (0)