При модульном тестировании вы часто заинтересованы в проверке фрагмента полной системы, изолированной от компонентов, от которых она зависит. Чтобы протестировать фрагмент системы, мы можем заменить объекты макет, чтобы заменить зависимые компоненты. Объект mock реализует по крайней мере часть того же интерфейса, что и производственный объект, но часто таким образом, который является простым, эффективным, предсказуемым и управляемым. Когда вы используете платформу для мокинга, тестируемый компонент не знает, является ли его collaborator «реальным» объектом или объектом mock.
Например, предположим, что вы хотите протестировать алгоритм покупки запаса, но вы не хотите тестировать всю систему. Можно использовать объект mock, чтобы заменить функциональность поиска цены акций, и другой объект mock, чтобы проверить, что трейдер приобрел акции. Алгоритм, который вы тестируете, не знает, что он работает с объектами mock, и можно протестировать алгоритм, изолированный от остальной системы.
Используя объект mock, можно задать поведение (процесс, известный как упрямство). Например, можно задать, что объект создает предопределенные ответы на запросы. Можно также перехватывать и запоминать сообщения, отправляемые из тестируемого компонента объекту mock (процессу, известному как шпионаж). Например, можно проверить, что был вызван конкретный метод или задано свойство.
Типичный рабочий процесс тестирования компонента в изоляции заключается в следующем:
Создайте макеты для зависимых компонентов.
Задайте поведение макетов. Для примера задайте выходы, которую возвраты метод или свойство mocked, когда он вызывается с определенным набором входов.
Протестируйте интересующий компонент.
Определите взаимодействия между интересующим компонентом и высмеиваемыми компонентами. Например, проверьте, что был вызван 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]