Модульное тестирование корпоративных приложенийUnit Testing Enterprise Apps

Мобильные приложения имеют уникальные проблемы, которые не должны беспокоиться о приложениях для настольных систем и веб-приложений.Mobile apps have unique problems that desktop and web-based applications don't have to worry about. Мобильные пользователи будут использовать разные устройства, сетевые подключения, доступность служб и ряд других факторов.Mobile users will differ by the devices that they use, by network connectivity, by the availability of services, and a range of other factors. Поэтому необходимо тестировать мобильные приложения, так как они будут использоваться в реальном мире для повышения качества, надежности и производительности.Therefore, mobile apps should be tested as they will be used in the real world to improve their quality, reliability, and performance. Существует много типов тестирования, которые следует выполнить в приложении, включая модульное тестирование, тестирование интеграции и тестирование пользовательского интерфейса, при котором модульное тестирование является наиболее распространенной формой тестирования.There are many types of testing that should be performed on an app, including unit testing, integration testing, and user interface testing, with unit testing being the most common form of testing.

Модульный тест принимает небольшую единицу приложения, обычно метод, изолирует его от оставшейся части кода и проверяет, правильно ли он работает.A unit test takes a small unit of the app, typically a method, isolates it from the remainder of the code, and verifies that it behaves as expected. Его целью является проверка того, что каждая единица функциональности работает должным образом, чтобы ошибки не распространялись в течение всего приложения.Its goal is to check that each unit of functionality performs as expected, so that errors don't propagate throughout the app. Обнаружение ошибки в том, что она происходит, более эффективна, что повлияет на последствия ошибки косвенно на дополнительный момент сбоя.Detecting a bug where it occurs is more efficient that observing the effect of a bug indirectly at a secondary point of failure.

Модульное тестирование оказывает наибольшее воздействие на качество кода, если оно является неотъемлемой частью рабочего процесса разработки программного обеспечения.Unit testing has the greatest effect on code quality when it's an integral part of the software development workflow. Сразу же после написания метода необходимо написать модульные тесты, которые проверяют поведение метода в ответ на стандартные, границы и неправильные варианты входных данных, а также проверяют любые явные или неявные предположения, выполняемые кодом.As soon as a method has been written, unit tests should be written that verify the behavior of the method in response to standard, boundary, and incorrect cases of input data, and that check any explicit or implicit assumptions made by the code. Кроме того, при разработке на основе тестирования модульные тесты записываются перед кодом.Alternatively, with test driven development, unit tests are written before the code. В этом сценарии модульные тесты действуют как документация по проектированию и функциональные спецификации.In this scenario, unit tests act as both design documentation and functional specifications.

Примечание

Модульные тесты очень эффективны по сравнению с регрессией, то есть к функциональным возможностям, которые использовались для работы, но были заблокированы при сбое обновления.Unit tests are very effective against regression – that is, functionality that used to work but has been disturbed by a faulty update.

Модульные тесты обычно используют шаблон компоновки-акт-Assert:Unit tests typically use the arrange-act-assert pattern:

  • Раздел « Размещение » метода модульного теста инициализирует объекты и задает значение данных, передаваемых в тестируемый метод.The arrange section of the unit test method initializes objects and sets the value of the data that is passed to the method under test.
  • В разделе " акт " вызывается тестируемый метод с необходимыми аргументами.The act section invokes the method under test with the required arguments.
  • В разделе Assert проверяется, правильно ли выполняется действие тестируемого метода.The assert section verifies that the action of the method under test behaves as expected.

После этого шаблона гарантируется, что модульные тесты будут доступны для чтения и последовательной совместимости.Following this pattern ensures that unit tests are readable and consistent.

Внедрение зависимостей и модульное тестированиеDependency Injection and Unit Testing

Одной из причин внедрения слабо связанных архитектур является то, что она упрощает модульное тестирование.One of the motivations for adopting a loosely-coupled architecture is that it facilitates unit testing. Один из типов, зарегистрированных с помощью Autofac, OrderService — это класс.One of the types registered with Autofac is the OrderService class. В следующем примере кода показана структура этого класса:The following code example shows an outline of this class:

public class OrderDetailViewModel : ViewModelBase  
{  
    private IOrderService _ordersService;  

    public OrderDetailViewModel(IOrderService ordersService)  
    {  
        _ordersService = ordersService;  
    }  
    ...  
}

Класс зависит от типа, который разрешается контейнером OrderDetailViewModel при создании объекта. IOrderService OrderDetailViewModelThe OrderDetailViewModel class has a dependency on the IOrderService type which the container resolves when it instantiates a OrderDetailViewModel object. Однако вместо того, чтобы создавать OrderService объект для модульного OrderDetailViewModel тестирования класса OrderService , замените объект на макет для целей тестов.However, rather than create an OrderService object to unit test the OrderDetailViewModel class, instead, replace the OrderService object with a mock for the purpose of the tests. Эта связь показана на рис. 10-1.Figure 10-1 illustrates this relationship.

Рис. 10-1. Классы, реализующие интерфейс ИордерсервицеFigure 10-1: Classes that implement the IOrderService interface

Такой подход позволяет OrderService передавать объект OrderDetailViewModel в класс во время выполнения, а в интересах тестирования он позволяет OrderMockService передавать OrderDetailViewModel класс в класс во время тестирования.This approach allows the OrderService object to be passed into the OrderDetailViewModel class at runtime, and in the interests of testability, it allows the OrderMockService class to be passed into the OrderDetailViewModel class at test time. Основное преимущество этого подхода заключается в том, что он позволяет выполнять модульные тесты без использования неудобных ресурсов, таких как веб-службы или базы данных.The main advantage of this approach is that it enables unit tests to be executed without requiring unwieldy resources such as web services, or databases.

Тестирование приложений MVVMTesting MVVM Applications

Тестирование моделей и моделей представлений из приложений MVVM идентично тестированию любых других классов, а также использование одних и тех же средств и методик, как модульное тестирование и макетирование.Testing models and view models from MVVM applications is identical to testing any other classes, and the same tools and techniques – such as unit testing and mocking, can be used. Однако существуют некоторые закономерности, типичные для моделирования и просмотра классов моделей, которые могут воспользоваться преимуществами определенных методов модульного тестирования.However, there are some patterns that are typical to model and view model classes, that can benefit from specific unit testing techniques.

Совет

Протестируйте одну вещь с каждым модульным тестом.Test one thing with each unit test. Не рекомендуется делать модульный тест более чем одним аспектом поведения единицы.Don't be tempted to make a unit test exercise more than one aspect of the unit's behavior. Это ведет к тестам, которые трудно читать и обновлять.Doing so leads to tests that are difficult to read and update. Это также может привести к путанице при интерпретации сбоя.It can also lead to confusion when interpreting a failure.

Мобильное приложение eShopOnContainers использует xUnit для выполнения модульного тестирования, которое поддерживает два различных типа модульных тестов:The eShopOnContainers mobile app uses xUnit to perform unit testing, which supports two different types of unit tests:

  • Факты — это тесты, всегда имеющие значение true, которые проверяют инвариантные условия.Facts are tests that are always true, which test invariant conditions.
  • Теории — это тесты, которые имеют значение только для определенного набора данных.Theories are tests that are only true for a particular set of data.

Модульные тесты, включенные в мобильное приложение eShopOnContainers, — это тесты фактов, поэтому каждый метод модульного [Fact] тестирования снабжен атрибутом.The unit tests included with the eShopOnContainers mobile app are fact tests, and so each unit test method is decorated with the [Fact] attribute.

Примечание

тесты xUnit выполняются средством запуска тестов.xUnit tests are executed by a test runner. Чтобы выполнить тестовое средство, запустите проект eShopOnContainers. сообщение TestRunner для требуемой платформы.To execute the test runner, run the eShopOnContainers.TestRunner project for the required platform.

Тестирование асинхронных функцийTesting Asynchronous Functionality

При реализации шаблона MVVM Просмотр моделей обычно вызывает операции со службами, как правило, асинхронно.When implementing the MVVM pattern, view models usually invoke operations on services, often asynchronously. Тесты для кода, который вызывает эти операции, обычно используют макеты как замены для фактических служб.Tests for code that invokes these operations typically use mocks as replacements for the actual services. В следующем примере кода показано тестирование асинхронных функций путем передачи макета службы в модель представления:The following code example demonstrates testing asynchronous functionality by passing a mock service into a view model:

[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 иметь значение после вызова метода.This unit test checks that the Order property of the OrderDetailViewModel instance will have a value after the InitializeAsync method has been invoked. InitializeAsync Метод вызывается при переходе к соответствующему представлению модели представления.The InitializeAsync method is invoked when the view model's corresponding view is navigated to. Дополнительные сведения о навигации см. в разделе Навигация.For more information about navigation, see Navigation.

OrderService При создании OrderDetailViewModel экземпляра он принимает в качестве аргумента экземпляр.When the OrderDetailViewModel instance is created, it expects an OrderService instance to be specified as an argument. OrderService Однако получает данные из веб-службы.However, the OrderService retrieves data from a web service. Таким образом, OrderService OrderDetailViewModel экземпляр, являющийся макетом класса, указывается в качестве аргумента для конструктора. OrderMockServiceTherefore, an OrderMockService instance, which is a mock version of the OrderService class, is specified as the argument to the OrderDetailViewModel constructor. Затем при вызове InitializeAsync метода модели представления, который IOrderService вызывает операции, извлекаются Макетные данные, а не связь с веб-службой.Then, when the view model's InitializeAsync method is invoked, which invokes IOrderService operations, mock data is retrieved rather than communicating with a web service.

Тестирование реализаций INotifyPropertyChangedTesting INotifyPropertyChanged Implementations

INotifyPropertyChanged Реализация интерфейса позволяет представлениям реагировать на изменения, происходящие из моделей и моделей представления.Implementing the INotifyPropertyChanged interface allows views to react to changes that originate from view models and models. Эти изменения не ограничиваются данными, отображаемыми в элементах управления — они также используются для управления представлением, например для представления состояний моделей, которые приводят к запуску анимации или отключению элементов управления.These changes are not limited to data shown in controls – they are also used to control the view, such as view model states that cause animations to be started or controls to be disabled.

Свойства, которые могут быть обновлены непосредственно модульным тестом, можно проверить, присоединив обработчик событий PropertyChanged к событию и проверив, вызвано ли событие после установки нового значения для свойства.Properties that can be updated directly by the unit test can be tested by attaching an event handler to the PropertyChanged event and checking whether the event is raised after setting a new value for the property. В следующем примере кода показан такой тест:The following code example shows such a test:

[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 свойства.This unit test invokes the InitializeAsync method of the OrderViewModel class, which causes its Order property to be updated. Модульный тест будет пройден PropertyChanged Order при условии, что событие вызывается для свойства.The unit test will pass, provided that the PropertyChanged event is raised for the Order property.

Тестирование связи на основе сообщенийTesting Message-based Communication

Просмотр моделей, использующих MessagingCenter класс для обмена данными между слабо связанными классами, может быть модульным тестом с помощью подписки на сообщение, отправляемое тестируемым кодом, как показано в следующем примере кода:View models that use the MessagingCenter class to communicate between loosely-coupled classes can be unit tested by subscribing to the message being sent by the code under test, as demonstrated in the following code example:

[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 сообщение в ответ на выполнение.This unit test checks that the CatalogViewModel publishes the AddProduct message in response to its AddCatalogItemCommand being executed. Поскольку класс поддерживает многоадресные подписки на сообщения, модульный тест может подписываться AddProduct на сообщение и выполнять делегат обратного вызова в ответ на его получение. MessagingCenterBecause the MessagingCenter class supports multicast message subscriptions, the unit test can subscribe to the AddProduct message and execute a callback delegate in response to receiving it. Этот делегат обратного вызова, указанный в качестве лямбда-выражения boolean , задает поле, используемое Assert инструкцией для проверки поведения теста.This callback delegate, specified as a lambda expression, sets a boolean field that's used by the Assert statement to verify the behavior of the test.

Тестирование обработки исключенийTesting Exception Handling

Можно также написать модульные тесты, проверяющие, что для недопустимых действий или входных данных создаются определенные исключения, как показано в следующем примере кода:Unit tests can also be written that check that specific exceptions are thrown for invalid actions or inputs, as demonstrated in the following code example:

[Fact]  
public void InvalidEventNameShouldThrowArgumentExceptionText()  
{  
    var behavior = new MockEventToCommandBehavior  
    {  
        EventName = "OnItemTapped"  
    };  
    var listView = new ListView();  

    Assert.Throws<ArgumentException>(() => listView.Behaviors.Add(behavior));  
}

Этот модульный тест создаст исключение, так как ListView элемент управления не имеет события с именем. OnItemTappedThis unit test will throw an exception, because the ListView control does not have an event named OnItemTapped. Метод является универсальным методом, где T — тип ожидаемого исключения. Assert.Throws<T>The Assert.Throws<T> method is a generic method where T is the type of the expected exception. Аргумент, передаваемый в Assert.Throws<T> метод, является лямбда-выражением, которое будет вызывать исключение.The argument passed to the Assert.Throws<T> method is a lambda expression that will throw the exception. Таким образом, модульный тест будет передавать при ArgumentExceptionусловии, что лямбда-выражение создает исключение.Therefore, the unit test will pass provided that the lambda expression throws an ArgumentException.

Совет

Избегайте написания модульных тестов, которые проверяют строки сообщений об исключениях.Avoid writing unit tests that examine exception message strings. Строки сообщений об исключениях могут меняться со временем, поэтому модульные тесты, основанные на их присутствии, считаются нестабильным.Exception message strings might change over time, and so unit tests that rely on their presence are regarded as brittle.

Проверка проверкиTesting Validation

Тестирование реализации проверки имеет два аспекта: Проверка правильности реализации правил проверки и проверка того ValidatableObject<T> , что класс работает должным образом.There are two aspects to testing the validation implementation: testing that any validation rules are correctly implemented, and testing that the ValidatableObject<T> class performs as expected.

Логика проверки обычно проста в тестировании, так как обычно это автономный процесс, в котором выходные данные зависят от входных данных.Validation logic is usually simple to test, because it is typically a self-contained process where the output depends on the input. Должны быть тесты для результатов вызова Validate метода для каждого свойства, имеющего по крайней мере одно связанное правило проверки, как показано в следующем примере кода:There should be tests on the results of invoking the Validate method on each property that has at least one associated validation rule, as demonstrated in the following code example:

[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 в экземпляре содержат данные.This unit test checks that validation succeeds when the two ValidatableObject<T> properties in the MockViewModel instance both have data.

Кроме проверки успешности Valueпроверки, модульные тесты проверки должны также проверять значения свойств, IsValidи Errors каждого ValidatableObject<T> экземпляра, чтобы убедиться, что класс работает должным образом.As well as checking that validation succeeds, validation unit tests should also check the values of the Value, IsValid, and Errors property of each ValidatableObject<T> instance, to verify that the class performs as expected. В следующем примере кода показан модульный тест, который делает это:The following code example demonstrates a unit test that does this:

[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);  
}

Этот модульный тест проверяет, что проверка завершается ошибкой, MockViewModel если Surname свойство объекта не содержит данных, Valueа IsValidсвойство, Errors и каждого ValidatableObject<T> экземпляра заданы правильно.This unit test checks that validation fails when the Surname property of the MockViewModel doesn't have any data, and the Value, IsValid, and Errors property of each ValidatableObject<T> instance are correctly set.

СводкаSummary

Модульный тест принимает небольшую единицу приложения, обычно метод, изолирует его от оставшейся части кода и проверяет, правильно ли он работает.A unit test takes a small unit of the app, typically a method, isolates it from the remainder of the code, and verifies that it behaves as expected. Его целью является проверка того, что каждая единица функциональности работает должным образом, чтобы ошибки не распространялись в течение всего приложения.Its goal is to check that each unit of functionality performs as expected, so that errors don't propagate throughout the app.

Поведение тестируемого объекта может быть изолировано путем замены зависимых объектов макетами объектов, имитирующих поведение зависимых объектов.The behavior of an object under test can be isolated by replacing dependent objects with mock objects that simulate the behavior of the dependent objects. Это позволяет выполнять модульные тесты без неудобного использования ресурсов, таких как веб-службы или базы данных.This enables unit tests to be executed without requiring unwieldy resources such as web services, or databases.

Тестирование моделей и моделей представлений из приложений MVVM идентично тестированию любых других классов, и можно использовать одни и те же средства и методики.Testing models and view models from MVVM applications is identical to testing any other classes, and the same tools and techniques can be used.