Posts tagged testing
Testing strategies for business logic
Sep 1st
Let’s talk about testing. How do you test your business logic? I spent some time thinking about it and came up with three different strategies for three different ways of implementing business logic.
CRUD
As you can read here, I think CRUD is OK for small and simple applications. You have to, however, remember what is not supported in this architecture – collections of nested objects.In the previous post I’ve written about few reason why is that. Today I will show you another one, this time related to testing. You test a CRUD application by performing a business action given some fake repository object(s) and verifying (on this fake repository) if proper objects have been created, updated and deleted.
//Arrange var orderRepo = new FakeOrderRepository(); var orderService = new OrderService(orderRepo); //Act orderService.CreateNewOrder(orderData); //Assert var order = orderRepo.Get(id); Assert.IsNotNull(order); Assert.AreEqual(10, order.Value);
That’s it, no rocket science. It would be much more difficult, however, if you allowed relations between objects. In such case you would have to verify also these relations. Moreover, mocking a repository if you support relations would be much harder. You would basically had to provide an in-memory database. If you stick to simple structures, you mock a repository using a single collection and your’re done.
The classic approach to DDD
This approach is basically a compilation of what you can find in the Blue Book and in Jimmy Nilsson’s book. It is Domain-Driven Design with objects mapped to database using an Object-Relational Mapper (remember — NHibernate). These objects have properties for getting their attributes and showing them on the UI. Of course there is no dumb setters. Last but not least, there are also value objects that are used to represent attributes with complex values.
So how do you test such architecture? You take advantage of persistence ignorance of good domain model. Prepare a domain object in state you want to test by invoking normal business methods. Once it is ready, call the method you want to test. Then verify the results by using property getters.
//Arrange
var customer = new Customer("Szymon", "Pobiega");
var product = new Product("Widgets", new Money(10, new Currency("USD")));
var order = new Order(orderId, customer);
order.AddLineItem(3, product);
//Act
order.AddLineItem(1, product);
//Assert
Assert.AreEqual(2, order.LineItems.Count())
Testing value objects is even simpler because of their immutability. All you need is to call a method on it (returning a new instance of a value object) and compare it with expected value.
//Arrange
var usd = new Currency("USD");
var total = new Money(10, usd);
var toSubtract = new Money(2, usd);
//Act
var result = total.Minus(toSubtract);
//Assert
var expected = new Money(8, usd);
Assert.AreEqual(expected, result);
The trick in testing classic DDD is to not depend on your ORM to do anything for you behind the scenes. The good example is many-to-many relation. In NHibernate, if you set value only on one end and persist the entities, both ends will be set magically when you get the object from database next time. But before that, for some time the object model will be inconsistent. Good practice for such relations is have code in domain model that explicitly sets both ends of particular relation.
Another gotcha is preparing the object state. Like I said, you do it by calling normal methods. The downside of this approach is something may go wrong in this phase of test and the tested method will be given different state than expected. My personal pragmatic approach is to do nothing until it hurts. If it starts to cause major problems you can think about adding Assert calls after the object has been prepared to verify if it really is in the expected state.
Event Sourced DDD
One of the reasons I really like Event Sourcing is it doesn’t cause any (known to me) problems when testing. If I was to name the ideal types of code for testing, the first one on the list would be functional code and event sourced entities would be right behind it.
The beauty of testing event sourced objects comes from the fact that state mutations and business logic are separated. You can mutate the state (by applying an event) without any business action. That’s how entities are hydrated from the event stream in the first place. There is also an inherent beauty in definition of such tests
//Arrange var order = new Order(); order.Apply(new ItemAddedEvent(10, productId)); order.Apply(new ItemAddedEvent(5, productId)); order.Apply(new ItemRemovedEvent(2, productId)); //Act order.AddItem(3, productId); //Assert order.UncommittedEvents.Contains(new ItemAddedEvent(3, productId));
Which translates to
Given these events had happened before, when this action is called, I expect these events to be published
Nice, isn’t it?
Summary
Of course you can’t always afford to create a fully-featured event sourced solution. For simple scenarios it’s an overkill. But that’s not an excuse for not having good tests. Every approach to structuring your business logic is testable, you only have to know what constraints you have to place on the code so that testing it is a pleasure, not a pain in the back.
Why do I find Event Sourcing interesting?
Jun 7th
What makes Event Sourcing so interesting? I it is not the free, proven, audit trial, nor the possibility of reincarnating object in any particular state it had in the history. It is also not the great performance of add-only event store. It is testability.
Why Event Sourcing makes things testable? By splitting up logic responsible for two distinct things: calculating new state of the object and applying this state change. Normally, these two operations are mixed in one business logic method like this:
public class Customer
{
public void PlaceOrder(Product product, int quantity)
{
CheckProductAvailability();
CheckCustomerCreditLimit();
CheckSomethingElse();
decimal price = CalculateTotalPrice(product, quantity);
Order o = new Order(product, quantity, price);
_orders.Add(o);
}
//...
}
If you read this carefully, you will see that first 4 lines contain the business logic of state change calculation. The last 2 lines, on the other hand, contain no logic at all. Now, what’s the problem with this code’s testability?
The problem becomes visible, when I want to test how PlaceOrder behaves when adding fifth order. I have to call PlaceOrder four times to prepare state of the object. That is wrong, because my test code invokes a lot of logic during preparation phase. This means that a lot of things can go wrong and nobody likes tests which fail before the actual test code gets executed.
Let’s see if PlaceOrder can be corrected using SOLID principles. Does it have exactly one reason to change? It seems like it does. When order placement logic changes, the method changes, right? Let’s dig deeper. What about the last two lines? Do they change when the rest of method changes? Well, I don’t think so. Looks like they indeed do represent a different concern. So let’s refactor them to a separate method.
public class Customer
{
public void PlaceOrder(Product product, int quantity)
{
CheckProductAvailability();
CheckCustomerCreditLimit();
CheckSomethingElse();
decimal price = CalculateTotalPrice(product, quantity);
ApplyPlaceOrder(product, quantity, price);
}
private void ApplyPlaceOrder(Product product, int quantity, decimal price)
{
Order o = new Order(product, quantity, price);
_orders.Add(o);
}
//...
}
Now it looks better. At least from the have one reason to change principle. But I don’t see this improved testability at all. That is because although we split the responsibilities to separate methods, we left the methods tightly coupled to one another. Lets see what we can do about this.
public class Customer
{
public void PlaceOrder(Product product, int quantity)
{
CheckProductAvailability();
CheckCustomerCreditLimit();
CheckSomethingElse();
decimal price = CalculateTotalPrice(product, quantity);
Apply(new OrderPlacedEvent(product, quantity, price));
}
private void OnOrderPlaced(OrderPlacedEvent @event)
{
Order o = new Order(@event.Product, @event.Quantity, @event.Price);
_orders.Add(o);
}
//...
}
We introduced a concept of event as a way of encapsulating the state change we are about to apply. Now we can represent state changes by a series of events. The other thing we did was adding the Apply method. What is it? The Apply method is the API of Event Sourcing. It is a way of communicating hey, I, the aggregate root, want to apply the state change represented by this event! Apply invokes a method to apply state change. It can do this in a variety of ways: based on convention (like On… convention in this sample), an attribute or any other. The bottom line is, Apply applies the state change and it can be called externally, for example during tested state preparation.
[TestFixture]
public class CustomerTests
{
[Test]
public void When_placing_fifth_order()
{
Product product = new Product();
//Arrange
Customer c = new Customer();
c.Apply(new OrderPlacedEvent(product, 1, 10));
c.Apply(new OrderPlacedEvent(product, 1, 10));
c.Apply(new OrderPlacedEvent(product, 1, 10));
c.Apply(new OrderPlacedEvent(product, 1, 10));
//Act
c.PlaceOrder(product, 1);
//Assert...
}
}
You can see that we invoke no business logic during arrange phase. We only apply predefined, known to be right, state changes. The chance something bad will happen during this phase is minimal, as we certainly tested the OnOrderPlaced method in a different test, didn’t we?
This is what I call well tested code. PlaceOrder method is always tested in isolation, no matter if it is first or fifth order. One cannot overestimate the benefits of a testable codebase. If you happen to play (or work) with Domain-Driven Design, Event Sourcing can be beneficial for you. I see it as the missing piece of puzzle — the model is finally fully testable.



