Создайте макет

При модульном тестировании вы часто заинтересованы в проверке фрагмента полной системы, изолированной от компонентов, от которых она зависит. Чтобы протестировать фрагмент системы, мы можем заменить объекты макет, чтобы заменить зависимые компоненты. Объект mock реализует по крайней мере часть того же интерфейса, что и производственный объект, но часто таким образом, который является простым, эффективным, предсказуемым и управляемым. Когда вы используете платформу для мокинга, тестируемый компонент не знает, является ли его collaborator «реальным» объектом или объектом mock.

Test a component using mocked-up dependencies.

Например, предположим, что вы хотите протестировать алгоритм покупки запаса, но вы не хотите тестировать всю систему. Можно использовать объект mock, чтобы заменить функциональность поиска цены акций, и другой объект mock, чтобы проверить, что трейдер приобрел акции. Алгоритм, который вы тестируете, не знает, что он работает с объектами mock, и можно протестировать алгоритм, изолированный от остальной системы.

Используя объект mock, можно задать поведение (процесс, известный как упрямство). Например, можно задать, что объект создает предопределенные ответы на запросы. Можно также перехватывать и запоминать сообщения, отправляемые из тестируемого компонента объекту mock (процессу, известному как шпионаж). Например, можно проверить, что был вызван конкретный метод или задано свойство.

Типичный рабочий процесс тестирования компонента в изоляции заключается в следующем:

  1. Создайте макеты для зависимых компонентов.

  2. Задайте поведение макетов. Для примера задайте выходы, которую возвраты метод или свойство mocked, когда он вызывается с определенным набором входов.

  3. Протестируйте интересующий компонент.

  4. Определите взаимодействия между интересующим компонентом и высмеиваемыми компонентами. Например, проверьте, что был вызван mocked метод с конкретными входами или что задано свойство.

Зависят от компонентов

В этом примере тестируемый компонент является простым алгоритмом дневной торговли. Это часть системы, которую необходимо протестировать независимо от других компонентов. Алгоритм дневной торговли имеет две зависимости: сервис данных для извлечения данных о цене акций и брокер для покупки акций.

В файл DataService.m в текущей рабочей папке создайте абстрактный класс, который включает в себя lookupPrice способ.

classdef DataService
    methods (Abstract,Static)
        price = lookupPrice(ticker,date)
    end
end

В производственном коде может быть несколько конкретных реализаций DataService класс, такой как BloombergDataService класс. Этот класс использует Datafeed Toolbox™. Однако, поскольку мы создаем макет DataService класс, вам не нужно устанавливать тулбокс, чтобы запустить тесты для торгового алгоритма.

classdef BloombergDataService < DataService
    methods (Static)
        function price = lookupPrice(ticker,date)
            % This method assumes you have installed and configured the
            % Bloomberg software.
            conn = blp;
            data = history(conn,ticker,'LAST_PRICE',date-1,date);
            price = data(end);
            close(conn)
        end
    end
end

В этом примере предположим, что компонент брокера еще не разработан. После того, как он будет реализован, у него будет buy метод, который принимает символ тикера и указанное количество акций для покупки и возвращает код состояния. Макет для компонента брокера использует неявный интерфейс и не выводится из суперкласса.

Тестируемый компонент

В файл trader.m в текущей рабочей папке создайте простой алгоритм дневной торговли. The trader функция принимает как входы объект услуги данных, который ищет цену акции, объект брокера, который определяет, как покупается акция, символ тикера и количество акций для покупки. Если цена со вчерашнего дня меньше цены два дня назад, поручите брокеру купить указанное количество акций.

function trader(dataService,broker,ticker,numShares)
    yesterday = datetime('yesterday');
    priceYesterday = dataService.lookupPrice(ticker,yesterday);
    price2DaysAgo = dataService.lookupPrice(ticker,yesterday-days(1));
    
    if priceYesterday < price2DaysAgo
        broker.buy(ticker,numShares);
    end
end

Макет объектов и объектов поведения

Объект mock является реализацией абстрактных методов и свойств интерфейса, заданных суперклассом. Можно также создать макет без суперкласса, в этом случае макет имеет неявный интерфейс. Тестируемый компонент взаимодействует с объектом mock, например, путем вызова метода mock object или доступа к свойству mock object. Объект mock выполняет предопределенные действия в ответ на эти взаимодействия.

Когда вы создаете макет, вы также создаете связанный объект поведения. Объект поведения определяет те же методы, что и объект mock, и управляет поведением mock. Используйте объект поведения, чтобы задать действия mock и определить взаимодействия. Например, используйте его, чтобы задать значения, которые возвращает mocked метод, или проверьте, что к свойству был получен доступ.

В командной строке создайте тест mock для интерактивного использования. Использование макет в тестовом классе вместо в командной строке представлено позже в этом примере.

import matlab.mock.TestCase
testCase = TestCase.forInteractiveUse;

Создайте заглушку, чтобы определить поведение

Создайте макет для зависимости службы данных и исследуйте методы на нем. Сервисный макет данных возвращает предопределенные значения, заменяя реализацию сервиса, который обеспечивает фактические цены запаса. Поэтому он проявляет упрямое поведение.

[stubDataService,dataServiceBehavior] = createMock(testCase,?DataService);
methods(stubDataService)
Methods for class matlab.mock.classes.DataServiceMock:



Static methods:

lookupPrice  

В DataService класс, lookupPrice метод является абстрактным и статическим. Платформа для мокинга реализует этот метод как конкретный и статический.

Задайте поведение для макета службы данных. Для обозначения тикера "FOO", он возвращает цену вчера как $123 и что угодно до вчерашнего дня $234. Поэтому согласно trader функция, брокер всегда покупает акции "FOO". Для символа тикера "BAR", он возвращает цену вчера как $765 и что угодно до вчерашнего дня является $543. Поэтому брокер никогда не покупает акции "BAR".

import matlab.unittest.constraints.IsLessThan
yesterday = datetime('yesterday');

testCase.assignOutputsWhen(dataServiceBehavior.lookupPrice(...
    "FOO",yesterday),123);
testCase.assignOutputsWhen(dataServiceBehavior.lookupPrice(...
    "FOO",IsLessThan(yesterday)),234);

testCase.assignOutputsWhen(dataServiceBehavior.lookupPrice(...
    "BAR",yesterday),765);
testCase.assignOutputsWhen(dataServiceBehavior.lookupPrice(...
    "BAR",IsLessThan(yesterday)),543);

Теперь можно вызвать издевавшуюся lookupPrice способ.

p1 = stubDataService.lookupPrice("FOO",yesterday)
p2 = stubDataService.lookupPrice("BAR",yesterday-days(5))
p1 =

   123


p2 =

   543

В то время как assignOutputsWhen метод по testCase удобно задавать поведение, функциональности больше, если вы используете AssignOutputs действие. Для получения дополнительной информации см. Раздел «Задание поведения объекта макета»

Создайте шпиона для перехвата сообщений

Создайте макет зависимости брокера и исследуйте методы на нем. Поскольку макет брокера используется для проверки взаимодействий с тестируемым компонентом (trader функция), он показывает шпионское поведение. Макет брокера имеет неявный интерфейс. В то время как buy метод в данный момент не реализован, с ним можно создать макет.

[spyBroker,brokerBehavior] = createMock(testCase,'AddedMethods',{'buy'});
methods(spyBroker)
Methods for class matlab.mock.classes.Mock:

buy  

Вызовите buy метод макета. По умолчанию возвращается пустым.

s1 = spyBroker.buy
s2 = spyBroker.buy("inputs",[13 42])
s1 =

     []


s2 =

     []

Начиная с trader функция не использует код возврата состояния, поведение по умолчанию макет возврата пустым приемлемо. Макет брокера - чистый шпион, и не нужно реализовывать какое-либо упрямое поведение.

Вызов тестируемого компонента

Вызовите trader функция. В сложение к символу тикера и количеству акций, которые нужно купить, trader функция принимает как входы услугу передачи данных и брокер. Вместо передачи фактических объектов сервиса данных и брокеров, передайте в spyBroker и stubDataService макеты.

trader(stubDataService,spyBroker,"FOO",100)
trader(stubDataService,spyBroker,"FOO",75)
trader(stubDataService,spyBroker,"BAR",100)

Проверьте взаимодействие функций

Используйте объект поведения брокера (шпион), чтобы проверить, что trader функция вызывает buy метод, как и ожидалось.

Используйте TestCase.verifyCalled метод для проверки того, что trader функция проинструктировала buy метод покупки 100 акций FOO запас.

import matlab.mock.constraints.WasCalled;
testCase.verifyCalled(brokerBehavior.buy("FOO",100))
Verification passed.

Проверьте, что FOO акции приобретались два раза, независимо от указанного количества акций. В то время как verifyCalled метод удобно задавать поведение, функциональности больше, если использовать WasCalled ограничение. Для примера можно проверить, что издевавшийся метод назывался конкретным количеством раз.

import matlab.unittest.constraints.IsAnything
testCase.verifyThat(brokerBehavior.buy("FOO",IsAnything), ...
    WasCalled('WithCount',2))
Verification passed.

Проверьте, что buy метод не вызывался, запрашивая 100 акций BAR запас.

testCase.verifyNotCalled(brokerBehavior.buy("BAR",100))
Verification passed.

Хотя и trader была вызвана функция с запросом 100 долей BAR запас, заглушка определила вчерашнюю цену для BAR чтобы вернуть более высокое значение, чем за все дни до вчерашнего дня. Поэтому брокер никогда не покупает акции "BAR".

Тестовый класс для trader Функция

Интерактивный тест удобно экспериментировать в командной строке. Однако типично создавать и использовать макеты в рамках тестового класса. В файле в текущей рабочей папке создайте следующий тестовый класс, который включает интерактивную проверку из этого примера.

classdef TraderTest < matlab.mock.TestCase
    methods(Test)
        function buysStockWhenDrops(testCase)
            import matlab.unittest.constraints.IsLessThan
            import matlab.unittest.constraints.IsAnything
            import matlab.mock.constraints.WasCalled
            yesterday = datetime('yesterday');
            
            % Create mocks
            [stubDataService,dataServiceBehavior] = createMock(testCase,...
                ?DataService);
            [spyBroker,brokerBehavior] = createMock(testCase,...
                'AddedMethods',{'buy'});
            
            % Set up behavior
            testCase.assignOutputsWhen(dataServiceBehavior.lookupPrice(...
                "FOO",yesterday),123);
            testCase.assignOutputsWhen(dataServiceBehavior.lookupPrice(...
                "FOO",IsLessThan(yesterday)),234);
            
            % Call function under test
            trader(stubDataService,spyBroker,"FOO",100)
            trader(stubDataService,spyBroker,"FOO",75)
            
            % Verify interactions
            testCase.verifyCalled(brokerBehavior.buy("FOO",100))
            testCase.verifyThat(brokerBehavior.buy("FOO",IsAnything),...
                WasCalled('WithCount',2))
        end
        function doesNotBuyStockWhenIncreases(testCase)
            import matlab.unittest.constraints.IsLessThan
            yesterday = datetime('yesterday');
            
            % Create mocks
            [stubDataService,dataServiceBehavior] = createMock(testCase,...
                ?DataService);
            [spyBroker,brokerBehavior] = createMock(testCase, ...
                'AddedMethods',{'buy'});
            
            % Set up behavior
            testCase.assignOutputsWhen(dataServiceBehavior.lookupPrice(...
                "BAR",yesterday),765);
            testCase.assignOutputsWhen(dataServiceBehavior.lookupPrice(...
                "BAR",IsLessThan(yesterday)),543);
            
            % Call function under test
            trader(stubDataService,spyBroker,"BAR",100)
            
            % Verify interactions
            testCase.verifyNotCalled(brokerBehavior.buy("BAR",100))
        end
    end
end

Запустите тесты и просмотрите таблицу результатов.

results = runtests('TraderTest');
table(results)
Running TraderTest
..
Done TraderTest
__________


ans =

  2×6 table

                      Name                       Passed    Failed    Incomplete    Duration      Details   
    _________________________________________    ______    ______    __________    ________    ____________

    'TraderTest/buysStockWhenDrops'              true      false       false        0.24223    [1×1 struct]
    'TraderTest/doesNotBuyStockWhenIncreases'    true      false       false       0.073614    [1×1 struct]