Equality and the Domain

If you’re test driving your code, and you find yourself writing `assertEquals` you’re making a fundamental statement about your system: that the two objects you’re dealing with are equal (whatever that means). So, testing for equality with `assertEquals` is just. Whether you’re using the `equals` method correctly, however, depends on whether you’ve understood your domain correctly.

After I left a comment, George emailed me with a follow-up to his post with a further example:


In my application, I have two instances of Employee: Tony and Larry. Let’s assume, that in the business context, the definition of equality is defined based only on the yearsOfExperience attribute.


That means, I could do assertEquals( tony, larry ), and if Tony and Larry have the same years of experience, the test will pass.


However, Tony has a different name and a different age than Larry, so the test could potentially be wrong, if what it was supposed to be testing was that it’s the instance of Larry that I’m passing around.

In the above statement of our domain (the business context), we have identified an `Employee` that has a single attribute, that from it’s naming, doesn’t appear to be unique. It’s not an identifier.

The thing is, most of the times we use the term `Employee` it’s because we’re interested in being able to identify a particular Employee, rather than just having a set of employees that we record experience with. So, the example uses language that sits a little uneasy, but more on that in a second.

But, if we are saying that years of experience is all that’s important then of course `equals` would only need to check whether `yearsOfExperience` are equal to determine the equality of two employees. But, the language of the domain is a little misleading. This is what George mentions when he says “if what it was supposed to be testing was that it’s the instance of Larry that I’m passing around”.

Of course, we do hit problems if it’s the actual instance we’re trying to check is the same. But, then our test would be wrong and our statement of intent would be wrong in asserting equals. This would be an example of misunderstanding the domain, as much as anything else.

Domain Driven

Domain Driven Design gives us great terminology to discuss these things (much better than any other thing I’ve read on the subject). It categorises domain objects into two categories

1. Entities. These represent objects in our system where (unique) identity is important in our system, where their lifecycle and continuity are important – their persistence etc.
2. Value Objects. Immutable objects, where it’s attributes are important.

In the example George emailed to me, the code would indicate the `Employee` should be classified as a Value Object. Since, the only thing we know of that we care about is `yearsOfExperience`, and importantly, we make no statements about it’s uniqueness.

But, because we’ve named it `Employee` we’re immediately using language of a familiar domain – that the `Employee` class represents someone that’s important to us – rather than just an attribute holder. On face value, the object is a Value Object.

Chances are though, what we’re actually talking about is an `Entity`. In which case, testing for equality on `yearsOfExperience` is wrong. And it’s wrong within the context of our system – our domain. Instead, we need to identify what we should do to identify the object.

Importantly, our tests are revealing something about our code that we hadn’t previously thought about.

Our tests have revealed that our Employee is probably an Entity, rather than a Value Object. This could be verified by us writing future tests that don’t expect two employees (that share the same `yearsOfExperience`) to be equal. In which case, we would have to introduce some kind of natural key (or other unique identifier) that makes sense to identify the object, and thus determine equality.

By us implicitly using `equals` to make a statement about our objects, we’ve highlighted that we have an Entity that’s being treated as a Value Object.

Of course, tests can also show up where we have a Value Object that’s being held in aspects of another class.

I was working on some code today with Stuart Caborn, another brilliant member of the ThoughtWorks team (and co-author of the testing anti-patterns with George). We were trying to make sense of some code, whilst discussing this (and some other Domain Driven Design stuff). We suddenly noticed something.

We had a lot of Entities in this part of our code, but not so many Value Objects. Or at least, we had some around the rest of the system but they weren’t being used consistently, or widely. Some of our tests were doing something along the lines of

public void testShouldBuildCorrectPaymentFromPaymentSpecification() {  ...  assertEquals("GBP", payment.getAmountCurrency());  assertEquals("10.00", payment.getAmountValue().toString());  assertEquals("Addr1", payment.getBeneficiaryAddress1());  assertEquals("Addr2", payment.getBeneficiaryAddress2());  assertEquals("Addr3", payment.getBeneficiaryAddress3());}

Ok, ignoring any issues with our code as it is. The thing is we are testing equality here. Whether we admit it or not (for now), we are trying to test equality for aspects of our Entity. This smelly test code has revealed that we have some Value Objects that do exist in our domain, it’s just we hadn’t identified them before.

Instead, we should be looking towards writing something like

public void testShouldBuildCorrectPaymentFromPaymentSpecification() {  ...  assertEquals(expectedAmount, payment.getAmount());  assertEquals(expectedAddress, payment.getBeneficiary().getAddress());}

and we’d move some of our other assertions into other tests

public class MoneyTest extends TestCase {  public void testShouldConsiderMoneysWithSameCurrencyAndQuantityEqual() {    assertEquals("GBP", amount.getCurrency());    assertEquals("10.00", payment.getQuantity().toString());  }}

We’ve introduced the value objects into our domain, has made better use of encapsulation, and aligned our code against the Ubiquitous Language. Our code has become more expressive of our model, and provided a deeper model for developers to work with.

Our model has been extended and enriched by test-driving it.

It’s surprising, but it’s amazing how these kinds of insights only come from taking a step back and looking at what gives you pain. And that’s why I love tests and pairing. You’ve gotta love TDDDD (I just made that up, probably an acronym too far :).