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

Например, предположим, что вы хотите протестировать алгоритм покупки запаса, но вы не хотите тестировать всю систему. Можно использовать имитационный объект для замены функциональности поиска цены акций, а другой имитационный объект - для проверки того, что трейдер приобрел акции. Тестируемый алгоритм не знает, что он работает на макетных объектах, и можно протестировать алгоритм, изолированный от остальной части системы.
С помощью имитационного объекта можно определить поведение (процесс, известный как stubbing). Например, можно указать, что объект создает предопределенные ответы на запросы. Можно также перехватывать и запоминать сообщения, отправленные из тестируемого компонента в имитационный объект (процесс, известный как шпионаж). Например, можно проверить, что был вызван конкретный метод или задано свойство.
Типичный рабочий процесс тестирования изолированного компонента выглядит следующим образом:
Создайте макеты для зависимых компонентов.
Определите поведение издевательств. Например, определите выходные данные, возвращаемые издевательским методом или свойством при вызове с определенным набором входных данных.
Протестируйте интересующий компонент.
Квалификация взаимодействий между интересующим компонентом и издевательскими компонентами. Например, убедитесь, что макетный метод был вызван с определенными входами, или что свойство установлено.
В этом примере тестируемый компонент представляет собой простой алгоритм дневной торговли. Это часть системы, которую требуется проверить независимо от других компонентов. Алгоритм дневной торговли имеет две зависимости: сервис данных для получения данных о цене акций и брокер для покупки акций.
В файле DataService.m в текущей рабочей папке создайте абстрактный класс, включающий lookupPrice способ.
classdef DataService methods (Abstract,Static) price = lookupPrice(ticker,date) end end
В производственном коде может быть несколько конкретных реализаций DataService класс, такой как BloombergDataService класс. В этом классе используется Toolbox™ Datafeed. Однако, поскольку мы создаем макет 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 в текущей рабочей папке создайте простой алгоритм торговли днями. 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 является реализацией абстрактных методов и свойств интерфейса, заданных суперклассом. Можно также создать макет без суперкласса, в этом случае макет имеет неявный интерфейс. Тестируемый компонент взаимодействует с имитационным объектом, например, вызывая метод имитационного объекта или обращаясь к свойству имитационного объекта. Имитационный объект выполняет предопределенные действия в ответ на эти взаимодействия.
При создании макета также создается связанный объект поведения. Объект поведения определяет те же методы, что и имитационный объект, и управляет имитационным поведением. Используйте объект поведения для определения имитационных действий и определения взаимодействий. Например, используйте его для определения значений, возвращаемых издевательским методом, или убедитесь, что к свойству был получен доступ.
В командной строке создайте имитационный тестовый пример для интерактивного использования. Использование макета в тестовом классе вместо в командной строке представлено ниже в этом примере.
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]