Overview of Behavior-Driven Development and Cucumber

The BDD Process

BDD always starts with a conversation. Instead of passing requirements from one person to another and hoping everyone gets it right, the teams that practice BDD get together early enough to have conversations about what the customer really needs, and what BDD does is it gives us techniques to structure and facilitate these conversations. The team talks through concrete examples and counter examples of how a user would interact with the application and what outcomes they'd like to achieve. And by the end of the conversation, the whole team has a really clear picture of what they need to build, and just as importantly, why it would be useful, and it's not a one-way street. The team members get to ask questions to clarify doubts, even to propose new ideas or suggestions that all go to making the features better. It's a little bit like they're building a picture of the feature as they went, bit by bit, clarifying the details as they go, but this is just the first phase of the BDD process. It's often called the discovery phase, or as I like to call it, the illustrate phase because it's where the product owner and teams work together to draw a picture, a mental model, of the feature the business needs. In the illustrate phase, the team talks through concrete examples about how the feature would work and how it would benefit the business. During these sessions, we don't see much Cucumber. The main tools we use are whiteboards and Post-its, and we aim to produce a list of business rules and acceptance criteria that we can use for this feature. We pass these rules and examples to the next stage of the process, which we call the formulate phase, and this is where the team members, often a BA and a tester, turn these rules and examples into a format that can act both as specifications that a business person can read, and also as automated acceptance tests that we can execute as part of our test suite, and this is where Cucumber comes into play. We write these executable specifications in a special language called Gherkin, which uses the Given-When-Then notation that you may have already seen. Next, we write test automation code to turn these Given-When-Then statements, or these executable specifications, into automated acceptance tests. This usually happens before development starts or during the same sprint. And once these tests pass, we can show the business concrete evidence that the feature does what it was expected to do. Since these tests are written in the same language as the specifications, in fact they are the specifications, the business folk can read and understand them. Talking the business language is a great way to increase confidence in the software we deliver. So BDD works by giving teams a single source of truth. Executable specifications written in a business language that work both as specifications and as automated tests, and unlike traditional test automation, teams that practice BDD write these automated tests before, or as, a feature is being developed, so it helps drive and accelerate development work and helps the developers as well rather than just finding defects after the work is completed. And finally, when a feature is delivered, these specifications act as documented examples of how the application actually works, and the best thing about this kind of functional documentation is that as long as the tests pass, you can be pretty confident that it's up to date. Working collaboratively like this gives us massive benefits on many, many levels. Agile is all about collaboration, but BDD forces you to be really agile, not just to go through the motions. We often say that BDD helps us bridge the communication gap between business folk and the development team, but it also makes team members within the team itself communicate better. This early collaboration gives teams a deep shared understanding of what the business really needs, and this helps them deliver more innovative higher-value features, and teams that participate in this sort of activity really feel like they own the solution. It makes them more motivated and engaged. The automated acceptance tests give faster feedback and more confidence about the features we build. Teams practicing BDD generally see a huge drop in defects, not only from the automated tests, but more importantly because you don't get this communication gap and the developers really, really understand what they're building. In the next section, we'll look at a little bit more about Gherkin, and we'll take a look at the most popular BDD automation tool, Cucumber.

What Is Gherkin?

So what is Gherkin and what is Cucumber, and where do they fit in to the BDD landscape? Well, Gherkin is that Given-When-Then notation we mentioned earlier. It's what we write our executable specifications in when we practice BDD with Cucumber. It's designed to be both business friendly and easy to automate. Doing both is a bit of a trick, and we'll look at some techniques later on to get this balance right. And Cucumber is the automation library that we use to run our executable specifications. Cucumber lets us automate our Gherkin specifications in languages like Java, or JavaScript, or Ruby, or many others. Let's take a look at some real Gherkin. This is a simple Gherkin script. As you can see, it's written in almost plain English. You can also see that it's written in terms that a business person could understand, talking about business domain concepts like customers and orders that business people can relate to. Always remember, Gherkin is above all a communication tool. Finally, as we'll see shortly, this text is actually executable. We can wire this up to test automation code and turn these plain English requirements into a fully-fledged automated acceptance test. Now I did say almost plain English, and there is a little bit of structure that Gherkin scenarios need to follow. You can see it here. Gherkin specifications describe features, so we start with a description of the feature we are discussing. This one is from our demo project, which is an online delivery service for smoothies. Customers could sign up and get their morning smoothie everyday for breakfast, and this particular requirement describes what happens when customers want to view their orders. A feature is illustrated by a number of examples, which we call scenarios. Each scenario illustrates a business rule, and sometimes you can have several scenarios to describe different variations or educators. The scenario here would be the first of many, and describes a customer, Michael, who wants to view his current order. Scenarios use special words like Given, When, and Then. At the start of each line, these can structure to our scenarios. The first two lines here are our preconditions. Given Michael is a Morning Freshness member and he has ordered a Green Goodness Smoothie. This describes a state of the system at the start of the scenario. The When is the heart of the scenario. It's where all the action happens. It's what the scenario is really about. In this case, we're saying when Michael views his upcoming orders. Finally the Then describes the outcomes we expect. Michael should see the orders that he's made with the correct status. And what is Cucumber? Well Cucumber is a tool that lets us turn Gherkin scenarios like this into automated tests. So Gherkin is the notation we use to write our executable specifications, and Cucumber is the software we use to automate these executable specifications. And the software interacts with the application that we are testing.

Demo: Running a Cucumber Scenario

Before we finish this scenario, we'll take a look at what scenarios likely look like in the wild. We'll see what a Cucumber Java project looks like and what it's like to run a Cucumber scenario in a development environment, like IntelliJ. Let's look at one of the scenarios we saw earlier on. Here it is, Placing Orders. In order to get a healthy breakfast, as a morning freshness member, I want to be able to order my smoothies online. And our first scenario is this one: Michael orders a smoothie. Give Michael is a Morning Freshness member, when he orders a Green Goodness Smoothie, then his order should be placed in the preparation queue. Like any Cucumber scenario, this is not simply text. There is Java code behind each of these lines that turns this text into an executable specification that we can run when we run this feature file. For example, this first step, Given Michael is a Morning Freshness member, behind the scenes, this needs to create a new Morning Freshness member called Michael that we can use in our text, and tools like IntelliJ make it very easy to step between this text form, this business-readable step, and the Java code that actually executes this step. So here we have our Given Michael is a Morning Freshness member. We pass in the name of the member, Michael, and we call the registerAMorningFreshnessMember object to create a new Morning Freshness member for our test. These feature files live in a directory called src/test/resources/features, and this is a Java Cucumber convention that we use. In this directory, you can see some other feature files as well, such as places where we have placing_orders, and we also have viewing_upcoming_orders. Should we want to run these scenarios, we need to run a special class called a test runner. Here, we've named it AcceptanceTestRunner, and it looks something like this. It's a very simple class with no methods, just two annotations. The first one is RunWith Cucumber, and this tells Java to run this test as a Cucumber test suite. And the second one, CucumberOptions, well, this gives Cucumber some configuration details about how to run the scenarios. In this case, the most important one is the features attribute, which tells Cucumber to look in that src/test/resources/features directory to find the feature files. And this is just a conventional JUnit test so we can run it. Let's see what happens when we do. So I'm running the test and all of the tests pass. You can see in the output it's displayed the scenarios that we've executed, Placing Orders, and we can also see the Viewing upcoming orders. And in the console app, what we can also see is documentation about each step that was actually executed. And here we have given Michael the Morning Freshness member, and so forth. He's placed his order, and it's been placed in the preparation queue. They're all green, so this appears to have worked. This was just a very quick overview of what a Cucumber scenario looks like when you run it in a development environment like IntelliJ. We'll go into the details on how you can make this work for your own scenarios in the following modules, so stay tuned.

Building a Shared Understanding: BDD Requirements Discovery

Discovering the Requirements, BDD Style In this module, we'll be looking at what comes before Cucumber and Gherkin. We'll be looking at how teams collaborate to define the specifications that we end up writing in Gherkin and automating in Cucumber. In the previous module, we saw how there's a lot more to behavior-driven development than just writing Cucumber test scripts. The important part of BDD happens well before you open your development environment. It's when the team gets together to have conversations about a feature or a user story, and when they come up with examples of business rules that will eventually become our feature files in Gherkin scenarios. We often call the group that has these conversations the three amigos because they represent different perspectives. We want someone who understands and presents the business name. What does the business actually want? What problem are we trying to solve? We also need someone who understands the technique or solution, and who can ask the right questions to make sure that the requirements are detailed enough that they don't contain any ambiguity or uncertainty that might make things harder when it comes to actually building the solution. And we want someone from a QA or testing perspective who makes sure that the requirements are precise enough to be tested and verified. The actual roles may vary and there can be more than three people present, but the important thing is that the conversation is a dynamic exchange, not just a walkthrough or a rubberstamping of a user story. The aim of the conversation is to challenge the requirement, to really understand what problem we're trying to solve, to spot any uncertainty or ambiguity, and to make sure that the team understands the requirements well enough to implement them effectively. This happens before work on a new requirement starts. For teams doing Scrum, it typically happens before a sprint starts, leading up to sprint planning. The team talks with the business to decide what would be the most valuable thing to build next. The team might use discovery techniques, such as Example Mapping, or Feature Mapping, or they might simply write up some examples on a whiteboard until the business and team are satisfied that they've understood the problem they need to solve. The story can then be slated into the next sprint and some of the team members, often a business analyst and a tester, will go away and turn these examples into feature files in a Gherkin scenario. And at this point, the story can go into the sprint backlog. Let's take a look at how a typical BDD discovery session could play out. This is Sam. Sam is the founder of Morning Freshness, a startup that delivers smoothies and other beverages each morning to people who are too busy to make them for themselves. Sam is working with her development team to add features that will help her sell more smoothies. There's Belinda, the business analyst, Dave, the developer, and Tess, the tester. Sam has a problem. She wants her existing customers to buy more smoothies so that she can grow her business. The question is, how? Fortunately, Sam has an idea. The super smoothie loyalty card. The idea is simple. When a customer with a loyalty card buys a certain number of smoothies, they get one free. Let's see what a discovery session about this requirement might look like.

Can You Give Me an Example?

So, how do you see these loyalty cards working? Can you walk us through an example? Sure. Basically, members get a free smoothie after they make a certain number of purchases. So imagine Michael is a regular customer. Over the past few months, he has bought 10 smoothies and this gets him a 10th one for free. Let's call this example, the one where Michael gets a free smoothie. So let me get this right. Say Michael buys 10 banana smoothies, next time he orders a 10th banana smoothie, he gets it for free. Assuming he has signed up for the loyalty program, then yes, that's right. Okay, so first he needs to have a loyalty card. Gotcha. Does it make a difference what kind of drink Michael has bought? Yes. Not all smoothies have the same price. So if you want to buy, say, a triple berry blend smoothie, which is one of our special fancy smoothie range, you would need to have bought more banana smoothies. How many more? Fourteen. It's based on a point system. Okay, so if I understand correctly, it works like this: Suppose Mike wants a fancy smoothie. If Mike has bought 14 banana smoothies, he can get a triple berry blend for free, but if he had only bought 13 he would have to pay for it. That's right. Could you walk us through how the point system works in more detail? How many points do I get when I buy a smoothie, and how many points do I need to get a free drink? Sure. So you earn points whenever you order a smoothie, and when you earn enough points, you can get a free one, but not all smoothies cost the same number of points. You need to earn 150 points to get a free regular smoothie, like banana smoothies, but for a fancy smoothie, like our famous triple berry blend, you would need 200 points. And are all smoothies worth the same number of points? No. Fancy smoothies earn you 20 points, whereas regular smoothies only earn you 15. We could say that a drink of category regular smoothie, such as a banana smoothie, earns 15 points, and a fancy smoothie, like the triple berry blend, earns 20. Are there any other types of smoothie that would earn different points? No. We only have two categories of smoothie, regular and fancy, but we might want others later. Are there any other types of drinks that can earn points? Yes. A tea, like Earl Grey or English Breakfast, will earn 10 points. Any coffee, like an espresso or cappuccino, earns 12. Teams that practice BBD use examples and tables that come out of these conversations to form the acceptance criteria for the stories that they need to implement. And it's these acceptance criteria that we express in Cucumber's Given-When-Then notation so that we can turn them into automated acceptance tests. So the quality of our Cucumber features and scenarios depends a lot on how well these conversations work. In the next clip, we'll be looking at a few of the ways that teams that practice BDD structure and organize these conversations to make them more focused and more productive. So the quality of our Cucumber features and scenarios depends a lot on how well we make these conversations work. In the next clip, we'll be looking at a few of the ways the teams that practice BDD structure these conversations to make them more focused and more productive.

Example Mapping and Feature Mapping

BDD always starts with a conversation. Remember, if the conversation didn't happen, then it's not really BDD. In this clip, we will be looking at a few ways that teams make these conversations more effective. Sometimes a simple conversation is enough to understand what the business really makes, but these cases are rare and often we need to structure and guide our conversations a little bit more to ensure that we really understand the problem we're trying to solve. There are three practices that come in very handy when you have these conversations. The first is simply writing down lists of examples and tables on a whiteboard or on butcher paper. This is a very simple, but still very effective way, to discuss many requirements. Another approach is called Example Mapping. Example Mapping is a practice that uses colored Post-its to have more structured conversations about rules, examples, and counterexamples, which can help us think of cases that we might otherwise miss. And finally, Feature Mapping. It's similar to Example Mapping, but it's useful when we want to break down examples into more detail. We break our examples into steps and consequences and try and understand what the inputs and outputs are of each example. Feature Mapping works well for more complex requirements and can also be useful when you need to break a larger requirement down into more manageable slices. Let's have a look at each of these techniques in a little bit more detail. Tables are a great way to model many requirements, particularly when there are clear inputs and outputs. This is the one we saw earlier and has a fairly typical form. On one side, we have a business rule, followed by some sample data that illustrates this rule with some inputs and at least one outcome. Tables can be more complex than this and can, for example, have more than one input color, or more than one output. It's important to make sure that the tables contain only the information that is really needed for the business rule that you're illustrating and not any extra detail that might distract the reader. For example, we might be tempted to add the price of the drink into this table, but this would not be relevant to the business rules that we're describing here and so could distract the person reading a scenario. As we see later on in the course, tables are very easy to express in Gherkin. Example Mapping is another popular technique with teams using BDD. In Example Mapping, we have conversations about business rules, and we illustrate these rules with concrete examples and counterexamples. Sometimes when we discuss an example, or a counterexample, it leads us to discover new rules, or maybe it helps us unearth a question that we didn't know the answer to, and later on we can use these examples as the basis to write our Gherkin scenarios. Let's look at a simple Example Mapping action. We'll start with a new user story about a buy one, get one free special. Suppose that one of our acceptance criteria is that the free drink needs to be in the same category, or a lower category, than the one we buy. Each drink has a category, so fancy smoothies are in a higher category than regular smoothies, and so on. One example might be the one where our user, Mike, orders two banana smoothies, so one of them is free. A counterexample might be the one where Mike orders a banana smoothie and a triple blend berry smoothie. So in this case, he'll be charged the price of the triple berry blend. But this leads to another question, what if he orders a croissant as well? And so when we talk about this, we might discover that we have a new rule that the special only works with drinks. And as an example for this rule, we could have something like the one where Mike orders a banana smoothie and a croissant. The last technique is Feature Mapping, which you've already seen in action in the previous clip. Feature Mapping is like Example Mapping, but rather than just listing down rules and examples, we break each example down into steps and outcomes, or as we like to call them in Feature Mapping terms, consequences. For each step, we look for variations that might lead to different outcomes or other business rules that we hadn't know about, and for each outcome, we ask ourselves what else should happen? What other outcomes should we consider? Here is a feature map we saw in the previous clip. Now that we've seen how feature maps work, this might make a little bit more sense. On the left was have a business rule. Members can get a free smoothie after a certain number of purchases. Then we list some examples, green cards, like in Example Mapping, the one where Mike gets a free smoothie, the one where Mike wants a fancy smoothie, and so on. And for each of these examples, we break them down into steps, which are the yellow cards. So for the first one, we have three steps. Mike has a loyalty card, Mike has already bought 10 smoothies, and when he orders anther smoothie, and then we have a consequence in pink. The consequence here is the smoothie is free. Feature Mapping is a great technique for more complex requirements, for user stories that involve a user journey or a workflow, or simply when there are a number of inputs that needs to be considered. Features maps are also a great way for teams to explore a brand-new requirement or a domain that they're unfamiliar with. So what have we learned in this section? Well, we've seen that BDD always starts with a conversation between the business and the development team, and the conversation uses techniques like Example Mapping, or Feature Mapping, or just writing out tables or listing examples to go through concrete examples and business rules and to get a deeper understanding of the business requirement. We've also seen how these examples form the basis for our acceptance criteria that we'll later on turn into Cucumber/Gherkin Given-Then-When scenarios.

Dependencies and Project Structure

Now that you've seen the environment settings and the IDE configuration that you need to run Cucumber, let's look at some of the library dependencies in the project structure that you'll need. There are three main dependencies that you need, Cucumber itself, a test runner, and an assertion library. For Cucumber, we need the core Cucumber library, but we also need some runner classes so that we can run our tests in JUnit. As a test runner, we'll be using JUnit, the most widely used test runner in the Java world, more specifically, we'll be using JUnit 4. And we also need a way to describe the outcomes we expect, and a way to see what happens if we don't get these results. We'll use libraries like Hamcrest and AssertJ for this. In Maven, we add these dependencies into the dependency section of the pom.xml file. You can either opt to define your step definition methods in Cucumber using Java 8 Lambda expressions, in which case you'd need Cucumber Java 8 dependency, or you could use a more conventional annotation-based approach with the Cucumber Java dependency. You can run Cucumber feature files directly from the command line in Java, but it is much more common to run them through your Maven build, especially when they're a part of the build process. And for this, we need to add the cucumber-junit dependency, which gives us our JUnit test runner classes. Finally, we'll need an assertion library. The two most commonly used are Hamcrest and AssertJ. Hamcrest is a powerful and flexible library that gives you a great range of assertions and is very useful if you need to write your own assertions for more complex checks. And AssertJ is an alternative to Hamcrest that also has a rich set of features, but is a little bit easier to use and more intuitive in many ways than Hamcrest. So this is the library we'll be using in our project. The project structure we'll be using is fairly typical of Maven and Gradle Cucumber projects. At the project root, we have the build scripts, your pom.xml or your build.gradle file. The source code goes in the src directory, application code goes in src/main, and test code goes in src/test. In particular, the step definition and other test code classes go in src/test/java, and the feature files themselves go in the src/test/resources directory.

First Cucumber Test

We're going to create a very simple file with a single scenario. It won't do very much. It's really just a Cucumber equivalent of a hello world, but it will help us ensure that our project is fully operational. In the course resources for this module, you'll find a zipped version of the starter project, as well as a completed version. You can use these if you want to follow along at home. Let's get started. Let's start off by importing the Maven project into IntelliJ. So if I open the morning-fresh project, I'll click through all the default options, and IntelliJ will import this Maven project into the IDE. So here we have the Maven project, and you can see that structure that we saw in the slides. We can see the src/test/java and resources directory. And we can also see the POM file which contains those dependencies, so cucumber-java, cucumber-junit, we've got the JUnit dependency, and we have the assertj-core dependency. Now let's create our first feature file. It will be called calculator.feature, and we'll put it in the com.Pluralsight .bdd package in our resources folder. Let's give the feature a name, Calculator, and we'll give this feature a scenario, Adding two numbers. So this will be a very simple scenario where all we want to do is add two numbers. So we can start off with Given I have a calculator, When I add 1 and 2, Then I should get 3. So this is our first Hello World Cucumber scenario. Let's see how we would automate that. First of all, we need a test runner, and that will go in our src/test/java folder. It's a Java Class. Let's give it a simple name like CalculatorTest, and inside our test, we're going to turn this into a Cucumber runner. We do this by adding the RunWith annotation and saying that we will run this test with the Cucumber runner class. And that's all we need to do. Cucumber will figure out some default values when we run the test. So let's run it and see what happens. When Cucumber runs a test that hasn't been implemented yet, it gives us some sample step definitions, some sample automating code that we can use to start off our test. If we take this code, and then place it in a separate class, which we call a step definition class, Cucumber will then go and look for those bits of code and execute them when we run our test. So let's copy those step definitions, those methods with our Given-When-Then annotations, into our CalculatorStepDefinitions method. And now we can actually implement our test. In this first step, we need a calculator. So I'm going to create a new object of type Calculator, but Calculator doesn't exist yet, which is why it's marked in red. So we can create the variable, but we don't have the class yet. Let's create the class. I'm going to create it in a package called calculator. And now that our class exists, we can move on to our second method, When I add 1 and 2, and this is where we're going to call the calculator and calculate the sum of 1 and 2. The method could be add. And then in the third step, we need to check the expected outcomes. So we need to store the results somewhere, and we need to check the expected result against the actual result, and for that we use an assertion library. This is where we use AssertJ. We say assertThat, and this is a static import from the assertions class from AssertJ, we say assertThat result isEqualTo the expected result. And there's one last thing we need to implement, the add method, which doesn't do anything at the moment. So let's start off with a bare-minimum implementation. So this will compile, but let's run it and see if it works. And when we run our Cucumber scenario, we get a failing test. We were expected 3, but we actually got null, and this is what we expect. We expect it to fail because we haven't actually implemented the addition yet. Never trust a test until you've seen it fail. Now, we can go into our Calculator method and implement our solution. When we add a and b, we return a + b. And now when we run our test again, hopefully it should pass. And there we have our very first Cucumber scenario with a fully passing test. Everything's green, so we're all good. So congratulations, you've seen your first passing Cucumber scenario. So in this section, you've made some really important progress. Your environment should be operational, and your first project should be up and running. You've learned about the basic Cucumber project structure in Java, and you've seen your first running Cucumber test. You've also seen where the feature files go in and where the step definition classes go, and how to run these scenarios once you've put them in the right places. In the next section, we'll take a closer look at what actually goes into a feature file.

Gherkin Language: The Given-When-Then Notation

Organizing Features and Scenarios

So far, we've been focusing on scenarios, but a scenario doesn't exist in isolation. It's always part of a feature. A feature is a piece of useful functionality that we can deliver to our users and that our users will be happy with. We describe these features using business rules and examples that we write as scenarios. And when we want to build these features, we can group the scenarios together to form user stories. We use these user stories to plan and deliver our work. At the end of the sprint, we can throw these stories away. Once if delivered, we don't need them anymore, but the feature and its scenarios become part of the product's living documentation, along with all the other features in our application, and these we do want to keep. We store these features in feature files, which have the .feature suffix. A common convention in Java projects is to store these feature files in a src/test/resources/features directory, and in this directory, you'll have the feature files that represent the various features of your application. For smaller projects, these can be organized in a flat structure, but for larger ones, you often place these feature files in subdirectories which represent higher-level concerns, such as EPICs or capabilities or modules. Let's have a closer look at the ordering_smoothies.feature in more detail. Each feature file has a title and at least one scenario. A feature can also have a description. This is free take, so it's a great place to put an executive summary of your feature if you like and to give some context and background about why this feature is a useful thing to have in your application. Scenarios in the same feature file often cover similar business rules, or variations on the same rule, so they often share given steps. Here in both of our scenarios, we have Given Michael is a Morning Freshness member. Now we could use what we call a background section to make these scenarios a little bit more concise. Any step in the background section will be executed at the start of each scenario, so they're a great way to avoid duplication when you're setting up your test context. It's important to remember that any steps in the background section will be run before every scenario in the feature file. You can't pick and choose which scenarios you want your background to apply to.

Data Tables

In this section, we're going to be looking at a very useful Gherkin feature called data tables. Let's see how these might be used to make our scenarios more readable. Suppose we have a feature where our members get daily news and articles about health-related topics that they're interested in. For example, Michael, he's interested in running, gym, and dieting, so we want to send him articles that are just related to these topics, but there's a lot of repetition in this scenario. And he is interested in running, and he is interested in gym, and he is interested in dieting. All these words make the scenario quite hard to read. Fortunately, we can simplify this. We can use a table to represent a list of values that we pass into our scenario, and this makes the scenario a lot more readable, and we can use a similar table in the Then section to represent the list of topics that Michael should see. Value lists, like this, are a great way to help readers focus on the important bits of a scenario, and they are also a lot easier to extend and to maintain. But we can do a lot more with tables than just represent lists. Let's go back to Michael and his smoothies. Now suppose that Michael's resolutions for healthy meals haven't really lasted that long, and he wants to add a few more things to his order. Now not only does he want an apple and mango smoothie, but he also wants a cappuccino and a couple of croissants. We could write this in three steps, like you can see here, but that would look like he's making three separate orders, not a single order with several items, and there's also a lot of duplication, which makes the scenario quite hard to read. Or, we could rearrange this and put all of the items in a single line, so When he orders and Apple and Mango Smoothie and a cappuccino and 2 croissants, but this would be inflexible and hard to maintain. For example, what would happen if in another scenario Michael also wants a banana? We would end up with a lot of similar steps that we would need to automate separately, in this case, complicated and hard to maintain. Fortunately with Gherkin, we can have a much more elegant way to express this kind of requirement. Embedded tables. Using data tables, we can provide a pipe-deliminated embedded table just like this one. This is what you would expect. It's a table with two columns, and at the top of each column we have a column name, Product and Quantity in this case, and we have three rows of data corresponding to the three items we order. This format is both readable and flexible. You can easily add new items to the order just by adding a row, or you could add extra information about each order item by adding an extra column. For example, you might want to add the unit price or the tax for each row. Tables can be written in many different ways. Sometimes it makes more sense when we put the headings in the first column and values in the following ones. For example, suppose Michael needs to be able to check how many calories he'll be consuming with his gourmet breakfast. In the second Given step, or the end step here, we can describe his order using the classic format that we saw earlier with the column headings in a row for each order. But to describe the calorie count for each item, we could use a map format. Here the column values are fairly obvious. We have the product on one side and the calorie count on the second. So here, the map notation, rather than a table like this, is quite intuitive, so that's another way we can express our tables. We could also take things a little bit further and have a multi-column map. Now this is a map structure, but the keys on the one side are Apple and Mango Smoothie, Croissants, and then the Total value. And each entry is another map where the keys are Quantity, calories, and the total value. So this is a more complicated structure for more complicated requirements, but it's still quite intuitive to read.

Scenario Outlines

The last topic I want to look at is scenario outlines, which as you will see, use tables in a very different way. Let's return to Sam's super smoothie loyalty card idea. In the requirements discovery session that we sat in on last time, we came up with a few rules. Each time you buy a drink, you get some points added to your card. So for a basic smoothie you get 15 points, for a fancy one you get 20 points, for a tea you get 10 points, and for a coffee you get 12 points. We could write this as a simple table, like this one. For each drink category, we show the number of points you earn. And we might make this table a little bit more descriptive by adding a sample drink so that the readers can get an idea of the difference between a regular smoothie and a fancy smoothie for example. Concrete examples like this always make requirements a lot more accessible and easier to understand. So how could we represent this business rule in Gherkin? Now each row is effectively a different example, so we could write a scenario for each row. Our first scenario might be about regular smoothies, Given Michael is a Morning Freshness member, When Michael purchases a banana smoothie, Then he should earn 15 points. And we could add a second scenario about fancy smoothies. When Michael purchases a Three-Berry-Blend smoothie, Then he should earn 20 points. And when he purchases an Earl Grey tea, he should get 10 points, and you get the idea. For coffee, he purchases a cappuccino, he gets 12 points. But can you see how much duplication there is here? All this text makes the scenarios much harder to read and harder to understand than the simple table we saw earlier on. I've highlighted in green here all the bits of the scenarios that are exactly the same. The bits that are left over, the white bits, well, they're the values we had in the original table. All this extra text makes it a very hard job to actually get to the essential bits of these scenarios. It's so much repetition we lose focus and can easily miss the important details between the scenarios. But what if we could write this test so it looks more like the original table? It turns out we can using a thing called a scenario outline. You could easily spot a scenario outline because it has the words Scenario Outline at the top instead of Scenario. When you use the Scenario outline, you replace the bits of the scenario that change. The test data, with tokens, like these pointy bracket things here, Sample Drink and Points Earned, and these tokens are used to tell Cucumber where to inject the test data from the Examples table. The actual test data goes in this Examples table that you can see down here at the bottom. And the values and the columns are injected into the placeholders for each row. So here we would have a single scenario being executed for Regular smoothies, and then when you add more rows, you effectively add more tests. One test for each row. So in this table we now have four tests, one for each type of drink.

Working with Feature Files

Now let's look at some of the tricks that you could use when it comes to writing feature files in your IDE. Back in our project, I'm going to create a features directory to hold our feature file, and now I'm going to create a subdirectory for the loyalty card feature. I'm going to call this loyalty_cards. Now inside this subdirectory, I'm going to create the feature file that corresponds to our feature, our super_smoothies-_program feature. Let's open up this feature file and see what goes into it. Now the first line is our title, Super Smoothies Loyalty Card, Loyalty Card Program, that will do nicely. Then we can write a little description to describe what this feature is about. This is a little summary, and we can write any free text we want. Now normally I'd probably write a little bit more, but here let's keep it simple. Now the scenario that I'd like to write is the requirement about earning points when you purchase smoothies, and I want to make this a scenario outline because I want to compare the different points that you earn based on the type of drink that you buy and the number of drinks that you buy. So, I'm going to start with a scenario outline. Earning points when purchasing smoothies. Now the steps would be something like this: Given Michael is a morning Freshness member. Now this is a scenario outline, so I'm going to try and express a table first and then put the actual steps where Michael purchases the quantity of drinks and then earns a certain number of points. So let's see what the table would look like. First of all, I want to say I want a Drink type, so I want him to order an apple and banana smoothie, for example, I want him to order a certain quantity, and I want him to earn a number of points. So his first smoothie will be a banana smoothie. He'll just order 2 of them, and that will earn him 15 points. He'll next of all, he's going to order a triple blend, Triple Berry Blend, which he'll order 1 of those and that'll get him 20 points, and he wants to order an Earl Grey tea as well, 3 of them, and at 10 points each, he's going to make 30 points. I'm going to reformat this so that it formats nicely, and I'm going to add the steps that I made. When Michael purchases a Quantity of drinks, then he should earn a certain number of points. Now for this scenario to make sense, I really need to know what category each drink belongs to. I could put this in a Given, but if I were to have multiple scenarios, I might also put it in a background section. And here, for the sake of diversity and to show you how the background sections work, let's add it to a background section. So I'm going to add a Background section here, Given the following drink categories, and here I'm going to have a table with drinks by category and the number of points each of them earns. Banana is a Regular smoothie, it earns 15 points. Triple Berry Blend is a Fancy smoothie, and it earns 20 points. Earl Grey is a tea, and that's worth 10 points. Now this scenario and this feature are ready to automate, and in the next section, we'll learn about how to write the glue code, the automation code, which will turn this feature into an executable specification. In this section, we've covered all the key features of the Gherkin language. We've talked about the role of Gherkin as a business-readable specification language, but also as something we can automate as automated acceptance criteria. We've looked at the basic Given-When-Then structure that you see in the Gherkin scenario. And we've also seen how to represent tabular data inside your Given-When-Then steps, and how you can use background steps to avoid duplication. And we've seen how to use scenario outlines to describe multiple examples of the same requirement, and how this lets you compare and contrast the behavior for different values. In the next section, we're going to take one step further and have a look at how to turn these executable specifications into automated acceptance tests. We're going to look at the glue code.

Glue Code: Automating Scenarios

Test Runner Classes In this module, we'll learn how to automate our Gherkin scenarios, and in particular, how we can pass test data from our scenarios into our test code. We'll look at the layers that go into a well-designed Cucumber test automation framework. We'll learn how to write classes that run your Cucumber feature files, and we'll learn about the expression language that Cucumber uses to allow us to automate our Gherkin scenarios. A well-written Cucumber test automation framework is organized into several layers to make the automation code easier to understand and to maintain. At the top layer, we have our executable specifications written in Gherkin, the Given-When-Then notation that you just saw in the previous module. These describe our scenarios, along with the preconditions and expected outcomes for each scenario. Each scenario in this step gets executed using test automation code written using Cucumber. We call this code glue code, and its job is to coordinate and orchestrate how we will interact with the application under test. Unless the test automation for project is very, very small, such as a tutorial or a demo, the glue code shouldn't actually manipulate the application itself. This would make the code very hard to maintain. For example, we shouldn't actually execute web driver or make REST calls or query databases. If you put this sort of thing into your glue code, the glue code quickly becomes complex and messy and hard to maintain. Instead, the glue code coordinates classes and methods in a test domain layer, which models how we interact with the application, and this is where we actually do our web driver or REST API calls. To actually run the scenario using Cucumber with Java, we write a class called a test runner. This is a special kind of JUnit class which reads your feature files and then finds the glue code that it needs to execute them. You can configure a single test runner to run one or many feature files. Let's see what a test runner class actually looks like. In fact, a test runner is just an empty class, as we can see here, with a few annotations. The first one, RunWith Cucumber, tells JUnit to hand over control to Cucumber when it runs the tests. And the second one lets us specify configuration options such as where Cucumber should look for the feature files in the glue code. There are quite a few options that you can configure with the Cucumber runner, but probably the most important are the glue and features options. The glue option lets you define the package where Cucumber will look for your step definitions. By default, this will be the same package as your feature file, but many teams find it more readable to place their features in a meaningful directory structure under the src/test/features directory rather than in a Java package structure. And the features option lets you define just this. In this case, you tell Cucumber to look for feature files on the class path under the features package.

Glue Code: Cucumber Expressions

Cucumber has two ways to associate your scenarios with your test code and to pass data between your scenarios and your automation methods. You can either use regular expressions, which was actually the only way we could do it up until Cucumber 4, or you can use Cucumber expressions, a newer, more readable option that's available from Cucumber 4 onwards. In this section, we'll be looking at Cucumber expressions. Suppose that we have a scenario like this one, Given I have ordered 9 smoothies. The way Cucumber works is it will try and match that text, the last part of the expression, I have ordered 9 smoothies, and it'll try and find a method in your code to run for this text. We call these methods step definition methods. They go in a class in your test code, and they're marked with annotations like this, Given, or When, or Then, and these annotations include the text of the scenario step that we want to match. So in this case, you can see it's matching I have ordered 9 smoothies. We call this text the step definition expression. But matching the exact expression like this is a bit rigid. What if we want to use another number? What if we want to say 8 smoothies or 10 smoothies? We'd need a separate method for that, and that's not very convenient. Fortunately, Cucumber gives us many ways to make our step definitions more flexible. Step definitions can have variable bits that can be passed as parameters to the step definition method. In this case, 9 is a variable. It's an integer, so we can represent it by this expression, curly bracket, int, that you can see here. This is called a Cucumber expression. It's one of the ways that Cucumber lets you define parts of the step definition that you need to pass through to your method. When Cucumber executes this step, it will pass the number 9 into your step definition method as an integer parameter. Cucumber expressions also allow us to pass different types of parameters into our step definition methods. Suppose that we want to pass a name of the product, Green Goodness Smoothies. In this case, the parameter would be a string. We would tell Cucumber it needs to capture a string using the curly bracket string expression, and Cucumber will expect this string to be enclosed in quotes, single or double quotes will work fine, but these quotes won't be included in the parameters. So in string order here, all you will get is Green Goodness Smoothie without the quotes. We can have more than one expression in the line, like here where we have both the number of smoothies and the type of smoothie that we want. In other cases, we want to capture an individual word embedded in a sentence without the quotes, like Gold here. You can use the word parameter to do this, capture a single word without whitespace and without the quotes. So here the word parameter will pass in Gold to the memberLevel parameter variable. We could also capture floating point numbers using the float expression. So here we've got $ 5.95, and we're going to match that to the float expression here, curly brackets, float, which will pass in the value to the cost parameter of our step definition method. Finally, we can use the anonymous matcher, which matches anything in a given position in the string. So here what I want to do is match everything between he orders a and Smoothie. In other words, the name of the smoothie. I don't need to enclose it in quotes. All I do is say he orders up curly brackets, Smoothie, and then this will get passed into our smoothie parameter.

Glue Code: Regular Expressions

In this section, we'll look at that other way of defining your glue code methods using regular expressions. Now just like Cucumber expressions, regular expressions are another way that allow you to match text inside your step definitions and to pass parameters into your step definition method. They're extremely powerful, and in fact they used to be the only way to isolate parameters in your scenario steps; however, they can be a little bit harder to read than Cucumber expressions. Regular expressions still come in very handy with Cucumber 4, as although Cucumber expressions tend to be more readable, there are still many things that you can only do with regular expressions. The other reason that you might prefer regular expressions is that at the moment, Cucumber expressions are not completely supported in all IDEs. Let's take a look at a few key regular expressions. Here are some symbols, the more basic, commonly used symbols that you come across in your regular expression. The first one, the hat symbol, indicates the start of a string. You can put this at the start of your regular expression so that Cucumber will know that you're using a regular expression and not a Cucumber expression. If you don't do this, Cucumber can sometimes get confused and not know which parser to use. And the dollar expression, the dollar symbol, indicates the end of the string. So, a regular expression in Cucumber is often encapsulated by a hat at the start and a dollar at the end, but probably the most commonly used expression is dot star. Now here we've got dot start surrounded by parentheses, but the actual regular expression is dot star. Dots represent any character and star represents any number of time. So dot star matches anything, including nothing at all. Dot plus is similar to dot star except that it requires at least one character. Here's an example. You can see the hat at the start and the dollar at the end, but we've also got this dot star in parentheses, which means that we'll match anything between the words order and smoothies. Now we could be a little bit more precise than that. For example, if we wanted to detect a sequence of digits using the d symbol or a sequence of word characters, which are either letters or digits, and we can do that using the \w. In both of these cases, you can see we need need a double backslash in Java. The actual regular expressions only have a single backslash. Here's another example. In this one, we want to match the number of smoothies, but also the type of smoothie. So the number of smoothies is an integer, so we could match that with \d+. And the type of smoothie, well that's a string which may include spaces, so we can just match that with a. *, and you notice here I'm not working with Cucumber expressions. We don't need to worry about the quotes. We can also use regular expressions to define slight variations on the same expression so that the variations all go to the same method. This helps make the scenarios more readable and the code more easy to maintain. For example, these sentences, Given I order a smoothie, Given I have ordered a smoothie, Given I have ordered a Apple smoothie, imagine we want all of them to map to the same step definition to make the code easier to maintain. So we can do this sort of thing in a number of ways. With regular expressions, one of the simplest ways is to use a question mark after a character and that makes it optional, so S? he will match either she or he, or an with a question mark after the n at the end will match either A or an. So these are useful little grammatical tricks that make your step definitions more flexible. But for more complex cases where you want to match orders or has ordered, what we need to do is to define what we call a non-capturing expression, and in this case, the question mark colon tells Cucumber that it won't be a parameter, but we want to match this expression, and the expression is order pipe has ordered, and that will match either order or has ordered. So here's an example. In this case, the first step definition, When he orders 2 Green Goodness smoothies, will match either she orders or he orders. It will also match she has ordered or he has ordered. So orders goes to our regular expression here that you can see is a non-captured expression with that question mark colon, and it will match either orders or has ordered. And then we have at the end, we might only order one smoothie, so we have a question mark after the s. That's probably enough regular expressions to get you up and running with Cucumber, and even keep you going for quite a while.

Glue Code: Working with Tables

Let's have a look at working with tables and lists in our step definitions. Here we have a very simple example, just a simple list of values. How would we pass this to our step definition? It turns out it's really easy. All we need to do is pass in a list of strings, and this will tell Cucumber to convert this table into a list of values And so in this case, we have Michael passing in his favorite flavors, which will turn into a list of strings. Tables and lists don't need any regular expressions or Cucumber expressions to match them in the glue code. All you need to do is to declare a parameter like this one as part of your method's signature. Now supposed that the next step is to list what today's daily specials are in a table like this one. This is not a list of strings, but it's actually a list of maps of strings where each map has a key title and a key flavor and the values are the corresponding values in each row. So we will see how to manipulate these maps later on in our coding demo, but for now, all you need to know is that you can pass this in as a list of maps of strings to strings and each map corresponds to one row in the table. We can also have steps which combine embedded parameters in our step definition text and tables, like this one where we pass in the member name, Michael, and the table of specials, which comes after it, and we only need a Cucumber expression, or regular expression, for the memberName. The table simply follows as a parameter in our method signature. Now imagine Michael also wants to know the calorie count for the specials we're proposing. We could present this as a map, like this one. Now notice how we are passing in the first column as a string, and this will be the map key, but then we pass the second column as an integer, the number of calories for each drink, and Cucumber will convert this format to a map of strings to integers. So here when we tell Cucumber that we have a map and not a list, it treats the first column as a key and the second column as the value. The other place that we use table notations is in scenario outlines. When we want to run a number of different examples through the same scenario, or like in this case where we've parameterized quantity and total and we want to run three examples, three scenarios about Michael ordering different numbers, different quantities of smoothies so that we can check that we calculate the total correctly. Now the great thing is that in Cucumber, there's no difference between the step definition for a scenario outline method and a normal scenario method. They're exactly the same, so we don't need to make any changes for this scenario to work either in the context of a scenario outline or a normal scenario step.

Writing Glue Code

Let's see what Cucumber step definitions look like in practice. First of all, we're going to need to create our test runner, and we'll create it in a package called loyalty_cards. Let's call out test runner SuperSmoothieLoyaltyCardProgramTestSuite, something like that. And this test suite is nothing special. It's exactly what we saw in the slides earlier on. We say RunWith Cucumber, and we give it a few options, some Cucumber options where we're going to tell it what feature file to look for. We want a feature file in the classpath in the features directory, and inside this features directory, we have our loyalty_cards subdirectory, and that's where we want Cucumber to look for our step definitions. Next we need to tell Cucumber where to look for the step definition classes themselves, the glue code, and we're just going to tell Cucumber to look at the com.Pluralsight .bdd package, so anything under this package is fair game for Given-When-Then annotations. Now let's run this test suite and see what it generates. And as you can see, it's listed a few of these scenarios as undefined, and it's gone and given us the step definition methods, or the template step definition methods for the Given-When-Then statements that we're going to need to automate our scenarios. Let's copy them and use them as the starting point for our glue code. First of all, I want to create a step definition class for our step definitions. We can create these in the same package as our test runner or we could create it in a subdirectory. It's really the glue code parameter which matters. Now let's copy the glue code into our step definition class and tailor it to our needs. First of all, let's fix the imports so that we get the proper annotations being imported, and then we can move to our first method, which has a data table. Now a data table is Cucumber's default mechanism for representing one of these embedded tables, but we could really think of this as a list of maps, and that'll be easier to work with. So we can replace this parameter with a list of maps going from string to string, and that will represent our drink categories. Now let's move on to the next step definition. Michael is a Morning Freshness Member. Now here it's just the raw text, but what we really mean by Michael is a Morning Freshness Member is we want to create a Morning Freshness member called Michael. So Michael is our parameter. So we want to make this into a variable expression. Now here I'm using a regular expression and I'm passing in the name. So now let's see what we need to replace in the When. So we already have an integer, an Apple and Kale drink, so we've hardcoded Apple and Kale, that's not so great, and Michael is hardcoded as well. So let's replace Michael by the name of the member. We will pass this in as a parameter called name. Let's replace the integer with a regular expression because here we're using regular expressions, and that's just a sequence of numbers, followed by a text block, which will be the name of our product. So we have three parameters in all. We have the name of the Morning Freshness Member, we have the number of drinks, and we have the name of the drink itself. But Michael might only purchase one drink, so what we'd like to do is make the s optional, which we'll do here with the question mark. Michael can purchase one smoothie or multiple smoothies. And our last step definition is Then he should earn however many points. Let's give this variable a meaningful name, and then we can leave it as a Cucumber expression. And then we can remove the redundant scenarios, and we have our complete step definition file for our Given, our When, and our Then. And back in our Gherkin code, we can see that IntelliJ can find these methods. When we hover over it, we can see the connection to those step definition methods.

Implementing Step Definition Methods

In this second part of our demonstration, we're going to look at how to implement some code in our step definition methods. We'll see how we convert the parameters coming in from Cucumber into useable code, and how we go from step definition code into our actual production design. The first thing that I want to do is to set up our drink categories. So I'm going to take this drink category map, and for each entry in the drink category, I want to extract the type of drink category and the other information that we get from the table. So first of all, we want to get the name of the drink, so we can say drinkCategory.get Drink, and then what else do we need? Well, we need the category, and the category that comes from the category column. So we say drinkCategory.get Category. And finally, we have the number of points you earn, and we do something similar, but we need to convert this value from a text, from a string to an integer. So that's why we're passing the integer from the points column. So now we have the three values of our table. We've taken our drinks category tables and we've gone and converted it into our three entries. And at this point, we can imagine what we would like to have as an API to set up our drink category. So this is the test domain layer that we talked about earlier. A nice way to write this might be something like this where we have a drinkCatalog and we add a drink to it, and so now we know that we need a drinkCatalog class. And we can go and create this drink catalog class. Let's put it in our loyalty_cards package, and next we can add the addDrink method, which has two parameters, the drink and the category. Now at this point, the implementation doesn't matter so much. We can just make a very simple implementation, an in-memory map, but in a real application, this might talk to a database or a web service, or call some sort of endpoint to interact with the real application. And with our drinkCatalog sorted, we can imagine our superSmoothieSchema system where we're going to need to add a number of points for each category. So we could imagine a method like this, a superSmoothieSchema object and a setPointsPerCategory. So let's create a class called SuperSmoothieSchema. Once again, this is the interface to our application code, and I'm going to create this class and create the method that goes with it. So you can see I'm driving my design from these step definition methods and discovering what interfaces I'd need to talk to my application and maybe even to discover what our application API might look like. So here I could imagine I need a points per category, which is going to need to be a map in this case. Again, we'll do a very simple implementation, but in a real application, this may well talk to a web service or some sort of database. Now we go to our second step, michael_is_a_Morning_Freshness_Member. Now we could imagine a class called MorningFreshnessMember, which would describe the Morning Freshness Members. And to create that, we might need to pass in the name of the Morning Freshness Member. So we're creating Michael as a new Morning Freshness Member. And now we just have to create a local variable for Michael of type MorningFreshnessMember, and then we can move to our next step and figure out what Michael needs to do, and that's what we're going to do in the next clip.

Implementing Step Definition Methods (Part 2)

In the previous clip, we implemented some glue code and some application code to create a few drink categories and to add a Morning Freshness Member, Michael, to our application. Now let's see how we can complete the scenario. We want to automate the bit where Michael orders a drink and where we check that the order has been successfully placed in our system. Michael here in this step is purchasing some drinks. We could represent this with a simple method called orders, Michael orders a drink, but he also orders a certain number of those drinks. So we pass in an integer amount and the name of the drink. And then in our Then statement, he should earn a certain number of points. We can do a simple assertion here. We say assert that Michael, and well, we need to know Michaels points, so we can say michael.getPoints. So I'm using AssertJ here, a fairly common assertion library, and I need Michael to be able to tell us how many points he has. So I have michael.getPoints, we want to assert that the number of points that Michael has is equal to the expected points. And now we've actually got a fully functional test. What we need to do is add the objects that we're using, our DrinkCategory and our SuperSmoothieSchema, and we can run that and see that our test fails, and this is to be expected. You can never trust a test that you haven't seen fail. We have hardcoded 0, so this code doesn't actually do anything yet. So let's make it actually do something. We could imagine that we need to be able to figure out how many points we earn for a particular number of drinks. So we can say well, we need to ask the schema how many points can you earn for this drink, and so we're going to need to know where these points are calculated. We need to pass in the SuperSmoothieSchema so that we can ask how many points is this drink worth. And that means that we have discovered that our schema object also needs a method, getPointsFor, and we need to implement this method. So again, this might be a REST call, or a database query, or an application call, but in our simple implementation, we're just going to query the map that we set up earlier, but we need to find the category of the drink because if we pass in the name of the drink, we don't know its category. So we've also discovered that our SuperSmoothieSchema will need to know about the catalog to know what category a particular drink is. So let's pass in the DrinkCatalog so that we can ask the DrinkCatalog what is the category for a given drink. So we could imagine a method, getCategoryOf drink. And so now we've discovered the method, we can implement it. And now we need to pass in the drink catalog to the SuperSmoothie object. We can add it as a constructor parameter. And this way, the SuperSmoothieSchema will know about the drink catalog. Now that we can calculate the points for a particular drink, let's modify our MorningFreshnessMember so that the MorningFreshnessMember keeps track of the number of points. So we need to keep track of the number of points as an integer, and each time we add the points for the drink, times the amount. And then finally, our getPoints method will return the number of points that our MorningFreshnessMember currently has. Now we need to update our MorningFreshnessMember constructor to pass in the superSmoothies, and we can run our test suite. And it looks like we have a failure, which is interesting because we weren't expecting that. This error message is telling us that we were expecting 15 in this first scenario, but we actually got 30. So what's going on here? Let's have a look at our Gherkin scenario again, and try and understand. Ah, can you see the error? We've made a mistake with the number of points. It should actually be 30 for 2 drinks, not 15. Let's run the test again now. And now we can see all of our scenarios pass. In this section, we learned about the different layers that go into a typical test automation framework and how test automation source code is organized in a typical Cucumber project. We also saw how to write step definition methods with annotations that match each line in a scenario, and we saw how to use Cucumber expressions and regular expressions to capture parts of these lines and pass them in as parameters. And we saw how to work with lists and maps to pass in lists and tabular data into our step definition methods. And we saw that as far as our step definition code goes, there really isn't a difference between scenario outlines and scenarios. We can use the same methods for both. In the next section, we will be looking at how we can organize our feature files so that they play a role, not just as automated acceptance criteria, but as real living documentation for your product.

Producing Living Documentation Reports with Cucumber and Serenity BDD

Writing Good Living Documentation In this module, we will be looking at a very important aspect of BDD and Cucumber, and one that is sometimes overlooked by teams who are too focused on using Cucumber simply as a test automation tool. We'll see how we can turn Cucumber features and scenarios into business-readable, automatically updated product documentation. We call this living documentation, and it is a great way to improve both the quality of the features you build, and to increase the confidence in what you deliver, and accelerate your delivery process. Let's take a look. We'll be looking at some of the key principles behind writing living documentation, including how to write effective feature and scenario names and how to add extra details in feature and scenarios descriptions, and also how we can generate living documentation reports using tools like Serenity BDD. Before we can look at how Cucumber helps us write more descriptive and better-quality living documentation, let's take a look at what it means to write a feature file in a style that can be used as both acceptance criteria and as product documentation. Now the first source of good-quality living documentation actually comes from the very scenarios you write. If your scenarios are hard to read, your living documentation will be subpar, no matter how well you organize and write your feature file. So, let's talk about how you can make sure your scenarios are top notch. Let's start with some general tips. Good quality living documents are expressed, first of all, in clear business specifications. They're not test scripts. Test scripts describe a sequence of actions, for example, and to evaluate a field click this button, and so on. It's just a sequence of actions that a tester needs to perform to verify a piece of software. But business specifications are quite different. They're more interested in describing the business rules and the business outcomes that a particular piece of functionality is supposed to achieve. So there's a lot less emphasis on the user interface, for example, and more emphasis on business data, business state, and business outcome. Because these executable specifications, this living documentation, aims to bridge the communication gap between business folk and the development team, and for this reason, they use a language the business can understand. They don't talk about database tables. They don't talk about technical jargon. They don't talk about input fields, or buttons, or drop-down lists. They are written in exactly the same business language that the business folk would use in their normal discussions about their business. And good documentation is self-explanatory. It should contain everything the business person needs or the developer needs to understand the problem that we're trying to solve. It should contain the inputs, the data, the background that you need to understand a problem, and describe the expected outcomes in a way that's clear and testable, but still in a way that's concise and focused enough to be easy to read and to understand. Now this may sound a little theoretical, a little vague, so in the rest of this module, we're going to be looking at how to write Cucumber features and scenarios that follow these principles. Let's look at this in the context of a new requirement. Sam, our smoothie owner, wants to make it easy for people who are paying attention to what they eat to choose smoothies that work well with their diet. For example, Peter here wants to watch his calories, so he'd like to know how many calories he's going to be consuming when he purchases a smoothie. He wants to see that, for example, an apple smoothie is low in calories, whereas an apple and pear smoothie has a little bit more, and an ice cream smoothie will definitely exceed his smoothie calorie quota for the day. The feature we need to build has to allow Peter to do this and more. To be successful, it really needs to encourage Peter to buy smoothies even if he's on a diet. We'd even like Peter to go and tell his other dieting friends about our new diet-friendly smoothie feature so that we can get their business as well. Remember that BDD always starts with a conversation. When the team talks about this requirement, they come up with a few business rules. This includes the fact that we give a calorie rating for each type of smoothie based on its calorie count. We call these flames. One flame for each certain number of calories a smoothie has. And members can opt for this new diet smoothie option. We don't give it to everyone. We let people choose, and if they do this, then well, they'll get to see the calorie ratings when they choose their smoothies. Finally, we want our dieting customers to be able to make educated choices, so we want to propose lower-calorie alternatives if they pick a high-calorie smoothie. And we can add some examples. For example, an apple and kale smoothie only has 160 calories, so that's a 1-flame smoothie, but our famous triple berry blend, well that one has 380 calories, so that's got a rating of 3 flames. And we can add examples of the other rules as well. For example, we can add the one where Diana is showing the flame count for the smoothies that she's looking at, and the one where we propose a healthier smoothie when Doug picks up a triple berry blend. In the next clip, we'll have a look at how we can turn these requirements into living documentation using Cucumber.

Feature and Scenario Names

Cucumber lets us use free text descriptions for features, scenarios, and even example tables, and this is a great way to provide extra details, extra background, or context about a requirement. Let's see how we can improve our diet-friendly smoothies feature using descriptions at different levels. Here we have our feature file. It's pretty basic at the moment. It just lists a title of the feature, and then the title of the scenarios, but we can do much better than this. We can add a whole block of text between the feature title and the scenario outline, and this is a free text description. There's no particular constraint on the format, and you can think of it really as the first paragraph in the chapter about this feature in your specification document. It's a great place to put any extra information that would help understand what this feature is really about. We can do the same trick with scenarios. So here we have out scenario outline for our first business rule about smoothies being organized into categories based on the number of calories. We can make this scenario easier to understand by adding a block of text between the scenario outline and the first Given-When-Then, as we can see here. Here we explain in just plain English terms what this scenario is actually about. Each category is represented by a number of flames, and so forth. We can even split example tables into multiple different subtables and give titles and descriptions to each of these. Supposed we wanted to mention that smoothies under 300 calories were part of our healthy smoothie range, whereas smoothies with 300 or more calories were part of our indulgent range. We could do this by expanding our table and adding a range column in addition to the flames column, just like you can see here. But we could make our scenario even more descriptive if we break this table up into smaller subtables. One for healthy smoothies and one for indulgent ones, and that would look something like this. We can even add a title to each of these so that we can have a title for our healthy smoothies table and another one to explain our indulgent smoothies. And we could take this even further and add a description for each of these so that each of our subtables describes a specific business rule that this requirement is about. Since large tables can become quickly very hard to read, this is a great way of presenting tables with many rows in still quite a readable manner. In the next section, we'll take a look at how to generate living documentation reports using Serenity BDD.

Feature and Scenario Descriptions

The first place to start when we write living documentation is the very names of the features and scenarios. These are the first things your reader sees, so it's really important to make sure that they are clear and understandable. This alone can make navigating through your living documentation a lot easier. A good starting point is to make sure that your feature names are in plain business language. Your list of features should read like a table of contents in a specification document. So each feature name and each feature file name should be immediately readable and relevant to the business reader. If you have more than a handful of feature files, don't stick them in a single flat file structure with a huge list of files. Put them into directories with meaningful names. What these names represent is up to you, but common choices include high-level features or capabilities, application modules, or business goals. Grouping features by project or QA-related concepts, such as releases, project milestones, or test types, is generally not a good idea because these structures will not be relevant for business folk, and they won't make much sense later on when this directory structure has to be used to represent the functional documentation for your product, and this is exactly what happens when we write living documentation. Every feature you write goes to building up this documentation of your product, so the structure should be built for long-term understanding, not for project-related questions. And don't put things that would make the feature names hard to read, such as identifiers, code, or abbreviations. As a rule of thumb, if you wouldn't want to see this in a table of contents of a functional specification document, it shouldn't really be in a feature file name. The best way to think about how to write good scenario names is to remember that they act like a table of contents for your feature. Reading through the scenario names gives you an overview of what the feature does. Scenario names can talk about business rules, or they can go through key examples. Sometimes, the business rule will be represented as a scenario outline, so adding many examples together that illustrate this same rule, and in that case, the business rule works well as a title for your scenario. Other times, you can convey the overall feature better if you walk through a list of concrete examples. In both cases, a great trick is to start with a general rule, or general high-level statement, and then flesh out the picture with counterexamples or more specific rules. So for diet-friendly smoothies, we could add rule cards, like the ones we have here, and use them as our scenario titles, and that would give us a feature file that looks a little bit like this one, but let's see how we can make this a little bit more descriptive.

Living Documentation with Serenity BDD

There are a few tools you can use to turn your Cucumber scenarios into readable living documentation reports. In this demo, we'll look at one of these, Serenity BDD. Serenity BDD is an open-source library that makes it easier for teams to write maintainable, well-structured automated acceptance tests in Java, and to produce readable living documentation that could be presented to stakeholders as functional documentation. You can learn more about Serenity BDD by looking at the documentation website at serenity- bdd.github .io or on the Serenity BDD project home page at serenity- bdd.info. To get Serenity BDD working with our project, we need to make a few changes to the project and the test runner configuration. First of all, we need to add serenity dependencies to our project, and we need to add a Serenity reporter, which comes in the form of a Maven plugin. Finally, we need to use a Serenity test runner instead of the plain Cucumber one we've been using so far. We'll start off by updating the Cucumber dependencies. We can keep the first one, Cucumber Java, but we'll update our JUnit dependency to the Serenity BDD core dependency that you can see here. The next thing we need to add is the Serenity Cucumber 4 library. This provides the integration between Serenity and Cucumber. Now Serenity BDD uses Cucumber 2 by default, so we need to exclude the Cucumber core library from the Serenity dependencies. If we don't do this, Maven will include both Cucumber 4 and Cucumber 2, which can cause some confusing results. We also need to add the Serenity Maven plugin to our POM file. This plugin generates the full Serenity report at the end of each build. The configuration you can see here will ensure that Serenity reports get generated whenever we run Maven verify. Finally, we need to update our test runner. In the current test runner, we're using the RunWith Cucumber runner, but for Serenity, we need to replace Cucumber with CucumberWithSerenity. Let's take a look at what happens when we run these scenarios now. Let's start off by looking at our POM file. Here, you can see we have the serenity.version, the serenity.maven .version, and the serenity.cucumber .version, which we've stored as properties to make maintenance a little bit easier. Next, in our dependency section, we can see we have the cucumber-java dependency, but we also have serenity-core, and also the serenity-cucumber4 library, and we're being careful to exclude the cucumber4 library so that we don't get those version conflicts we talked about earlier. Further down, we can see the Maven, serenity-maven-plugin where we have the execution section, which is integrated into the post-integration-test, and it runs the aggregate goal. Now what this means in simple terms is that when you run Serenity, when you run Maven, verify it will generate the reports for you. Next, in our AcceptanceTestSuite, our test runner, we need to replace RunWith Cucumber with one with CucumberWithSerenity. Now we can run the full test Suite from the command line by going maven-clean-verify, and this will run all of our tests and also generate our living documentation report. The report gets generated in the target site, serenity directory, and if we go to this directory and we find the index.html, which we can find down here, we can open this file in a browser and have a look at the full Serenity BDD reports. This is the home page of the Serenity reports. You can see, as you'd expect, it gives a summary of the outcomes with the Passing, Pending, failing tests, and so forth. And if we click on Test Results, we can see the actual detailed results for our test run. But the real living documentation doesn't live in the test results, it lives in this Requirements tab where we can see our requirements structure based on our directory structure that we looked at earlier. We can see the number of scenarios and features that make up each requirement, and we can see the test results, whether it's passing, pending, or failing, for example. If we go into a feature, we see a rendered version of the feature file with the scenarios in each feature and the details of each scenario further down. This is our real living documentation. It combines the specifications with the executable results that you can see with those little green ticks on the side. And naturally, we can go into the details of any one of these scenarios to see what actually happened during the test result, see what the actual outcomes are of our living documentation. So what did we learn in this section? We saw how to get the most out of Cucumber as a living documentation tool. We saw how to structure your feature hierarchy in a meaningful way, and how to name your features and scenarios so that they can be used to understand the bigger picture behind a requirements document. We also saw how you can complement the Given-When-Then scenarios with text descriptions for features, scenarios, and even example tables, and how these can be used to provide extra information or background about a requirement. Finally, we looked at Serenity BDD, a open-source living documentation tool that can produce excellent living documentation reports from your Cucumber scenarios and feature file.