Модульное тестирование
Совет
Это содержимое является фрагментом из электронной книги, шаблонов корпоративных приложений с помощью .NET MAUI, доступных в .NET Docs или в виде бесплатного скачиваемого PDF-файла, который можно прочитать в автономном режиме.
Проблемы с мультиплатформенными приложениями похожи на классические и веб-приложения. Мобильные пользователи будут отличаться по устройствам, сетевому подключению, доступности служб и различным другим факторам. Таким образом, многоплатформенные приложения должны тестироваться так, как они будут использоваться в реальном мире для улучшения качества, надежности и производительности. Многие типы тестирования должны выполняться в приложении, включая модульное тестирование, тестирование интеграции и тестирование пользовательского интерфейса. Модульное тестирование является наиболее распространенной формой и важной для создания высококачественных приложений.
Модульный тест принимает небольшую единицу приложения, как правило, метод, изолирует его от остальной части кода и проверяет, работает ли оно должным образом. Его целью является проверка того, что каждая единица функциональных возможностей выполняется должным образом, поэтому ошибки не распространяются по всему приложению. Обнаружение ошибки, в которой оно происходит более эффективно, чем наблюдение за эффектом ошибки косвенно в вторичной точке сбоя.
Модульное тестирование оказывает наибольшее влияние на качество кода, если это неотъемлемая часть рабочего процесса разработки программного обеспечения. Модульные тесты могут выступать в качестве документации по проектированию и функциональным спецификациям для приложения. Как только метод написан, модульные тесты должны быть записаны, которые проверяют поведение метода в ответ на стандартные, границы и неправильные входные данные и проверяют любые явные или неявные предположения, сделанные кодом. Кроме того, при разработке на основе тестов модульные тесты записываются перед кодом. Дополнительные сведения о разработке на основе тестов и его реализации см. в пошаговом руководстве по разработке на основе тестов с помощью обозревателя тестов.
Примечание.
Модульные тесты очень эффективны для регрессии. То есть функциональные возможности, которые использовались для работы, но были нарушены сбойным обновлением.
Модульные тесты обычно используют шаблон упорядочения-act-assert:
Этап | Description |
---|---|
Упорядочить | Инициализирует объекты и задает значение данных, передаваемых в тестируемый метод. |
Действие | Вызывает метод, тестируемый с необходимыми аргументами. |
Assert | Проверяет, работает ли действие метода, выполняемого в тесте, как ожидалось. |
Этот шаблон гарантирует, что модульные тесты доступны для чтения, самоописания и согласованности.
Внедрение зависимостей и модульное тестирование
Одна из мотивов внедрения слабо связанной архитектуры заключается в том, что она упрощает модульное тестирование. Одним из типов, зарегистрированных в службе внедрения зависимостей, является IAppEnvironmentService
интерфейс. В следующем примере кода показана структура этого класса:
public class OrderDetailViewModel : ViewModelBase
{
private IAppEnvironmentService _appEnvironmentService;
public OrderDetailViewModel(
IAppEnvironmentService appEnvironmentService,
IDialogService dialogService, INavigationService navigationService, ISettingsService settingsService)
: base(dialogService, navigationService, settingsService)
{
_appEnvironmentService = appEnvironmentService;
}
}
Класс OrderDetailViewModel
имеет зависимость от IAppEnvironmentService
типа, который контейнер внедрения зависимостей разрешает при создании экземпляра OrderDetailViewModel
объекта. Однако вместо создания IAppEnvironmentService
объекта, который использует реальные серверы, устройства и конфигурации для модульного тестирования OrderDetailViewModel
класса, вместо этого замените IAppEnvironmentService
объект макетом для целей тестов. Объект макета — это объект, имеющий ту же сигнатуру объекта или интерфейса, но создается определенным образом, чтобы помочь в модульном тестировании. Он часто используется с внедрением зависимостей для предоставления конкретных реализаций интерфейсов для тестирования различных сценариев данных и рабочих процессов.
Этот подход позволяет IAppEnvironmentService
объекту передаваться в OrderDetailViewModel
класс во время выполнения, а также в интересах тестируемости, что позволяет передавать класс макета в OrderDetailViewModel
класс во время тестирования. Основное преимущество этого подхода заключается в том, что он позволяет выполнять модульные тесты без необходимости использовать неуправляемые ресурсы, такие как функции платформы среды выполнения, веб-службы или базы данных.
Тестирование приложений MVVM
Тестирование моделей и моделей просмотра из приложений MVVM идентично тестированию любого другого класса и использует те же средства и методы; сюда входят такие функции, как модульное тестирование и макетирование. Однако некоторые шаблоны, типичные для моделей и представлений классов моделей, могут воспользоваться определенными методами модульного тестирования.
Совет
Протестируйте одну вещь с каждым модульным тестом. По мере расширения сложности теста он затрудняет проверку этого теста. Ограничив модульный тест одной проблемой, мы можем убедиться, что наши тесты являются более повторяемыми, изолированными и имеют меньшее время выполнения. Дополнительные рекомендации см . в рекомендациях по модульной тестировании с помощью .NET .
Не заманчиво делать модульное тестирование упражнение более одного аспекта поведения единицы. Это приводит к тестам, которые трудно читать и обновлять. Это также может привести к путанице при интерпретации сбоя.
Приложение eShop с несколькими платформами использует MSTest для выполнения модульного тестирования, которое поддерживает два различных типа модульных тестов:
Тип тестирования | Атрибут | Description |
---|---|---|
TestMethod | TestMethod |
Определяет фактический метод тестирования для запуска. |
DataSource | DataSource |
Тесты, которые соответствуют только определенному набору данных. |
Модульные тесты, включенные в мультиплатформенное приложение eShop, являются TestMethod, поэтому каждый метод модульного теста украшен атрибутом TestMethod
. Помимо MSTest есть несколько других платформ тестирования, включая NUnit и xUnit.
Тестирование асинхронных функций
При реализации шаблона MVVM модели представления обычно вызывают операции со службами, часто асинхронно. Тесты кода, вызывающего эти операции, обычно используют макеты в качестве замены фактических служб. В следующем примере кода демонстрируется тестирование асинхронной функциональности путем передачи макетной службы в модель представления:
[TestMethod]
public async Task OrderPropertyIsNotNullAfterViewModelInitializationTest()
{
// Arrange
var orderService = new OrderMockService();
var orderViewModel = new OrderDetailViewModel(orderService);
// Act
var order = await orderService.GetOrderAsync(1, GlobalSetting.Instance.AuthToken);
await orderViewModel.InitializeAsync(order);
// Assert
Assert.IsNotNull(orderViewModel.Order);
}
Этот модульный тест проверяет, будет ли Order
свойство OrderDetailViewModel
экземпляра иметь значение после InitializeAsync
вызова метода. Метод InitializeAsync
вызывается при переходе к соответствующему представлению модели представления. Дополнительные сведения о навигации см. в этом разделе.
OrderDetailViewModel
При создании экземпляра IOrderService
ожидается, что экземпляр будет указан в качестве аргумента. Однако данные OrderService
извлекаются из веб-службы. Таким образом, OrderMockService
экземпляр, макет версии OrderService
класса, указывается в качестве аргумента конструктора OrderDetailViewModel
. Затем извлекается макет данных, а не обмен данными с веб-службой при вызове метода модели InitializeAsync
представления, который использует IOrderService
операции.
Тестирование реализаций INotifyPropertyChanged
INotifyPropertyChanged
Реализация интерфейса позволяет представлениям реагировать на изменения, поступающие из моделей и моделей представления. Эти изменения не ограничиваются данными, отображаемыми в элементах управления. Они также используются для управления представлением, например состояния модели представления, которые вызывают запуск анимации или отключение элементов управления.
Свойства, которые можно обновить непосредственно с помощью модульного теста, можно проверить, подключив обработчик событий к PropertyChanged
событию и проверив, вызывается ли событие после задания нового значения для свойства. В следующем примере кода показан такой тест:
[TestMethod]
public async Task SettingOrderPropertyShouldRaisePropertyChanged()
{
var invoked = false;
var orderService = new OrderMockService();
var orderViewModel = new OrderDetailViewModel(orderService);
orderViewModel.PropertyChanged += (sender, e) =>
{
if (e.PropertyName.Equals("Order"))
invoked = true;
};
var order = await orderService.GetOrderAsync(1, GlobalSetting.Instance.AuthToken);
await orderViewModel.InitializeAsync(order);
Assert.IsTrue(invoked);
}
Этот модульный тест вызывает InitializeAsync
метод OrderViewModel
класса, который приводит к обновлению его Order
свойства. Модульный тест пройдет, если PropertyChanged
событие вызывается для Order
свойства.
Тестирование взаимодействия на основе сообщений
Просмотр моделей, использующих MessagingCenter
класс для обмена данными между слабо связанных классов, можно модульного тестирования, подписавшись на сообщение, отправляемое тестируемым кодом, как показано в следующем примере кода:
[TestMethod]
public void AddCatalogItemCommandSendsAddProductMessageTest()
{
var messageReceived = false;
var catalogService = new CatalogMockService();
var catalogViewModel = new CatalogViewModel(catalogService);
MessagingCenter.Subscribe<CatalogViewModel, CatalogItem>(
this, MessageKeys.AddProduct, (sender, arg) =>
{
messageReceived = true;
});
catalogViewModel.AddCatalogItemCommand.Execute(null);
Assert.IsTrue(messageReceived);
}
Этот модульный тест проверяет, CatalogViewModel
публикует AddProduct
сообщение в ответ на его AddCatalogItemCommand
выполнение. MessagingCenter
Так как класс поддерживает подписки на многоадресную рассылку сообщений, модульный тест может подписаться на сообщение и выполнить делегат обратного AddProduct
вызова в ответ на получение. Этот делегат обратного вызова, указанный как лямбда-выражение, задает логическое поле, используемое Assert
инструкцией для проверки поведения теста.
Тестирование обработки исключений
Модульные тесты также можно записать, чтобы убедиться, что определенные исключения создаются для недопустимых действий или входных данных, как показано в следующем примере кода:
[TestMethod]
public void InvalidEventNameShouldThrowArgumentExceptionText()
{
var behavior = new MockEventToCommandBehavior
{
EventName = "OnItemTapped"
};
var listView = new ListView();
Assert.Throws<ArgumentException>(() => listView.Behaviors.Add(behavior));
}
Этот модульный тест вызовет исключение, так как элемент ListView
управления не имеет события с именем OnItemTapped
. Этот Assert.Throws<T>
метод является универсальным методом, где T
является тип ожидаемого исключения. Аргумент, переданный методу Assert.Throws<T>
, является лямбда-выражением, которое вызовет исключение. Таким образом, модульный тест будет проходить, если лямбда-выражение вызывает исключение ArgumentException
.
Совет
Избегайте написания модульных тестов, которые проверяют строки сообщения об исключении. Строки сообщений об исключении могут меняться со временем, и поэтому модульные тесты, которые полагаются на их присутствие, считаются хрупкими.
Проверка тестирования
Существует два аспекта для тестирования реализации проверки: проверка правильности реализации любых правил проверки и проверка того, что ValidatableObject<T>
класс выполняется должным образом.
Логика проверки обычно проста для тестирования, так как обычно это автономный процесс, в котором выходные данные зависят от входных данных. Необходимо проверить результаты вызова Validate
метода для каждого свойства, имеющего по крайней мере одно связанное правило проверки, как показано в следующем примере кода:
[TestMethod]
public void CheckValidationPassesWhenBothPropertiesHaveDataTest()
{
var mockViewModel = new MockViewModel();
mockViewModel.Forename.Value = "John";
mockViewModel.Surname.Value = "Smith";
var isValid = mockViewModel.Validate();
Assert.IsTrue(isValid);
}
Этот модульный тест проверяет успешность проверки, если два ValidatableObject<T>
свойства в экземпляре MockViewModel
имеют данные.
А также проверка успешности проверки, модульные тесты проверки также должны проверять значения Value
IsValid
Errors
и свойства каждого ValidatableObject<T>
экземпляра, чтобы убедиться, что класс выполняется должным образом. В следующем примере кода показан модульный тест, который делает это:
[TestMethod]
public void CheckValidationFailsWhenOnlyForenameHasDataTest()
{
var mockViewModel = new MockViewModel();
mockViewModel.Forename.Value = "John";
bool isValid = mockViewModel.Validate();
Assert.IsFalse(isValid);
Assert.IsNotNull(mockViewModel.Forename.Value);
Assert.IsNull(mockViewModel.Surname.Value);
Assert.IsTrue(mockViewModel.Forename.IsValid);
Assert.IsFalse(mockViewModel.Surname.IsValid);
Assert.AreEqual(mockViewModel.Forename.Errors.Count(), 0);
Assert.AreNotEqual(mockViewModel.Surname.Errors.Count(), 0);
}
Этот модульный тест проверяет, что проверка завершается ошибкой, если Surname
свойство объекта MockViewModel
не имеет данных, а Value
IsValid
Errors
свойство и свойство каждого ValidatableObject<T>
экземпляра задано правильно.
Итоги
Модульный тест принимает небольшую единицу приложения, как правило, метод, изолирует его от остальной части кода и проверяет, работает ли оно должным образом. Его целью является проверка того, что каждая единица функциональных возможностей выполняется должным образом, поэтому ошибки не распространяются по всему приложению.
Поведение объекта под тестом можно изолировать, заменив зависимые объекты макетами, которые имитируют поведение зависимых объектов. Это позволяет выполнять модульные тесты, не требуя неуправляемых ресурсов, таких как функции платформы среды выполнения, веб-службы или базы данных
Тестирование моделей и моделей просмотра из приложений MVVM идентично тестированию любых других классов, а также используются те же средства и методы.