Beyond Traditional Acceptance Tests

Pesticide: A library to write Domain-Driven Tests

Uberto Barbini
Javarevisited
12 min readJun 4, 2020

--

Ladybug — a natural pesticide. https://pixabay.com/users/cocoparisienne-127419/

I am sorry about it, but I neglected this blog recently, the reason is that I’m currently trying to write a book explaining how to write complete applications in a functional way with Kotlin. This is taking a ridiculous amount of time because I need to explain functional programming principles while also writing a realistic web application (including database, progressive enhancement, auth-auth. etc.). Well, I hope it will be worth the effort. If you are interested you can sign-up for updates here.

As an aside, while working on the book, I have also created an Open Source library to write Domain-Driven Tests called Pesticide.

I did a presentation for the London Java Community explaining what is a DDT and how to use Pesticide.

It seems only fair that I will talk about this also on my own blog. Let’s start with an introduction.

Domain-Driven Tests

The most common automated tests are the so-called Unit-Tests. They are very useful but they work on a single small unit — hence the name — if we want to verify in an automatic way that our application works as a whole we need to write a test with a different level of granularity.

AcceptanceTests are a way to test our full application end-to-end in order to be sure to be on the right track. The traditional way to do that is recording user interactions with our system and replaying them back every time we want to verify that our system is still correct.

Unfortunately, writing tests in this way will make them very hard to understand, because the goal is hidden beyond layers of non-essential details about the user interaction.

Nat Pryce (of GOOS fame) invented the Domain-Driven style of tests when he got tired of tests written in a kind of “click here and then click there” way.
They are also influenced by Serenity and the Screenplay pattern by Anthony Marcano.

The name comes from the concern that tests should be written using business domain terms. DDT is also an apt name since they are quite efficient in killing bugs (like the pesticide).

So where is the innovation? The idea is to have a single interface (the interpreter) for two or more representations (protocols) of our application — for example, DomainOnly and Http. This forces us to define a common language that stays independent of the details of each protocol.

We also aim to use the same terms both in our tests and in the conversation with the business people. In this way, we can facilitate communication between people working on the software and the business domain experts. This is known as the ubiquitous language.

After a while, writing DDTs we discovered patterns that helped us to keep the code clean and other things that didn’t work very well. Ultimately, I decided to distill this knowledge and write a library in a more general and polished way and release it as an open-source.

Pesticide is a library to describe our requirements as stories composed by a list of interactions between actors (domain personas) and one or more interpreters of our system.

Running the same test using different implementations of the interpreter we get these benefits:

  • Be confident that the functionality works both end-to-end and in the in-memory domain.
  • Document our feature using a language close to the business.
  • Removing UI or technical details from tests.
  • Make sure there is no business logic in the infrastructure layer and there are no infrastructure details in the business logic.

Let’s see now how to write a test with Pesticide.

There are several examples in the GitHub pesticide-example project. We will look at the PetShopDDT example. Let’s imagine we need to write the RESTful API for a pet shop.

My recommendation is to start writing the DDT directly from the user stories before start coding the application.

Our story will say something like: “As a potential customer, I want to check the price of a pet, in order to buy it.”

Phase 1 — write the DDT

To make this compile we already have to define the interpreter interface for our domain. Something like this:

You can define all the methods you need here but there are some simple rules:

  • It has to be an interface, so you can have multiple implementations, one for each protocol you want to use.
  • It has to use only domain concepts, stuff like Json, Http Status, Buttons, etc. shouldn’t be present here.
  • The methods should reflect some atomic user interaction, either a question (“ask pet price”) or an action (“buy pet”).

Exact names are not important now because we will change them later as we proceed.

We now have to define the list of protocols on which to run the tests. There are already four defined in Pesticide but you can define your own.

For example, here we want to useDomainOnly and HttpRest protocols. This means we need to create two implementations ofPetShopInterpreter and put them in a collection to be used by our test.

At this point, we can only define the protocol and the prepare method for each, leaving all the methods implementations as a TODO():

To continue we need to define at least an actor. Actors represent the user of the system inside the DDT.

Let’s start defining a customer of our shop.

A few things worth noting here:

  • The actor class must have a field called name. This name will work as a reference for the actor, so we cannot have two actors with the same name in the same test.
  • The actor must inherit from DdtActor or DdtActorWithContext with the correct DomainInterpreter.
  • A method in the actor should be created using the step function. This is important because the method will become a dynamic test.
  • All the `$` sign in the method name will be replaced with the step parameters to create the test names. In this way, the tests can indicate the exact context they are testing.
  • The main responsibility of the actor’s step is to keep all the expectations (or assertions) together but hidden so that they will not clutter the actual test.

Now we can put the first test together:

A few notes on this code:

  • The test class need to inherit from DomainDrivenTest and specify which interpreters should run (allPetShopInterpreters).
  • The actor(s) can be created with the NamedActor delegator.
  • The single tests need to be marked with DDT annotation or TestFactory, since they will generate multiple tests.
  • Each test is generated with a ddtScenario which takes care of generating all the tests for Junit5.
  • The test DSL is setting … atRise play(steps) where the steps are the actors' interactions and setting part is optional to put the system in a given state.
  • It is possible to specify a work-in-progress modifier that will ignore failing tests until the due date with the wip extension.

We run the test as usual from our ide or using Gradle from command line. This is how it looks in IntelliJ:

running our test for the first time

Tests are in gray because they are marked as work in progress, and the date is not expired yet.

Note how each step becomes a test and it is running the same test twice using Http and DomainOnly protocols. Also, the dollar signs in the method names are replaced with actual values.

Phase 2—Write the walking skeleton

Now that we have the test in place, we should use it to guide the development of our application.

A good practice is to start from the Http interpreter and then modeling our domain interface. In this way, we avoid the risk of wasting time on a domain interface that doesn’t fit well with our web server.

To test the Http server we need an Http client. So we can put one inside the HttpInterpreter.

We also need to start our local server at the beginning of the tests

We do this with the override of the prepare method. At some point maybe this code will be generic enough to be part of Pesticide itself… but for the moment you need to write something like this yourself.

Note also that we start the local server only if the specified host for our tests is “localhost”. If we want, we can also use the same test on our cloud environment.

At this point, we need to implement our askPetPrice method using the Http client to send the request and then parse the response to return the price. Something like:

The only assertions in the HttpInterpreter are the technical ones, like checking the response code in this case. All domain assertions should stay in the actors’ steps.

Ok now our test asks for the rest call, but it will get an error because there is no server code yet.

So we go to the server and we will implement our route:

In Http4k everything is a function. We attach our routes to simple functions of type (Request) -> Response. To keep everything in a nice functional style, the function itself — petDetails in this case— consists of a chain of simpler functions.

The actual domain is represented here by the hub field. Whilst writing the PetShopHandler we “discover” the methods that PetShopHub must expose — getByName in this case.

To keep everything simple in this example, we use null to capture all the errors. So we avoid completely exceptions and instead we just return an error page if any step gives us a null.

Incidentally, in case you are interested, my book will analyze and explain in detail this style.

Phase 3—Write the domain

So now our Http DDT fails on the PetShopHub method, which is not implemented yet.

At this point, we put the Http DDT away and we concentrate on the DomainOnly DDT, because we need to write the domain now.

As we put the Http client inside the HttpInterpreter, we put an instance of our hub inside our DomainOnlyInterpreter, and we use it to in this way:

And now we can create our domain:

We start using a list in memory to store the pets. If this were a real project, at some point later we will inject a function to read and write from a database or some other form of persistence.

If we run our DDT now, we can see they are still failing because the shop is empty. We need to add a setup in our test and populate the shop:

we also need to implement the method on the interpreter interface:

And the HttpRestPetshop:

And the DomainOnlyPetShop:

And finally, the DDT will pass on both protocols!

We can remove the wip flag and we can run the tests again:

All tests are green now!

Phase 4 — Add new steps to the test

We can now go on adding new steps to the tests, and new features to our application until we consider the scenario completed:

Running now the test, we can see how everything is green:

The complete test running successfully

We can also run the test from the command line with ./gradlew test :

It should be all downhill from here. You can find the full code for all PetShop tests here:

There is only another interesting feature worth mentioning here—storing and retrieving new data during the test.

For example, a common pattern for e-commerce sites is creating a virtual cart or basket to put your articles. If the site doesn’t require registration, it will generate a unique id for the cart, that the user has to remember until the checkout.

So far so good. Now the problem is that we cannot anticipate what the cart id will be, so how can we write assertions about it?

Let’s look at the step when Mary is putting her lamb in the cart:

Here we get the cartId from our response and we store it in the test context — see the line in bold.

In the next step, we can retrieve the current value of cartId and use it to call the checkout.

I think this is enough for a first look at how to use Pesticide, there are other possibilities like testing java application, testing javascript pages, testing microservices, testing legacy applications, and so on. Some of these are already covered by the examples, and some will be in the near future.

Let’s conclude with the big question:

Is it safe to use Pesticide in my project?

Well, it’s your call but please consider that:

  • We are using it in a big project and it is the result of several years of experience.
  • It is test code — it will not put at risk your application.
  • It is open-source, relatively small and test covered — even if I abandon the project you can still use it and expand it.
  • It’s not a mature product, so there may be rough edges and there may be changes of API, although not in version 1.x.

That’s all folks! I really hope the Pesticide library can be useful for other people's projects as well.

I’m more than happy to help and discuss any questions about Pesticide on this blog, or on Twitter, or on Github.

If you are interested in more similar posts please follow me here or on my twitter account @ramtop

--

--

Uberto Barbini
Javarevisited

JVM and Kotlin independent consultant. Passionate about Code Quality and Functional Programming. Author, public speaker and OpenSource contributor.