Using Serenity with Cucumber, Part 3

This post follows on from the previous (see part 1 and part 2). Here I’ll close out this series of posts by performing a bit more abstraction and finish up with some thoughts about how and to what extent the Cucumber abstraction fits in.

Continuing on directly from the last post, we have a fully working implementation of a Cucumber feature file. This feature file is matched up to a series of step definitions which in turn delegate to a page object. In this post, I want to abstract away from the page object. But how? What I want to do is focus on a user who is acting out a series of steps.

Provide a Steps Library Reference

Let’s change our RecordTodoSteps like this:

Here I’m using the @Steps annotation. The @Steps annotation marks a Serenity step library.

In Serenity, the idea of a Step Library is to add a layer of abstraction between the intent and the implementation. I talked a little about this in my somewhat generic post on Java Automation with Serenity. This idea, however, takes on an interesting focus when you throw Cucumber into the mix. After all, the idea of Cucumber feature files — and their associated step definitions — already suggest such a separate of intent (“what”) from implementation (“how”).

So are just adding a superfluous layer here? In my view, not necessarily. The step library concept of Serenity can help you organize your overall code into more reusable components. This is the case regardless of whether Cucumber is in place or not.

With this current example, what I’m suggesting here is a move away from page objects and instead saying that Cucumber steps should be done in the context of a user’s actions. So, for that, I’ve established that there will be an instance of a User class. Where should this class go?

I’m going to put this in my src/main/java root package. Specifically I’ll create a steps package that is at the same level as the pages package we created in the previous post. In this package, create a User class. Here’s what your structure will look like:

Provide a Steps Library

To get ourselves going, let’s extend this User class from ScenarioSteps:

ScenarioSteps refers to a set of reusable steps for use in an acceptance test suite. The idea is that any class that extends the ScenarioSteps class calls the step library. So keep in mind what we have here. We have a test steps file (RecordTodoSteps) that has a particular instance (user) annotated with @Steps. That tells Serenity that this class is going to contain a series of steps that should be reported on as part of test execution. In effect, the User class is going to be the step library.

So let’s change our test class to start doing this. Change your @Given method statement as such:

Keep in mind what we’ve done here. This is important.

We changed the the code from todoPage.open() to user.opens_todo_application(). Is that a big deal? Well, sort of. It means we’re abstracting away from the page and more onto the user. If the user needs to delegate to the page, then that will be handled as part of the user actions. In fact, let’s create the opens_todo_application() method on the User class:

Notice that here I’m referencing the page object (todoPage) in this class and I have a @Step annotated method that indicates that when this method is executed, it should be reported on in the test reporting. Keep in mind that the user class is a step library. That means it is composed of methods that are steps. You indicate which methods are steps by the @Step annotation.

Let’s replace our other statements in our test class so that they call methods on the user rather than on the page. In fact, here’s what the test class should look like (and I’ve removed imports when necessary):

Notice that any references to the page object are now gone. This means the User class, with the above new methods created, should look like this:

Notice that each method has a @Step annotation.

Go ahead and run this again. You should everything passes. In the test report, the steps for your scenario should show up like this:

The Abstractions

Yeah, that’s great and all … but haven’t I really just shuffled code around? In one sense, yes. But in another, very real sense, no. What I’ve done is moved the potentially more volatile code further down the stack. So my Cucumber feature file provides a certain level of intent, my test steps provide a certain level of implementation. That implementation is rooted in the actions of a user. That user delegates to a page object in this case, because that’s what is being tested. But that same user could be delegating down to a mobile emulator or to a REST service.

One thing I’ll say though is that the Cucumber abstraction still sticks out like a sore thumb to me. That’s mainly because it’s really just repeating what the code does. Consider my feature file and my code steps:

    Given the todo application
    When  the todo action 'Digitize Supreme Power Collection' is added
    Then  'Digitize Supreme Power Collection' should appear in the todo list

If you replace the variable names in the code with their values, you see how close everything aligns:

So what I really want is my Cucumber features to be a bit more declarative. How about this?

Feature: Add new todos
  Users need to be able to quickly add tasks as fast as they can think of them.

  Scenario: Add a new todo
    * users can add an action to the todo list

Here I’ve essentially reduced the scenario to one line, that is very declarative in nature. With this in place, let’s change the RecordToDo file as such:

Notice here that I didn’t have to change my User class at all nor did I have to change the page object, TodoPage.

If you run this, you’ll find the test report looks like this:

Pull Down vs Push Up

While this is admittedly a very simple example, I do think it showcases exactly how you want to think about “pulling down English” versus “pushing up English”. Notice, however, that to do this I had to use the “*” operator in Gherkin, which effectively just means “any step.” Not all BDD tools support this. JBehave, for example, does not. This is where I think Gherkin can becoming constricting.

Contrast some of this, incidentally, with the logic I presented in my Screenplay Serenity posts. At one point, you’ll end up with code like this:

Yes, it’s “busier” than pure English would be but, on the other hand, as we’ve just seen I can keep my English fairly constrained by sticking just to the business rules. Instead of trying to “modularize” at the level of natural language, I modularize at the code level, where that kind of practice belongs.

Further, it allows me to push English back up, so that if people do want the implementation details, the automation can serve not just as an execution mechanism but also as a documentation generator.

How Business Speaks

To drive this home a bit, here’s an example of how most business teams I work with want to describe features and scenarios.

Doctors provide information about medical conditions to patients via targeted articles.
Patients need to be able to access those articles conveniently.

Patients must be able to print an article
Patients must be able to reprint an article
Patients must be able to send an article link to an email address

Doctors must be able to view activity on the article
Doctors must be able to deactivate an article
Doctors must be able to edit provider and location for a scheduled article

This, to me, is perfect. It’s exactly what I want from product and business teams. A statement of why this overall feature adds value. Then a breakdown of how it adds value by talking about a high-level interpretation of what the feature does to provide value. It’s now up to developers and testers to work out the logistics of what exactly the solution will look like.

The development code and test code should be developed in tandem. This way both sets of code become a common specification and thus a single source of truth. This is somewhat in line with some of my thinking from modern testing.

Notice, too, that the concept of specific users can be directly translated from those business requirements into a particular type of class (DoctorUser, PatientUser). Even better, if you go with the Screenplay approach that I talked about in previous posts, you can treat Doctor and Patient as particular types of Actor instances.

This gives you a lot more flexibility to change based on development spikes or the product team refining its understanding. How you manage that flexibility is the effective use of abstraction layers. You’ll notice that, in this post, that meant going towards a very minimal implementation of a “Gherkin” style specification.

To Cuke Or Not To Cuke

I’ll close off here with this final consideration.

Ask yourself this: why are “Gherkin-like” approaches chosen? Usually because those are “easier” to automate. But it’s not necessarily because it’s easier to speak in. People don’t “speak Gherkin.” The above example of Patient/Doctor was plenty good enough to spark further conversations and collaborations. And you’ll notice not one bit of that is in Gherkin.

So while the posts in this series have focused on the integration of Cucumber and Serenity, I want to leave you with the thought that you should, at minimum, consider the level of expression in terms of how you use Cucumber. Beyond that I would hope you consider whether or not Cucumber, and the focus on Gherkin-style feature files, is even the most effective or efficient approach.

Share

About Jeff Nyman

Anything I put here is an approximation of the truth. You're getting a particular view of myself ... and it's the view I'm choosing to present to you. If you've never met me before in person, please realize I'm not the same in person as I am in writing. That's because I can only put part of myself down into words. If you have met me before in person then I'd ask you to consider that the view you've formed that way and the view you come to by reading what I say here may, in fact, both be true. I'd advise that you not automatically discard either viewpoint when they conflict or accept either as truth when they agree.
This entry was posted in Automation, Cucumber-JVM, Serenity. Bookmark the permalink.

2 Responses to Using Serenity with Cucumber, Part 3

  1. Rafal says:

    I have read all your posts about Serenity (about Screenplay pattern and Cucumber integration) and I think that these are the best texts about it. You are writing step by step what is going there and it is very simple to understand. You helped me understand Screenplay pattern so I am changing my POM framework to it at the moment. Thanks for good job!

  2. Renata says:

    Hi,

    Great post! But I have some questions about having multiple pages to interact with inside User @Steps. In your example you have only to interact with TodoPage, but let’s say you have two pages where in each of them you have to fill a form, eg.: Login, ResetPassword. In this example I will have to enter e-mail address on both pages. Would your code look like this?

    public class Example {
        @Steps
        User user;
     
        @When("^I enter e-mail address on Login page$")
        public void enter_email_address_on_login_page(String email) {
            user.enter_email_address_on_login(email);
        }
     
        @When("^I enter e-mail address on Reset Password page$")
        public void enter_email_address_on_reset_password_page(String email) {
            user.enter_email_address_on_reset_password(email);
        }
    
    }

    I have this problem right now. I have many pages where I perform similar actions but in different pages. What I did in a previous project is to use @Steps per page. I used the following:

    @Steps
    LoginPage login;
    
    @Steps
    ResetPasswordPage resetPassword;

    But I don’t know if there is a better way of doing it. What do you think?

Leave a Reply

Your email address will not be published. Required fields are marked *