# By Jon Kruger. When calculating the total price of an order, add the price of the products in the order, the tax, and the shipping charges. Tax rate in.

## Presentation on theme: "By Jon Kruger. When calculating the total price of an order, add the price of the products in the order, the tax, and the shipping charges. Tax rate in."— Presentation transcript:

by Jon Kruger

When calculating the total price of an order, add the price of the products in the order, the tax, and the shipping charges. Tax rate in Ohio = 7%, Michigan = 6.5%, other states = 0%. Can ship to US only. Shipping charges = \$5 if cost of products is less than \$25, otherwise shipping is free.

public class OrderProcessor { public decimal CalculateTotalPrice(int orderId) { // load order from database var order = Database.GetOrder(orderId); var totalPriceOfAllProducts = order.Products.Sum(p => p.Price); // calculate tax decimal tax = 0; if (order.State == "OH") tax = totalPriceOfAllProducts *.07m; else if (order.State == "MI") tax = totalPriceOfAllProducts *.065m; // calculate shipping decimal shippingCharges = 0; if (totalPriceOfAllProducts < 25) shippingCharges = 5; return totalPriceOfAllProducts + tax + shippingCharges; }

[TestFixture] public class OrderProcessorTests { private decimal _totalPrice; private Order _order; [Test] public void CalculateTotalPrice() { Given_an_order(); When_calculating_the_total_price_of_an_order(); Then_the_total_price_of_the_order_should_be(15.70m); } [TearDown] public void Cleanup() { Database.DeleteOrder(_order); } private void Given_an_order() { _order = new Order { Id = 1, State = "OH", Products = new List { new Product {Price = 10} } }; Database.SaveOrder(_order); } private When_calculating_the_total_price_of_an_order() { _totalPrice = new OrderProcessor().CalculateTotalPrice(1); } private Then_the_total_price_of_the_order_should_be(decimal amount) { _totalPrice.ShouldEqual(amount); }

We have to account for the following scenarios when writing our tests: Tax (51 possibilities) Orders can be shipped to 51 states (50 states + DC) Shipping (3 possibilities) Order total is < 25 Order total is 25 exactly Order total is > 25 Loading Order from the database (1 possibility) Return the sum of products, tax, and shipping (1 possibility) This means that we have 153 different combinations to test!

What if we tested each individual piece of the order total calculating process in isolation? Test tax calculation in each state (51 tests) Test shipping calculation (3 tests) Test that Order can be loaded from the database (1 test) Test that return value is price of products + tax + shipping (1 test) Now were down to 56 test cases from 153 test cases!

public class OrderProcessor { public decimal CalculateTotalPrice(int orderId) { // load order from database var order = Database.GetOrder(orderId); var totalPriceOfAllProducts = order.Products.Sum(p => p.Price); // calculate tax decimal tax = new TaxCalculator().CalculateTax(order); // calculate shipping decimal shippingCharges = new ShippingCalculator().CalculateShipping(order); return totalPriceOfAllProducts + tax + shippingCharges; }

public class OrderProcessor { private readonly TaxCalculator _taxCalculator; private readonly ShippingCalculator _shippingCalculator; public OrderProcessor(TaxCalculator taxCalculator, ShippingCalculator shippingCalculator) { _taxCalculator = taxCalculator; _shippingCalculator = shippingCalculator; } public decimal CalculateTotalPrice(int orderId) { // load order from database var order = Database.GetOrder(orderId); var totalPriceOfAllProducts = order.TotalPriceOfAllProducts; // calculate tax decimal tax = _taxCalculator.CalculateTax(order); // calculate shipping decimal shippingCharges = _shippingCalculator.CalculateShipping(order); return totalPriceOfAllProducts + tax + shippingCharges; } Now maybe I could create test classes that derive from TaxCalculator and ShippingCalculator…

public class OrderProcessor { private readonly ITaxCalculator _taxCalculator; private readonly IShippingCalculator _shippingCalculator; public OrderProcessor(ITaxCalculator taxCalculator, IShippingCalculator shippingCalculator) { _taxCalculator = taxCalculator; _shippingCalculator = shippingCalculator; } public decimal CalculateTotalPrice(int orderId) { // load order from database var order = Database.GetOrder(orderId); var totalPriceOfAllProducts = order.TotalPriceOfAllProducts; // calculate tax decimal tax = _taxCalculator.CalculateTax(order); // calculate shipping decimal shippingCharges = _shippingCalculator.CalculateShipping(order); return totalPriceOfAllProducts + tax + shippingCharges; } Now I dont have to worry about whether those dependencies have virtual methods.

public class OrderProcessor { private readonly ITaxCalculator _taxCalculator; private readonly IShippingCalculator _shippingCalculator; public OrderProcessor(ITaxCalculator taxCalculator, IShippingCalculator shippingCalculator) { _taxCalculator = taxCalculator; _shippingCalculator = shippingCalculator; } public decimal CalculateTotalPrice(int orderId) { // load order from database var order = Database.GetOrder(orderId); var totalPriceOfAllProducts = order.TotalPriceOfAllProducts; // calculate tax decimal tax = _taxCalculator.CalculateTax(order); // calculate shipping decimal shippingCharges = _shippingCalculator.CalculateShipping(order); return totalPriceOfAllProducts + tax + shippingCharges; } I still cant stub out the database access… I cant take a static class in as a constructor parameter!

public class OrderProcessor { private readonly IGetObjectService _getOrderService; private readonly ITaxCalculator _taxCalculator; private readonly IShippingCalculator _shippingCalculator; public OrderProcessor(IGetObjectService getOrderService, ITaxCalculator taxCalculator, IShippingCalculator shippingCalculator) { _getOrderService = getOrderService; _taxCalculator = taxCalculator; _shippingCalculator = shippingCalculator; } public decimal CalculateTotalPrice(int orderId) { // load order from database var order = _getOrderService.Get(orderId); var totalPriceOfAllProducts = order.TotalPriceOfAllProducts; // calculate tax decimal tax = _taxCalculator.CalculateTax(order); // calculate shipping decimal shippingCharges = _shippingCalculator.CalculateShipping(order); return totalPriceOfAllProducts + tax + shippingCharges; } I removed the static class and replaced it with a non-static class hidden behind an interface.

Testable code is code that can we can test using a unit test instead of an integration test Provides a way to substitute fake objects for classes that the class that were testing depends on Consistent results on every test run Manual configuration is not needed before test run Order of tests do not matter Must be able to run only some of the tests Tests must run fast

The Law of Demeter states that a method of an object may only call methods of: 1) The object itself. 2) An argument of the method. 3) Any object created within the method. 4) Any direct properties/fields of the object.

public class OrderDisplayService { private readonly IOrderProcessor _orderProcessor; public OrderDisplayService(IOrderProcessor orderProcessor) { _orderProcessor = orderProcessor; } public void ShowOrderDetails(Order order) { if (!_orderProcessor.UserAuthenticationService.IsAuthenticated) { throw new InvalidOperationException( "not logged in"); } // do more stuff } Stubbing IsAuthenticated in a test would be difficult.

public class OrderDisplayService { private readonly IOrderProcessor _orderProcessor; public OrderDisplayService(IOrderProcessor orderProcessor) { _orderProcessor = orderProcessor; } public void ShowOrderDetails(Order order) { if (!_orderProcessor.IsAuthenticated) { throw new InvalidOperationException( "not logged in"); } // do more stuff } Encapsulate IsAuthenticated inside IOrderProcessor.

public class OrderDisplayService { private IOrderProcessor _orderProcessor; private IUserAuthenticationService _userAuthenticationService; public OrderDisplayService(IOrderProcessor orderProcessor, IUserAuthenticationService userAuthenticationService) { _orderProcessor = orderProcessor; _userAuthenticationService = userAuthenticationService; } public void ShowOrderDetails(Order order) { if (!_userAuthenticationService.IsAuthenticated) { throw new InvalidOperationException( "not logged in"); } // do more stuff }

Dont new up dependencies Dont do real work in constructors Dont expose static anything Dont expose singletons Entity objects should not have external dependencies Follow the Law of Demeter

Download ppt "By Jon Kruger. When calculating the total price of an order, add the price of the products in the order, the tax, and the shipping charges. Tax rate in."

Similar presentations