The concept of… Test Actors

Writing black box system tests is great! They automatically ensure that the product does what it was meant to do automatically and continuously.

But sometimes requirements change and whole work flows are changed. This causes ugly refactorings and - at least sometimes - the whole test suite might get outdated.

Another problem is that those tests can easily get rather complicated. Hard to read and hard to maintain. Also if your product owner demands test automation he/she might like to understand what these tests do.

The Concept of Test Actors

When my former team and I found ourselves in a situation like that we developed the concept of test actors. The basic idea is making the test just as readable and changeable as the abstract user acceptance test the product owner formulated for the story you are working on.

This to a certain level can be achieved using abstract super test classes that give you some methods to get a defined setup (given) and help to do things (when) and check the effects (then).

But this approach leads us to a rather huge amount of such methods partly related to each other or not. We ended up having headline comments and very long method names to distinguish between methods that are applicable for a certain use case.

If I see headline comments in production code I usually think about putting the methods in separate classes. So when we do this in production code, why not in tests.

We thought about names for such helper classes and soon found a rather simple and intuitive way: name the classes after the actors described in the user stories. For example User and give them methods like login or writeEmail or readEmail

These test actors made our test almost as readable as plain English.

Example

I will illustrate the idea with a rather complex example: Let's assume we are developing a blogging application. Our current user stories are the following

As a blog author,
I want no comment on my articles to be published before I approved it,
to be able to ensure that there are no inappropriate comments on my articles.

What needs to be given to test this feature? First we need an article that can be commented on. In the application articles can only be written by an author, so we need a test actor Author with a method writeArticle. Next we need a comment on that article. This is done by a reader of the blog, so we need another test actor Reader with a method writeComment. Both methods may return URL's to reference the created article and comment.

Now we got everything that needs to be given to test the new feature. Next we require another method approveComment of the Author to let the actor use the requested feature.

Finally, we need a way to check if the feature works as it was meant to work. The story wants a comment not to be published before it was approved. Published means that another reader (not the one that wrote the comment) can read the comment. So we just create another instance of the Reader and extend the actor with a method to read an article comment readComment that will return the content of a specified comment as string or null if it's not readable.

Given those actors our first test written with Groovy, with Spock and Geb may look as simple as this:

Author author = new Author(browser, "username", "password")
Reader reader = new Reader(browser)
Reader anotherReader = new Reader(browser)
 
def "An article comment only gets visible to other readers after the author approved it."() {
    given:
    Url articleUrl = author.writeArticle("Some Title", "Some content to test comment feature.")
    Url commentUrl = reader.writeComment(articleUrl, "Some appropriate comment.")
    String preApproval = anotherReader.readComment(commentUrl)
 
    when:
    author.approveComment(commentUrl)
 
    then:
    anotherReader.readComment(commentUrl) == "Some appropriate comment."
    preApproval == null
}

The disapproval test looks almost the same:

def "An article comment never gets visible to other readers if the author disapproved it."() {
    given:
    Url articleUrl = author.writeArticle("Some Title", "Some content to test comment feature.")
    Url commentUrl = reader.writeComment(articleUrl, "Some inappropriate comment.")
 
    when:
    author.disapproveComment(commentUrl)
 
    then:
    anotherReader.readComment(commentUrl) == null
}

As you can see these tests are almost plain English. So it is readable and even if our application changes on a technical level the tests stay valid since they only describe a workflow as described by the user story.

If by a later story we add an actor to the comment form, we just need to add this to the writeComment method. If we insist on the user to leave a valid email address with his/her comment, we just need to add an email address to the Reader's properties and make him use it in writeComment. Without test actors we would need to change every test that involves the comment feature.

Conclusion

Test actors are a concept that help us in several ways.

First they simply help us to put our various more or less complex helper methods into containers. That could be achieved in other ways of course but the actors make knowing where to put these methods and which are really needed easy: just ask yourself who or what in the real system would do it and you know the place. This also makes us realise if we break into the black box since a user cannot look inside any database.

Second the actors make your tests pretty readable. If there are new developers put on your team or even if an entirely new team picks up your work this will be a huge benefit. Also the Product Owner will be able to see what these tests do. The testers on your team -- even if not used to write code -- will be able to understand the tests and will be able to review and do pair programming on these tests. With some help from a developer they might even extend the tests or write additional ones.

Third the actors build an abstraction layer. The test itself only describes the workflow while the actors get are wrapping up all the technical details about the system. Changes on the system -- which we as agile developers are to expect -- can be done more easily thanks to this abstraction.