Модульное тестирование корпоративных приложений
Примечание.
Эта электронная книга была опубликована весной 2017 года и с тех пор не была обновлена. Есть много в книге, которая остается ценным, но некоторые из материалов устарели.
Мобильные приложения имеют уникальные проблемы, о которых не нужно беспокоиться о классических и веб-приложениях. Мобильные пользователи будут отличаться по устройствам, которые они используют, по сетевому подключению, по доступности служб и ряду других факторов. Таким образом, мобильные приложения должны тестироваться, так как они будут использоваться в реальном мире для повышения их качества, надежности и производительности. Существует множество типов тестирования, которые должны выполняться в приложении, включая модульное тестирование, тестирование интеграции и тестирование пользовательского интерфейса, при этом модульное тестирование является наиболее распространенной формой тестирования.
Модульный тест принимает небольшую единицу приложения, как правило, метод, изолирует его от остальной части кода и проверяет, работает ли оно должным образом. Его целью является проверка, что каждая единица функциональности выполняется должным образом, чтобы ошибки не распространялись по всему приложению. Обнаружение ошибки, в которой происходит более эффективно, что наблюдение за воздействием ошибки косвенно на вторичной точке сбоя.
Модульное тестирование оказывает наибольшее влияние на качество кода, если это неотъемлемая часть рабочего процесса разработки программного обеспечения. Как только метод был написан, модульные тесты должны быть записаны, которые проверяют поведение метода в ответ на стандартные, границы и неправильные варианты входных данных, и что проверка любые явные или неявные предположения, сделанные кодом. Кроме того, при разработке на основе тестов модульные тесты записываются перед кодом. В этом сценарии модульные тесты работают как в документации по проектированию, так и в функциональной спецификации.
Примечание.
Модульные тесты очень эффективны в отношении регрессии, т. е. функциональные возможности, которые использовались для работы, но были нарушены неисправным обновлением.
Модульные тесты обычно используют шаблон упорядочения-act-assert:
- Раздел упорядочивания метода модульного теста инициализирует объекты и задает значение данных, передаваемых в тестируемый метод.
- Раздел действия вызывает метод, тестируемый с необходимыми аргументами.
- В разделе assert проверяется, что действие метода в тестируемом режиме работает должным образом.
Следуя этому шаблону, модульные тесты доступны для чтения и согласованности.
Внедрение зависимостей и модульное тестирование
Одна из мотивов внедрения слабо связанной архитектуры заключается в том, что она упрощает модульное тестирование. Одним из типов, зарегистрированных в Autofac, является OrderService
класс. В следующем примере кода показана структура этого класса:
public class OrderDetailViewModel : ViewModelBase
{
private IOrderService _ordersService;
public OrderDetailViewModel(IOrderService ordersService)
{
_ordersService = ordersService;
}
...
}
Класс OrderDetailViewModel
имеет зависимость от IOrderService
типа, который контейнер разрешает при создании экземпляра OrderDetailViewModel
объекта. Однако вместо того, чтобы создать OrderService
объект для модульного тестирования OrderDetailViewModel
класса, замените OrderService
объект макетом для целей тестов. Рис. 10-1 иллюстрирует эту связь.
Рис. 10-1. Классы, реализующие интерфейс IOrderService
Этот подход позволяет OrderService
передавать объект в OrderDetailViewModel
класс во время выполнения и в интересах тестируемости, что позволяет OrderMockService
классу передаваться в OrderDetailViewModel
класс во время тестирования. Основное преимущество этого подхода заключается в том, что он позволяет выполнять модульные тесты без необходимости использовать неуправляемые ресурсы, такие как веб-службы или базы данных.
Тестирование приложений MVVM
Тестирование моделей и моделей представлений из приложений MVVM идентично тестированию любых других классов, а также те же средства и методы, такие как модульное тестирование и макетирование, можно использовать. Однако существуют некоторые шаблоны, типичные для моделей и представлений классов моделей, которые могут воспользоваться определенными методами модульного тестирования.
Совет
Протестируйте одну вещь с каждым модульным тестом. Не заманчиво делать модульное тестирование упражнение более одного аспекта поведения единицы. Это приводит к тестам, которые трудно читать и обновлять. Это также может привести к путанице при интерпретации сбоя.
Мобильное приложение eShopOnContainers выполняет модульное тестирование, которое поддерживает два различных типа модульных тестов:
- Факты — это тесты, которые всегда являются истинными, которые проверяют инвариантные условия.
- Теории — это тесты, которые являются истинными только для определенного набора данных.
Модульные тесты, включенные в мобильное приложение eShopOnContainers, являются тестами фактов, поэтому каждый метод модульного теста украшен атрибутом [Fact]
.
Примечание.
Тесты xUnit выполняются средством выполнения теста. Чтобы выполнить средство выполнения теста, запустите проект eShopOnContainers.TestRunner для требуемой платформы.
Тестирование асинхронных функций
При реализации шаблона MVVM модели представления обычно вызывают операции со службами, часто асинхронно. Тесты кода, вызывающего эти операции, обычно используют макеты в качестве замены фактических служб. В следующем примере кода демонстрируется тестирование асинхронной функциональности путем передачи макетной службы в модель представления:
[Fact]
public async Task OrderPropertyIsNotNullAfterViewModelInitializationTest()
{
var orderService = new OrderMockService();
var orderViewModel = new OrderDetailViewModel(orderService);
var order = await orderService.GetOrderAsync(1, GlobalSetting.Instance.AuthToken);
await orderViewModel.InitializeAsync(order);
Assert.NotNull(orderViewModel.Order);
}
Этот модульный тест проверка, что Order
свойство экземпляра OrderDetailViewModel
будет иметь значение после InitializeAsync
вызова метода. Метод InitializeAsync
вызывается при переходе к соответствующему представлению модели представления. Дополнительные сведения о навигации см. в этом разделе.
OrderDetailViewModel
При создании экземпляра OrderService
ожидается, что экземпляр будет указан в качестве аргумента. Однако данные OrderService
извлекаются из веб-службы. Таким образом, OrderMockService
экземпляр, являющийся макетной версией OrderService
класса, указывается в качестве аргумента конструктора OrderDetailViewModel
. Затем при вызове IOrderService
метода модели InitializeAsync
представления, вызывающего операции, извлекаются макетные данные, а не обмен данными с веб-службой.
Тестирование реализаций INotifyPropertyChanged
INotifyPropertyChanged
Реализация интерфейса позволяет представлениям реагировать на изменения, поступающие из моделей и моделей представления. Эти изменения не ограничиваются данными, отображаемыми в элементах управления. Они также используются для управления представлением, например состояния модели представления, которые вызывают запуск анимации или отключение элементов управления.
Свойства, которые можно обновить непосредственно с помощью модульного теста, можно проверить, подключив обработчик событий к PropertyChanged
событию и проверка, вызывается ли событие после задания нового значения для свойства. В следующем примере кода показан такой тест:
[Fact]
public async Task SettingOrderPropertyShouldRaisePropertyChanged()
{
bool 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.True(invoked);
}
Этот модульный тест вызывает InitializeAsync
метод OrderViewModel
класса, который приводит к обновлению его Order
свойства. Модульный тест пройдет, если PropertyChanged
событие вызывается для Order
свойства.
Тестирование связи на основе сообщений
Просмотр моделей, использующих MessagingCenter
класс для обмена данными между слабо связанных классов, можно модульного тестирования, подписавшись на сообщение, отправляемое тестируемым кодом, как показано в следующем примере кода:
[Fact]
public void AddCatalogItemCommandSendsAddProductMessageTest()
{
bool messageReceived = false;
var catalogService = new CatalogMockService();
var catalogViewModel = new CatalogViewModel(catalogService);
Xamarin.Forms.MessagingCenter.Subscribe<CatalogViewModel, CatalogItem>(
this, MessageKeys.AddProduct, (sender, arg) =>
{
messageReceived = true;
});
catalogViewModel.AddCatalogItemCommand.Execute(null);
Assert.True(messageReceived);
}
Этот модульный тест проверка, который CatalogViewModel
публикует AddProduct
сообщение в ответ на его AddCatalogItemCommand
выполнение. MessagingCenter
Так как класс поддерживает подписки на многоадресную рассылку сообщений, модульный тест может подписаться на сообщение и выполнить делегат обратного AddProduct
вызова в ответ на получение. Этот делегат обратного вызова, указанный как лямбда-выражение, задает boolean
поле, используемое Assert
инструкцией для проверки поведения теста.
Тестирование обработки исключений
Модульные тесты также можно записать, что проверка, что определенные исключения создаются для недопустимых действий или входных данных, как показано в следующем примере кода:
[Fact]
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
метода для каждого свойства, имеющего по крайней мере одно связанное правило проверки, как показано в следующем примере кода:
[Fact]
public void CheckValidationPassesWhenBothPropertiesHaveDataTest()
{
var mockViewModel = new MockViewModel();
mockViewModel.Forename.Value = "John";
mockViewModel.Surname.Value = "Smith";
bool isValid = mockViewModel.Validate();
Assert.True(isValid);
}
Этот модульный тест проверка, которые проверяются успешно, когда два ValidatableObject<T>
свойства в экземпляре MockViewModel
имеют данные.
Кроме проверка успешной проверки, модульные тесты проверки также должны проверка значения Value
IsValid
Errors
и свойства каждого ValidatableObject<T>
экземпляра, чтобы убедиться, что класс выполняется должным образом. В следующем примере кода показан модульный тест, который делает это:
[Fact]
public void CheckValidationFailsWhenOnlyForenameHasDataTest()
{
var mockViewModel = new MockViewModel();
mockViewModel.Forename.Value = "John";
bool isValid = mockViewModel.Validate();
Assert.False(isValid);
Assert.NotNull(mockViewModel.Forename.Value);
Assert.Null(mockViewModel.Surname.Value);
Assert.True(mockViewModel.Forename.IsValid);
Assert.False(mockViewModel.Surname.IsValid);
Assert.Empty(mockViewModel.Forename.Errors);
Assert.NotEmpty(mockViewModel.Surname.Errors);
}
Этот модульный тест проверка, которые проверка завершается ошибкой, если Surname
свойство объекта MockViewModel
не содержит никаких данных, а Value
IsValid
свойство и Errors
свойство каждого ValidatableObject<T>
экземпляра задано правильно.
Итоги
Модульный тест принимает небольшую единицу приложения, как правило, метод, изолирует его от остальной части кода и проверяет, работает ли оно должным образом. Его целью является проверка, что каждая единица функциональности выполняется должным образом, чтобы ошибки не распространялись по всему приложению.
Поведение объекта под тестом можно изолировать, заменив зависимые объекты макетами, которые имитируют поведение зависимых объектов. Это позволяет выполнять модульные тесты, не требуя неуправляемых ресурсов, таких как веб-службы или базы данных.
Тестирование моделей и моделей просмотра из приложений MVVM идентично тестированию любых других классов, а также используются те же средства и методы.