|
| ||||||||||||
| ||||||||||||
|
2010 г.
Архитектура среды тестирования на основе моделей, построенная на базе компонентных технологийКулямин В. В.
4. Пример построения тестаДалее описывается пример использования предложенных решений при построении тестов для простой реализации функциональности банковского счета. Интерфейс тестируемого > public interface Account
{
int getBalance();
int getMaxCredit();
Validator getValidator();
void setValidator(Validator p);
AuditLog getLog();
void setLog(AuditLog log);
int transfer(int sum);
}
Методы Метод Данный счет позволяет использовать специализированный валидатор транзакций, Еще одна функция счета — запись данных о попытках перевода денег в трассу для последующего аудита. При этом вызываются методы интерфейса Модель поведения для счета описана в виде двух независимых компонентов: модели основной функциональности и модели работы с трассировкой переводов. Это позволяет изменять и проверять эти две группы ограничений независимо. Описание основной функциональности выглядит так. public class AccountContract
{
int balance;
int maxCredit;
Account checkedObject;
public void setCheckedObject(Account checkedObject)
{
this.checkedObject = checkedObject;
this.balance = checkedObject.getBalance();
this.maxCredit = checkedObject.getMaxCredit();
}
public boolean possibleTransfer(int sum)
{
if (balance + sum > maxCredit) return true;
else return false;
}
public boolean transferPostcondition(int sum)
{
boolean permission =
checkedObject.getValidator().validateTransfer(checkedObject, sum);
if (Contract.oldBooleanValue(possibleTransfer(sum)) && permission)
return
Contract.assertEqualsInt(Contract.intResult(), sum
, "Result should be equal to the argument")
&& Contract.assertEqualsInt(balance, Contract.oldIntValue(balance)+sum
, "Balance should be increased on the argument")
&& Contract.assertEqualsInt(maxCredit, Contract.oldIntValue(maxCredit)
, "Max credit should not change");
else
return
Contract.assertEqualsInt(Contract.intResult(), 0
, "Result should be 0")
&& Contract.assertEqualsInt(balance, Contract.oldIntValue(balance)
, "Balance should not change")
&& Contract.assertEqualsInt(maxCredit, Contract.oldIntValue(maxCredit)
, "Max credit should not change");
}
public void transferUpdate(int sum)
{
if( possibleTransfer(sum)
&& checkedObject.getValidator().validateTransfer(checkedObject, sum))
balance += sum;
}
}
Здесь показаны постусловие метода Описание требований к работе с трассой для аудита дано ниже. Оно использует свободно распространяемую библиотеку для организации заглушек Mockito, вставляя заглушку для наблюдения за сделанными вызовами между счетом и связанным с ним трассировщиком. В ходе работы заглушка проверяет, что методы трассировщика вызывались в нужном порядке и с нужными аргументами. Поскольку построенная заглушка имеет модельное состояние, в ней также определен метод-синхронизатор этого состояния. Заглушка должна инициализироваться после каждого вызова public class AccountLogSpy
{
int balance;
int maxCredit;
Account checkedObject;
AuditLog logSpy;
public void setCheckedObject(Account checkedObject)
{
this.checkedObject = checkedObject;
this.balance = checkedObject.getBalance();
this.maxCredit = checkedObject.getMaxCredit();
logSpy = Mockito.spy(checkedObject.getLog());
checkedObject.setLog(logSpy);
}
int oldBalance;
boolean wasPossible;
public boolean possibleTransfer(int sum)
{
if (balance + sum > maxCredit) return true;
else return false;
}
public void initSpy(int sum)
{
Mockito.reset(logSpy);
oldBalance = balance;
}
public void transferLogSpy(int sum)
{
boolean permission =
checkedObject.getValidator().validateTransfer(checkedObject, sum);
if (wasPossible && permission)
{
Mockito.verify(logSpy).logKind("SUCCESS");
Mockito.verify(logSpy).logNewBalance(balance);
}
else if (!permission)
Mockito.verify(logSpy).logKind("BANNED");
else
Mockito.verify(logSpy).logKind("IMPROPER");
Mockito.verify(logSpy).logOldBalance(oldBalance);
Mockito.verify(logSpy).logSum(sum);
}
public void transferUpdate(int sum)
{
if( possibleTransfer(sum)
&& checkedObject.getValidator().validateTransfer(checkedObject, sum))
{
wasPossible = true;
balance += sum;
}
else
wasPossible = false;
}
}
Описание модели ситуаций представлено ниже. В ней ситуации классифицируются по четырем характеристикам: корректность перевода, прохождение валидации, знак предшествовавшего значения баланса и знак переводимой суммы. Поскольку определение ситуации зависит от модельного состояния счета и нуждается в синхронизаторе состояния, эта модель наследует модели функциональности, используя повторно определенные в ней элементы кода. public class AccountCoverage extends AccountContract
{
public void transferCoverage(int sum)
{
boolean permission =
checkedObject.getValidator().validateTransfer(checkedObject, sum);
if (possibleTransfer(sum)) Coverage.addDescriptor("Possible transfer");
else Coverage.addDescriptor("Too big sum");
if (permission) Coverage.addDescriptor("Permitted");
else Coverage.addDescriptor("Not permitted");
if(balance == 0) Coverage.addDescriptor("Zero balance");
else if(balance > 0) Coverage.addDescriptor("Positive balance");
else Coverage.addDescriptor("Negative balance");
if(sum == 0) Coverage.addDescriptor("Zero sum");
else if(sum > 0) Coverage.addDescriptor("Positive sum");
else Coverage.addDescriptor("Negative sum");
}
}
Модель теста для счета выглядит следующим образом. @Test public class AccountTest
{
Account account;
boolean permission = true;
@Mock Validator validatorStub;
public AccountTest()
{
MockitoAnnotations.initMocks(this);
Mockito.when(validatorStub.validateTransfer(Mockito.<Account>any()
, Mockito.anyInt())).thenReturn(true);
}
public void setAccount(Account account)
{
this.account = account;
account.setValidator(validatorStub);
}
public Validator getPermitterStub() { return validatorStub; }
@State public int getBalance() { return account.getBalance(); }
@State public boolean getPermission() { return permission; }
@Test
@DataProvider(name = "sumArray")
@Guard(name = "bound")
public void testDeposit(int x)
{
account.transfer(x);
}
@Test
@DataProvider(name = "sumIterator")
public void testWithdraw(int x)
{
account.transfer(-x);
}
@Test
@Guard(name = "bound")
public void testIncrement()
{
account.transfer(1);
}
@Test
public void switchPermission()
{
permission = !permission;
Mockito.when(validatorStub.validateTransfer(Mockito.<Account>any()
, Mockito.anyInt())).thenReturn(permission);
}
public boolean bound()
{
return getBalance() < 5 || !permission;
}
public int[] sumArray = new int[]{1, 2};
public Iterator<Integer> sumIterator()
{
return (Utils.ArrayToTypedList(sumArray)).iterator();
}
}
Состояние теста состоит из двух элементов: текущего значения баланса и значения поля
Наконец, конфигурационный файл для среды Spring, определяющий связи между всеми перечисленными компонентами, выглядит так. <?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
<bean id="accountImpl" class="mbtest.tests.AccountImpl"></bean>
<bean id="accountTest" class="mbtest.tests.AccountTest">
<property name="account" ref="accountImpl"/>
</bean>
<bean id="accountContract" class="mbtest.tests.AccountContract">
<property name="checkedObject" ref="accountImpl"/>
</bean>
<bean id="accountCoverage" class="mbtest.tests.AccountCoverage">
<property name="checkedObject" ref="accountImpl"/>
</bean>
<bean id="accountLogSpy" class="mbtest.tests.AccountLogSpy">
<property name="checkedObject" ref="accountImpl"/>
</bean>
<bean id="accountContractExecutor" class="mbtest.contracts.ContractExecutor">
<property name="postcondition"
value="mbtest.tests.AccountContract.transferPostcondition"/>
<property name="updater" value="mbtest.tests.AccountContract.transferUpdate"/>
<property name="object" ref="accountContract"/>
</bean>
<bean id="accountCoverageExecutor" class="mbtest.coverage.CoverageExecutor">
<property name="coverage"
value="mbtest.tests.AccountCoverage.transferCoverage"/>
<property name="updater" value="mbtest.tests.AccountCoverage.transferUpdate"/>
<property name="object" ref="accountCoverage"/>
</bean>
<bean id="accountSpyExecutor" class="mbtest.contracts.SpyExecutor">
<property name="initialization" value="mbtest.tests.AccountLogSpy.initSpy"/>
<property name="postcondition"
value="mbtest.tests.AccountLogSpy.transferLogSpy"/>
<property name="updater" value="mbtest.tests.AccountLogSpy.transferUpdate"/>
<property name="object" ref="accountLogSpy"/>
</bean>
<aop:config>
<aop:aspect id="accountContractAspect" ref="accountContractExecutor">
<aop:pointcut id="accoutTransfer"
expression="execution(* mbtest.tests.Account.transfer(..))"/>
<aop:around pointcut-ref="accoutTransfer" method="execute"/>
</aop:aspect>
<aop:aspect id="accountCoverageAspect" ref="accountCoverageExecutor">
<aop:pointcut id="accoutCTransfer"
expression="execution(* mbtest.tests.Account.transfer(..))"/>
<aop:around pointcut-ref="accoutCTransfer" method="execute"/>
</aop:aspect>
<aop:aspect id="accountSpyAspect" ref="accountSpyExecutor">
<aop:pointcut id="accoutSTransfer"
expression="execution(* mbtest.tests.Account.transfer(..))"/>
<aop:around pointcut-ref="accoutSTransfer" method="execute"/>
</aop:aspect>
</aop:config>
</beans>
В этой конфигурации указано, как инициализировать объекты всех перечисленных типов, и, кроме того, определена привязка постусловий и синхронизаторов всех моделей к методу Приведенный пример демонстрирует неинвазивность использованного метода построения тестовой системы из заданных компонентов — все эти компоненты ничего не знают друг о друге, кроме типов объектов, от которых они непосредственно зависят. В данной конфигурации модель основной функциональности и модель ситуаций представлены разными объектами, однако, поскольку вторая наследует первой, можно было бы реализовать их при помощи одного и того же компонента, играющего две разные роли. 5. ЗаключениеВ работе представлена компонентная архитектура инструментария для тестирования на основе моделей, построенная на основе компонентных технологий с использованием принципа неинвазивной композиции. Описана реализация предложенного подхода на базе среды Spring, реализующей техники внедрения зависимостей. Кроме того, приведен пример его использования для построения теста, включающего несколько моделей разных аспектов поведения тестируемого компонента. Имеющаяся на настоящий момент реализация описываемого подхода содержит следующие недостатки, которые нужно устранить.
Однако уже сейчас предложенная архитектура демонстрирует свои основные достоинства по сравнению с традиционными «монолитными» инструментами построения тестов — высокую гибкость, возможность совместного использования с разнообразными библиотеками, многочисленными инструментами, предназначенными для работы с компонентами Java (средами разработки, анализаторами кода, отладчиками и т.д.), возможность интеграции в более мощные среды. |
|
CITForum © 1997–2025