В моих статьях « Шаблон разработки стратегии » и « Шаблон расширенного проектирования стратегии » я объяснил преимущества применения шаблона разработки стратегии в ваших тестах автоматизации. Некоторые из преимуществ — это более удобный код, инкапсулированная логика алгоритма, легко взаимозаменяемые алгоритмы и менее сложный код. Шаблон разработки стратегии следует принципу Open Closed, который гласит, что «классы должны быть открыты для расширения , но закрыты для модификации ». Другой способ создания открытых классов расширения — использование шаблона проектирования Decorator., В этой публикации я собираюсь реорганизовать примеры кода из ранее упомянутых статей, чтобы сделать их еще более расширяемыми. Используемые стратегии будут «оборачиваться» через декораторы. Шаблон Decorator Design позволяет легко присоединить дополнительные обязанности к объекту динамически. Я считаю, что он может быть активно использован в тестах автоматизации из-за всех его преимуществ.
Определение
Шаблон Design Decorator прилагает дополнительные обязанности к объекту динамически. Декораторы предоставляют гибкую альтернативу подклассам для расширения функциональности.
- Вы можете обернуть компонент любым количеством декораторов.
- Измените поведение его компонента, добавив новую функциональность до и / или после вызова метода для компонента.
- Классы Decorator отражают тип компонентов, которые они украшают.
- Предоставляет альтернативу подклассам для расширения поведения.
Абстрактная диаграмма классов UML
участники
Классы и объекты, участвующие в этом шаблоне:
- Компонент — Определяет интерфейс для объектов, к которым можно динамически добавлять обязанности.
- Декоратор — Декораторы реализуют тот же интерфейс (абстрактный класс), что и компонент, который они собираются декорировать. Декоратор имеет отношение HAS-A с расширяющимся объектом, что означает, что первый имеет переменную экземпляра, которая содержит ссылку на последний.
- ConcreteComponent — объект, который будет динамически улучшен. Он наследует Компонент.
- ConcreteDecorator — Декораторы могут улучшить состояние компонента. Они могут добавлять новые методы. Новое поведение обычно добавляется до или после существующего метода в компоненте.
Шаблон оформления декоратора C # Code
Test’s Test Case
Тестовый пример примеров будет таким же, как и в предыдущих статьях. Основной целью будет покупка различных товаров у Amazon . Кроме того, должны быть проверены цены на последнем этапе процесса покупки — налоги, транспортные расходы, расходы на упаковку подарков и т. Д.
1. Перейдите на страницу товара
2. Нажмите Перейти к оформлению заказа.
3. Войти с существующим клиентом
4. Заполните информацию о доставке
5. Выберите скорость доставки
6. Выберите способ оплаты
7. Подтвердите сводку заказа
В предыдущих статьях подробно объясняется, как автоматизировать весь процесс покупки. Однако, чтобы представить преимущества шаблона проектирования декоратора , потребуется только последний шаг — итоговая проверка заказа. В публикациях о шаблоне разработки стратегии цены на последнем этапе процесса покупки проверяются с помощью различных стратегий проверки, которые реализуют IOrderPurchaseStrategy .
public class PurchaseContext
{
private readonly IOrderValidationStrategy orderValidationStrategy;
public PurchaseContext(IOrderValidationStrategy orderValidationStrategy)
{
this.orderValidationStrategy = orderValidationStrategy;
}
public void PurchaseItem(string itemUrl, string itemPrice, ClientLoginInfo clientLoginInfo, ClientPurchaseInfo clientPurchaseInfo)
{
ItemPage.Instance.Navigate(itemUrl);
ItemPage.Instance.ClickBuyNowButton();
PreviewShoppingCartPage.Instance.ClickProceedToCheckoutButton();
SignInPage.Instance.Login(clientLoginInfo.Email, clientLoginInfo.Password);
ShippingAddressPage.Instance.FillShippingInfo(clientPurchaseInfo);
ShippingAddressPage.Instance.ClickContinueButton();
ShippingPaymentPage.Instance.ClickBottomContinueButton();
ShippingPaymentPage.Instance.ClickTopContinueButton();
this.orderValidationStrategy.ValidateOrderSummary(itemPrice, clientPurchaseInfo);
}
}
Применена улучшенная версия расширенного шаблона проектирования стратегии
public class PurchaseContext
{
private readonly IOrderPurchaseStrategy[] orderpurchaseStrategies;
public PurchaseContext(params IOrderPurchaseStrategy[] orderpurchaseStrategies)
{
this.orderpurchaseStrategies = orderpurchaseStrategies;
}
public void PurchaseItem(string itemUrl, string itemPrice, ClientLoginInfo clientLoginInfo, ClientPurchaseInfo clientPurchaseInfo)
{
this.ValidateClientPurchaseInfo(clientPurchaseInfo);
ItemPage.Instance.Navigate(itemUrl);
ItemPage.Instance.ClickBuyNowButton();
PreviewShoppingCartPage.Instance.ClickProceedToCheckoutButton();
SignInPage.Instance.Login(clientLoginInfo.Email, clientLoginInfo.Password);
ShippingAddressPage.Instance.FillShippingInfo(clientPurchaseInfo);
ShippingAddressPage.Instance.ClickDifferentBillingCheckBox(clientPurchaseInfo);
ShippingAddressPage.Instance.ClickContinueButton();
ShippingPaymentPage.Instance.ClickBottomContinueButton();
ShippingAddressPage.Instance.FillBillingInfo(clientPurchaseInfo);
ShippingAddressPage.Instance.ClickContinueButton();
ShippingPaymentPage.Instance.ClickTopContinueButton();
this.ValidateOrderSummary(itemPrice, clientPurchaseInfo);
}
public void ValidateClientPurchaseInfo(ClientPurchaseInfo clientPurchaseInfo)
{
foreach (var currentStrategy in orderpurchaseStrategies)
{
currentStrategy.ValidateClientPurchaseInfo(clientPurchaseInfo);
}
}
public void ValidateOrderSummary(string itemPrice, ClientPurchaseInfo clientPurchaseInfo)
{
foreach (var currentStrategy in orderpurchaseStrategies)
{
currentStrategy.ValidateOrderSummary(itemPrice, clientPurchaseInfo);
}
}
}
Использование PurchaseContext не так просто, как вы можете видеть из кода ниже.
new PurchaseContext(new SalesTaxOrderPurchaseStrategy(), new VatTaxOrderPurchaseStrategy(), new GiftOrderPurchaseStrategy())
.PurchaseItem(itemUrl, itemPrice, clientLoginInfo, clientPurchaseInfo);
Различное сочетание валидации цен достигается путем итерации инициализированных стратегий. Однако недостатком предоставленного решения является то, что для каждого нового метода в интерфейсе IOrderPurchaseStrategy необходимо создать новый метод с оператором «foreach» в классе PurchaseContext. Кроме того, лично я считаю, что инициализация PurchaseContext в тестовом методе немного не читается.
Если вы не совсем разбираетесь в приведенных выше примерах кода, вы можете найти более подробные объяснения в моих статьях о шаблоне разработки стратегии — «шаблоне разработки стратегии » и « расширенном шаблоне разработки стратегии » .
Одним из решений проблемы инициализации PurchaseContext является создание большего количества классов стратегии, которые сочетают различные варианты поведения, например, VatSalesTaxOrderPurchaseStrategy, SalesTaxGiftOrderPurchaseStrategy, GiftOrderPurchaseStrategy,
NoTaxesOrderPurchaseStrategy и т. Д. Но, как вы видите, это быстро обострилось — типичный пример взрыва класса.
Если вам нужно добавить дополнительные валидаторы, вам нужно будет добавить еще пару классов, чтобы добиться смешивания. Вот где вступает в игру шаблон оформления декоратора . Прикрепленное поведение посредством наследования может быть определено только статически во время компиляции. Однако с помощью композиции декораторы могут расширять компонент во время выполнения.
Специальная диаграмма классов UML
участники
Классы и объекты, участвующие в этом шаблоне:
- OrderPurchaseStrategy (Component) — Определяет интерфейс для всех конкретных стратегий, которые будут проверять различные цены на последнем шаге процесса покупки.
- OrderPurchaseStrategyDecorator (Component Decorator) — Декоратор имеет переменную экземпляра, которая содержит ссылку на OrderPurchaseStrategy . Также содержит другую полезную информацию, которая будет использоваться конкретными декораторами для расчета различных ожидаемых сумм.
- TotalPriceOrderPurchaseStrategy (ConcreteComponent) — это потомок OrderPurchaseStrategy , который используется для проверки общей стоимости заказа.
- VatTaxOrderPurchaseStrategy (ConcreteDecorator) — может расширять стратегии покупки конкретного заказа. Добавляет новую логику для проверки НДС налога с заказа, а также добавляет новый налог к общей цене.
Рефакторинг стратегии покупки для поддержки шаблона дизайна декоратора
Базовый класс для всех конкретных стратегий и их декораторов — OrderPurchaseStrategy .
public abstract class OrderPurchaseStrategy
{
public abstract decimal CalculateTotalPrice();
public abstract void ValidateOrderSummary(decimal totalPrice);
}
Он содержит только два абстрактных метода.
CalculateTotalPrice — Возвращает общую стоимость заказа. Это зависит от применяемых налогов и скидок, поэтому каждая стратегия должна их реализовывать.
ValidateOrderSummary — проверяет все цены на странице сводки заказа — общая стоимость, налоги, скидки и т. Д.
Первым конкретным компонентом в примере является TotalPriceOrderPurchaseStrategy, который проверяет правильность итоговой цены.
public class TotalPriceOrderPurchaseStrategy : OrderPurchaseStrategy
{
private readonly decimal itemsPrice;
public TotalPriceOrderPurchaseStrategy(decimal itemsPrice)
{
this.itemsPrice = itemsPrice;
}
public override decimal CalculateTotalPrice()
{
return itemsPrice;
}
public override void ValidateOrderSummary(decimal totalPrice)
{
PlaceOrderPage.Instance.Validate().OrderTotalPrice(totalPrice.ToString());
}
}
Чтобы динамически добавлять новое поведение во время выполнения, все декораторы должны быть производными от класса OrderPurchaseStrategyDecorator .
public abstract class OrderPurchaseStrategyDecorator : OrderPurchaseStrategy
{
protected readonly OrderPurchaseStrategy orderPurchaseStrategy;
protected readonly ClientPurchaseInfo clientPurchaseInfo;
protected readonly decimal itemsPrice;
public OrderPurchaseStrategyDecorator(OrderPurchaseStrategy orderPurchaseStrategy, decimal itemsPrice, ClientPurchaseInfo clientPurchaseInfo)
{
this.orderPurchaseStrategy = orderPurchaseStrategy;
this.itemsPrice = itemsPrice;
this.clientPurchaseInfo = clientPurchaseInfo;
}
public override decimal CalculateTotalPrice()
{
this.ValidateOrderStrategy();
return this.orderPurchaseStrategy.CalculateTotalPrice();
}
public override void ValidateOrderSummary(decimal totalPrice)
{
this.ValidateOrderStrategy();
this.orderPurchaseStrategy.ValidateOrderSummary(totalPrice);
}
private void ValidateOrderStrategy()
{
if (this.orderPurchaseStrategy == null)
{
throw new Exception("The OrderPurchaseStrategy should be first initialized.");
}
}
}
Этот абстрактный класс содержит пару соответствующих переменных. Наиболее выдающимся является orderPurchaseStrategy, который инициализируется в конструкторе. Он содержит ссылку на объект, который в данный момент расширен. Другие переменные используются для расчета различных ожидаемых сумм.
Если мы хотим добавить логику в вышеприведенную стратегию, например — применение НДС и его проверка. Мы можем использовать VatTaxOrderPurchaseStrategy, который по своей сути является декоратором, способным расширять другие стратегии покупки.
public class VatTaxOrderPurchaseStrategy : OrderPurchaseStrategyDecorator
{
private readonly VatTaxCalculationService vatTaxCalculationService;
private decimal vatTax;
public VatTaxOrderPurchaseStrategy(OrderPurchaseStrategy orderPurchaseStrategy, decimal itemsPrice, ClientPurchaseInfo clientPurchaseInfo)
: base(orderPurchaseStrategy, itemsPrice, clientPurchaseInfo)
{
this.vatTaxCalculationService = new VatTaxCalculationService();
}
public override decimal CalculateTotalPrice()
{
Countries currentCountry = (Countries)Enum.Parse(typeof(Countries), clientPurchaseInfo.BillingInfo.Country);
this.vatTax = this.vatTaxCalculationService.Calculate(this.itemsPrice, currentCountry);
return this.orderPurchaseStrategy.CalculateTotalPrice() + this.vatTax;
}
public override void ValidateOrderSummary(decimal totalPrice)
{
base.orderPurchaseStrategy.ValidateOrderSummary(totalPrice);
PlaceOrderPage.Instance.Validate().EstimatedTaxPrice(vatTax.ToString());
}
}
VatTaxOrderPurchaseStrategy является потомком OrderPurchaseStrategyDecorator . Кроме того, он переопределяет свои методы. Интересно то, что общая цена рассчитывается с помощью метода рекурсии. Сначала общая сумма определяется конкретным компонентом (стратегия покупки заказа), а затем к нему добавляется рассчитанный налог на добавленную стоимость.
Тот же метод рекурсии используется для проверки пользовательского интерфейса сводки заказа. Прежде всего, будут выполняться методы ValidateOrderSummary всех расширенных стратегий, после чего проверяется налог на добавленную стоимость.
Налог с продаж можно проверить через аналогичного декоратора.
public class SalesTaxOrderPurchaseStrategy : OrderPurchaseStrategyDecorator
{
private readonly SalesTaxCalculationService salesTaxCalculationService;
private decimal salesTax;
public SalesTaxOrderPurchaseStrategy(OrderPurchaseStrategy orderPurchaseStrategy, decimal itemsPrice, ClientPurchaseInfo clientPurchaseInfo)
: base(orderPurchaseStrategy, itemsPrice, clientPurchaseInfo)
{
this.salesTaxCalculationService = new SalesTaxCalculationService();
}
public SalesTaxCalculationService SalesTaxCalculationService { get; set; }
public override decimal CalculateTotalPrice()
{
States currentState = (States)Enum.Parse(typeof(States), clientPurchaseInfo.ShippingInfo.State);
this.salesTax = this.salesTaxCalculationService.Calculate(this.itemsPrice, currentState, clientPurchaseInfo.ShippingInfo.Zip);
return this.orderPurchaseStrategy.CalculateTotalPrice() + this.salesTax;
}
public override void ValidateOrderSummary(decimal totalPrice)
{
base.orderPurchaseStrategy.ValidateOrderSummary(totalPrice);
PlaceOrderPage.Instance.Validate().EstimatedTaxPrice(salesTax.ToString());
}
}
Единственная разница между последним и первым заключается в том, как определяется налог.
Использование декорированных стратегий PurchaseContext
public class PurchaseContext
{
private readonly OrderPurchaseStrategy orderPurchaseStrategy;
public PurchaseContext(OrderPurchaseStrategy orderPurchaseStrategy)
{
this.orderPurchaseStrategy = orderPurchaseStrategy;
}
public void PurchaseItem(string itemUrl, string itemPrice, ClientLoginInfo clientLoginInfo, ClientPurchaseInfo clientPurchaseInfo)
{
ItemPage.Instance.Navigate(itemUrl);
ItemPage.Instance.ClickBuyNowButton();
PreviewShoppingCartPage.Instance.ClickProceedToCheckoutButton();
SignInPage.Instance.Login(clientLoginInfo.Email, clientLoginInfo.Password);
ShippingAddressPage.Instance.FillShippingInfo(clientPurchaseInfo);
ShippingAddressPage.Instance.ClickDifferentBillingCheckBox(clientPurchaseInfo);
ShippingAddressPage.Instance.ClickContinueButton();
ShippingPaymentPage.Instance.ClickBottomContinueButton();
ShippingAddressPage.Instance.FillBillingInfo(clientPurchaseInfo);
ShippingAddressPage.Instance.ClickContinueButton();
ShippingPaymentPage.Instance.ClickTopContinueButton();
decimal expectedTotalPrice = this.orderPurchaseStrategy.CalculateTotalPrice();
this.orderPurchaseStrategy.ValidateOrderSummary(expectedTotalPrice);
}
}
Следующий код теперь отсутствует в улучшенной версии.
public void ValidateClientPurchaseInfo(ClientPurchaseInfo clientPurchaseInfo)
{
foreach (var currentStrategy in orderpurchaseStrategies)
{
currentStrategy.ValidateClientPurchaseInfo(clientPurchaseInfo);
}
}
public void ValidateOrderSummary(string itemPrice, ClientPurchaseInfo clientPurchaseInfo)
{
foreach (var currentStrategy in orderpurchaseStrategies)
{
currentStrategy.ValidateOrderSummary(itemPrice, clientPurchaseInfo);
}
}
Теперь PurchaseContext содержит только одну ссылку на OrderPurchaseStrategy и использует ее для проверки общей суммы и всех других цен на странице сводки заказа.
Использование шаблона Designrator в тестах
[TestClass]
public class AmazonPurchase_DecoratedStrategies_Tests
{
[TestInitialize]
public void SetupTest()
{
Driver.StartBrowser();
}
[TestCleanup]
public void TeardownTest()
{
Driver.StopBrowser();
}
[TestMethod]
public void Purchase_SeleniumTestingToolsCookbook_DecoratedStrategies()
{
string itemUrl = "/Selenium-Testing-Cookbook-Gundecha-Unmesh/dp/1849515743";
decimal itemPrice = 40.49m;
var shippingInfo = new ClientAddressInfo()
{
FullName = "John Smith",
Country = "United States",
Address1 = "950 Avenue of the Americas",
State = "Texas",
City = "Houston",
Zip = "77001",
Phone = "00164644885569"
};
var billingInfo = new ClientAddressInfo()
{
FullName = "Anton Angelov",
Country = "Bulgaria",
Address1 = "950 Avenue of the Americas",
City = "Sofia",
Zip = "1672",
Phone = "0894464647"
};
ClientPurchaseInfo clientPurchaseInfo = new ClientPurchaseInfo(billingInfo, shippingInfo)
{
GiftWrapping = GiftWrappingStyles.Fancy
};
ClientLoginInfo clientLoginInfo = new ClientLoginInfo()
{
Email = "[email protected]",
Password = "ASDFG_12345"
};
OrderPurchaseStrategy orderPurchaseStrategy = new TotalPriceOrderPurchaseStrategy(itemPrice);
orderPurchaseStrategy = new SalesTaxOrderPurchaseStrategy(orderPurchaseStrategy, itemPrice, clientPurchaseInfo);
orderPurchaseStrategy = new VatTaxOrderPurchaseStrategy(orderPurchaseStrategy, itemPrice, clientPurchaseInfo);
new PurchaseContext(orderPurchaseStrategy).PurchaseItem(itemUrl, itemPrice.ToString(), clientLoginInfo, clientPurchaseInfo);
}
}
Наиболее заметная часть вышеприведенного кода — это то, как стратегии покупки заказа оформлены и использованы в PurchaseContext.
OrderPurchaseStrategy orderPurchaseStrategy = new TotalPriceOrderPurchaseStrategy(itemPrice);
orderPurchaseStrategy = new SalesTaxOrderPurchaseStrategy(orderPurchaseStrategy, itemPrice, clientPurchaseInfo);
orderPurchaseStrategy = new VatTaxOrderPurchaseStrategy(orderPurchaseStrategy, itemPrice, clientPurchaseInfo);
new PurchaseContext(orderPurchaseStrategy).PurchaseItem(itemUrl, itemPrice.ToString(), clientLoginInfo, clientPurchaseInfo);
Сначала создается экземпляр TotalPriceOrderPurchaseStrategy . Затем он передается в конструктор SalesTaxOrderPurchaseStrategy, таким образом он расширяется, и налог с продаж будет добавлен к общей цене. То же самое сделано для стратегии налога с продаж; инициализируется новый декоратор VatTaxOrderPurchaseStrategy . Наконец, общая цена будет равна цене товара плюс налог с продаж плюс налог с НДС.
Плюсы и минусы шаблона оформления декоратора
Cons
-
Декораторы могут привести ко многим маленьким объектам, и чрезмерное использование может быть сложным.
-
Может усложнить процесс создания экземпляра компонента, поскольку вам нужно не только создать экземпляр компонента, но и обернуть его в некоторых декораторах.
-
Может оказаться сложным, чтобы декораторы отслеживали другие декораторы, потому что оглядываясь назад на несколько слоев цепочки декораторов, начинаете выталкивать шаблон декоратора за пределы его фактического намерения.
-
Может вызвать проблемы, если клиент сильно зависит от конкретного типа компонентов.
Pros
-
Обеспечить гибкую альтернативу подклассам для расширения функциональности.
-
Разрешить изменение поведения во время выполнения, а не возвращаться в существующий код и вносить изменения.
-
Помогите решить проблему взрыва класса.
-
Поддержите Открытый Закрытый Принцип .
Исходный код
Вы можете скачать полный исходный код шаблона разработки расширенной стратегии из моего репозитория Github .
Если вам понравились мои публикации, не стесняйтесь ПОДПИСАТЬСЯ — http://automatetheplanet.com/newsletter/