Reading code with exercises and IntelliJ IDEA

This article was first published in NLJUG‘s Java Magazine. You can view an online copy here.

As developers, we read code more than we write it. Research has shown that “developers on average spend as much as 58% of their time comprehending existing source code” (The Programmer’s Brain – Felienne Hermans). And yet, we don’t practice reading code nearly as much as we do writing code. Let’s look at how to practice reading code and how IntelliJ IDEA can help.

Code Reading Club

In 2022, a friend invited me to join a Code Reading Club, based on the work of Felienne Hermans. We have monthly sessions where we take a sample of code and apply structured exercises to this code to try and make sense of it. I have really enjoyed our sessions, not only for the opportunity to practice reading code, but also because I find it very interesting to learn how other people read code. To start your own club, use the Code Reading Club resources. Or try the code reading exercises from Felienne’s book, The Programmer’s Brain.

While it’s possible to use these exercises in our daily work, developers mostly read code in the IDE.

Exploring an existing code base

When joining a new team or company, you need to quickly get up to speed with existing code bases. Obviously you can look at the project, but unfortunately not everything can be captured in the code.

It helps to have a teammate give an introduction to the application; a description of what the application does, its place in the landscape, relevant history and design choices, current plans (are you actively developing new features or mostly doing maintenance?), which parts of the application are changed more than others, as well as links to existing documentation and other relevant information. Also think about which stories a new team member can pick up to help them become familiar with the project.

As a new team member, check out all relevant repositories. Make sure you can build the project, run the tests and run the application locally. How to do this will hopefully be described in the README. If not, consider adding it. Running the project locally can help you to both see how the application works by trying it out, and make sure that you can test your changes when you are done.

Opening a project in the IDE allows you to search and navigate your codebase more easily, as well as use additional useful IDE features. Let’s dive into several powerful IntelliJ IDEA features you can use to quickly be productive.

Note: Keyboard shortcuts are provided for macOS and Windows/Linux respectively.

Project overview

Explore modules and packages in the Project tool window (⌘1 | Alt+1) to get an idea of the project. If you prefer a visual representation, IntelliJ IDEA can generate several types of diagrams for you, including UML class diagrams. Also consider drawing your own diagrams; this will help you retain the information better and lets you focus only on the information you need for the task at hand. 

To understand dependencies between modules, packages and classes, the Dependency Structure Matrix (DSM) can help. Open the DSM from the main menu using Code | Analyze Code | Dependency Matrix….

Dependency Structure Matrix

In this example the matrix displays modules in a project. As you can see in the legend at the top right, dependencies are marked in blue and the direction of the dependencies is marked in green and yellow; the module marked in green depends on the module marked in yellow. Visualising the dependencies can help us to get an overview of the application. If you prefer, you can also generate a Project Modules diagram. Note that this diagram only gives you one side of the equation (which modules the selected module depends on). 

Search & navigate

Use Search Everywhere (⇧⇧ | Shift+Shift) to find anything in your project, including IDE settings & features. Other useful navigation features include Recent Files (⌘E | Ctrl+E) or Recent Locations (βŒ˜β‡§E | Ctrl+Shift+E). Knowing how to search and navigate code in your IDE is helpful when working with existing code, so you can focus on the task at hand.

Looking at a slice of the application

Instead of looking at the whole code base, look at a specific slice of the application. For example, find the main method and see what the application does from there, find a specific endpoint and trace the code down to the database, or find the location where an error is thrown and trace the code back up to how you got there. 

Understanding a piece of code

At some point you’ll find a piece of code that you will need to understand, for example, to fix a bug or make necessary changes. If you’ve ever been overwhelmed by a piece of code without any idea how to start, this is where code reading exercises can help! Of course, your IDE can also provide you with handy features. For starters, IntelliJ IDEA provides you with hints about the code, like syntax highlighting, inlay hints and gutter icons.

One of the reasons code can be confusing is because of a lack of information. For example, you might not know the exact implementation of a class or method. While you can Jump to Source (βŒ˜β†“ | F4) to navigate to the relevant code, and use shortcuts to Navigate Back (⌘[ | Ctrl+Alt+Left Arrow) and Navigate Forward (⌘] | Ctrl+Alt+Right Arrow), it’s easy to get lost in a large code base. To see where a particular file is located in the project, use the crosshair icon at the top of the Project tool window.

Select Opened File

It is also possible to pull up additional information using Quick Documentation (F1 | Ctrl+Q) to show the Javadoc, Quick Definition (βŒ₯␣ | Ctrl+Shift+I) to see the code or Type Information (βŒƒβ‡§P | Ctrl+Shift+P) to determine which type is returned by an expression.

Did you know IntelliJ IDEA can help you work with regular expressions? You can mark a String as RegExp from Show Context Actions (βŒ₯⏎ | Alt+Enter), using Inject Language or Reference and selecting RegExp (or a specific flavor of regular expressions). From the Show Context Action menu, select Check RegExp to open a popup where you can check whether a certain String matches the regular expression. Place your String in the Sample field in the popup; a yellow warning sign will be shown if the String is incomplete, a red exclamation mark shows the String does not match, or a green check mark will indicate that the String matches the RegExp.

Check RegExp

The structure of the code

Code is not read from top to bottom. Code doesn’t run linearly either! Developers β€œscan” code looking for the parts you’re interested in. Using white space and formatting can help improve readability.

When you write code in IntelliJ IDEA, the code will be formatted automatically. If you encounter code that isn’t properly formatted, you can reformat it (⌘βŒ₯L | Ctrl+Alt+L).

You can collapse the code (βŒ˜β‡§- | Ctrl+Shift+Minus) to get an overview of a class without being overwhelmed with too many details. You can expand it again (βŒ˜β‡§+ | Ctrl+Shift+Plus) as needed. Other ways to get a quick overview of the code are to use the File Structure popup (⌘F12 | Ctrl+F12), or the Structure tool window (⌘7 | Alt+7). 

When reading code, it might help to play with it a little bit as you try to understand it. Remember to revert any changes you make!

Restructure

Restructure the code by moving code blocks around to match your mental model, preferred style or coding conventions. Use shortcuts to move a statement up (βŒ˜β‡§β†‘ | Ctrl+Shift+Up Arrow) or down (βŒ˜β‡§β†“ | Ctrl+Shift+Down Arrow). 

Refactoring

You can use refactoring to change the code to aid your understanding. Rename variables or methods (⇧F6 | Shift+F6), extract variables (βŒ₯⌘V | Ctrl+Alt+V) or extract methods (βŒ₯⌘M | Ctrl+Alt+M) and give them meaningful names, inline variables (βŒ₯⌘N | Ctrl+Alt+N) you don’t need. The most frequently used refactorings are available in the Refactor This menu (^T | Ctrl+Alt+Shift+T) . Additional refactorings or quick fixes might be available under Show Context Actions (βŒ₯⏎ | Alt+Enter).

Testing

Look at the tests for a piece of code to see what this code is supposed to do, using Navigate to Tests (βŒ˜β‡§T | Ctrl+Shift+T). Hopefully names of the tests will express the intended behavior of the application. This is useful for two reasons: 1. The tests will serve as executable documentation of the system. 2. Should the tests fail in the future, good names can help to quickly tell you what’s wrong, so you don’t spend more time than necessary analyzing failures. 

Debugging

Run code through the debugger to see if the actual behavior of the code matches your understanding. Place a breakpoint (⌘F8 | Ctrl+F8) in the code at the point where you want to observe its state. Run the test in debug mode (βŒƒD | Shift+F9); execution will halt when it hits the breakpoint. You can observe the state of objects & variables in the editor and in the debug window, and look at the call stack in the Debug tool window. Continue execution by either stepping into a method (F7) or stepping over a line (F8). 

The IntelliJ IDEA debugger is very powerful and has many useful features. I highly recommend familiarizing yourself with its features; this will come in handy next time you need to urgently debug your code for a production incident! 

Version control

Sometimes you might want to know how the code came to be the way it is, and do a bit of what I like to call β€œGit archeology”. Use Annotate with Git Blame to see when the code was last changed. Click the commit in the gutter to navigate to that commit in the Git tool window and look at the diff. If you’re using JetBrains AI Assistant [13], you can also ask it to explain the commit to you. Right-click the commit and select Explain Commit with AI Assistant from the context menu.

Explain Commit with AI Assistant

AI Assistant features to understand code

Finally, AI coding assistants, like JetBrains AI Assistant, may offer other useful features for understanding existing code. For example, chat with an AI Assistant and ask questions about software development, your project’s code and version control. Ask it to explain specific code to you, maybe even explain regular expressions, SQL queries, cron expressions, etc.

Because it is deeply integrated within IntelliJ IDEA, JetBrains AI Assistant can explain runtime errors right from the console. With another assistant you may need to copy-paste the error into a chat window. Alternatively, use an AI assistant to write documentation for a class or method. The generated Javadoc is often more concise than the explanation in the chat.

Conclusion

I hope you found this overview useful to see how to approach understanding existing code with the help of IntelliJ IDEA. This article is based on my talk β€œReading Code”, which includes live demos of the features discussed in this article.

Shortcuts

NamemacOSWindows/Linux
Project tool window⌘1Alt+1
Search Everywhere⇧⇧Shift+Shift
Recent Files⌘ECtrl+E
Recent LocationsβŒ˜β‡§ECtrl+Shift+E
Jump to SourceβŒ˜β†“F4
Navigate Back⌘[Ctrl+Alt+Left Arrow
Navigate Forward⌘]Ctrl+Alt+Right Arrow
Quick DocumentationF1Ctrl+Q
Quick DefinitionβŒ₯␣Ctrl+Shift+I
Type InformationβŒƒβ‡§PCtrl+Shift+P
Show Context ActionsβŒ₯⏎Alt+Enter
Reformat code⌘βŒ₯LCtrl+Alt+L
Collapse allβŒ˜β‡§-Ctrl+Shift+Minus
Expand allβŒ˜β‡§+Ctrl+Shift+Plus
File Structure⌘F12Ctrl+F12
Structure tool window⌘7Alt+7
Move Statement UpβŒ˜β‡§β†‘Ctrl+Shift+Up Arrow
Move Statement DownβŒ˜β‡§β†“Ctrl+Shift+Down Arrow
Refactor Rename⇧F6Shift+F6
Extract VariableβŒ₯⌘VCtrl+Alt+V
Extract MethodsβŒ₯⌘MCtrl+Alt+M
Inline VariableβŒ₯⌘NCtrl+Alt+N
Refactor This^TCtrl+Alt+Shift+T
Navigate to TestsβŒ˜β‡§TCtrl+Shift+T
Toggle Breakpoint⌘F8Ctrl+F8
DebugβŒƒDShift+F9
Step intoF7F7
Step overF8F8

Reading Code like a pro

As developers, we spend more time reading code than writing it, and this video provides tips to enhance your code-reading skills within the IntelliJ IDEA IDE. Learn how to leverage features like syntax highlighting, inlay hints, and code formatting to navigate and understand code effortlessly. Discover techniques to quickly scan code, collapse and expand sections for efficient navigation, and use powerful search functionalities to locate specific elements.

Whether you’re a beginner or an experienced developer, these tips will empower you to read and comprehend code with confidence, making your coding journey in IntelliJ IDEA a seamless and productive experience.

Links

Overview of IntelliJ IDEA 2023

IntelliJ IDEA is designed to help developers like us stay in the flow while we’re working. Like all IDEs, it has a lot of functionality available, but it’s designed to get out of your way to let you focus on the code.

Take a look at this overview of IntelliJ IDEA.

Introduction

  • Find Action: ⌘ ⇧ A (on macOS) / Ctrl+Shift+A (on Windows/Linux)
  • Feature Trainer
  • Hide all windows: ⌘ ⇧ F12 (on macOS) / Shift+Command+F12 (on Windows/Linux)
  • Project tool window: ⌘1 (on macOS) / Alt+1 (on Windows/Linux)
  • Quick Switch Scheme: ^`(on macOS) / Ctrl+` (on Windows/Linux)
  • IDE viewing modes
  • Preferences: ⌘, (on macOS) / Ctrl+Alt+S (on Windows/Linux)

Coding assistance

  • Code completion
  • Complete Current Statement: ⌘ ⇧ ⏎ (on macOS) / Shift+Ctrl+Enter (on Windows/Linux)
  • Show Context Actions: βŒ₯ ⏎ (on macOS) / Alt+Enter (on Windows/Linux)
  • Intention actions
  • Navigate to next highlighted error: F2
  • Navigate to previous highlighted error: Shift F2
  • Generate code: ⌘ N (on macOS) / Alt + Insert (on Windows/Linux)
  • Live templates

Refactoring

  • Rename: Shift F6
  • Extend selection: βŒ₯ Up (on macOS) / Ctrl+W (on Windows/Linux)
  • Extract variable: ⌘ βŒ₯ V on macOS) / Ctrl+Alt+V (on Windows/Linux)
  • Postfix completion
  • Reformat code: ⌘ βŒ₯ L (on macOS) / Ctrl+Alt+L (on Windows/Linux)
  • Move statement up: β‡§βŒ˜ Up (on macOS) / Ctrl+Shift+Up (on Windows/Linux)
  • Surround with: ⌘ βŒ₯ T (on macOS) / Ctrl+Alt+T (on Windows/Linux)
  • SmartType Completion: ^ ⇧ SpaceΒ  (on macOS) / Shift+Ctrl+Space (on Windows/Linux)
  • Inline: ⌘ βŒ₯ N (on macOS) / Ctrl+Alt+N (on Windows/Linux)
  • Extract method: ⌘ βŒ₯ M on macOS) / Ctrl+Alt+M (on Windows/Linux)

Testing & Debugging

Navigation

  • Navigate backwards: ⌘ [ (on macOS) / Ctrl+Alt+Left (on Windows/Linux)
  • Navigate forwards: ⌘ ] (on macOS) / Ctrl+Alt+Right (on Windows/Linux)
  • Find usages / declaration: ⌘ B (on macOS) / Ctrl+B (on Windows/Linux)
  • Recent Files: ⌘E (on macOS) / Ctrl+E (on Windows/Linux)
  • Recent locations: β‡§βŒ˜E (on macOS) / Ctrl+Shift+E (on Windows/Linux)
  • Search everywhere: ⇧⇧ (on macOS) / Shift Shift (on Windows/Linux)
  • Find in files: β‡§βŒ˜F (on macOS) / Ctrl+Shift+F (on Windows/Linux)

Reading Code

  • Folding -> Expand: ⌘ + (on macOS) / Ctrl+ + (on Windows/Linux)
  • Folding -> Collapse: ⌘ – (on macOS) / Ctrl+ – (on Windows/Linux)
  • Folding -> Expand All : ⇧ ⌘ + (on macOS) / Ctrl+Shift+ + (on Windows/Linux)
  • Folding -> Collapse All: ⇧ ⌘ + (on macOS) / Ctrl+Shift+ – (on Windows/Linux)
  • File Structure: ⌘ F12 (macOS) / Ctrl+F12 (Windows/Linux) – Twice to expand list
  • Quick documentation: F1 (macOS) / Ctrl+Q (Windows/Linux)
  • Toggle Rendered View:Β  ^ βŒ₯ Q (macOS) / Ctrl+Alt+Q (Windows/Linux)

Version Control support (Git)

  • Commit: ⌘ 0 (macOS) / Alt+0 (Windows/Linux)
  • Jump to last tool window: F12
  • Show diff: ⌘ D (macOS) / Ctrl+D (Windows/Linux)
  • Commit Anyway and Push: βŒ₯ ⌘ K (on macOS) / Ctrl+Alt+K (on Windows/Linux)
  • Git tool window: ⌘9 (on macOS) / Alt+9 (on Windows/Linux)
  • Terminal: βŒ₯ F12 (on macOS) / Alt+F12 (on Windows/Linux)
  • Git integration

Language and technology support

Integrated tools support

Use Testing to Develop Better Software Faster

This blog post was first published on the 97 Things blog on Medium and is published in the book β€œ97 Things Every Java Programmer Should Know” (O’Reilly Media).

Book cover of β€œ97 Things Every Java Programmer Should Know", which includes "Use Testing to Develop Better Software Faster".
Book cover of β€œ97 Things Every Java Programmer Should Know”

Testing your code will help you verify your code does what you expect it to do. Tests will also help you to add, change, or remove functionality, without breaking anything. But testing can have additional benefits.

Merely thinking about what to test will help to identify different ways the software will be used, discover things that are not clear yet, and better understand what the code should (and shouldn’t) do. Thinking about how to test these things before even starting your implementation could also improve your application’s testability and architecture. All of this will help you build a better solution before tests and code are written.

Alongside the architecture of your system, think not only about what to test but also where to test. Business logic should be tested as close as possible to where it lives: unit tests to test small units (methods and classes); integration tests to test the integration between different components; contract tests to prevent breaking your API; etc.

Consider how to interact with your application in the context of a test and use tools designed for that particular layer, from unit test (e.g., JUnit, TestNG), to API (e.g., Postman, RestAssured, RestTemplate), to UI (e.g., Selenium, Cypress).

Keep the goal of a particular test type in mind and use the tools for that purpose, such as Gatling or JMeter for performance tests, Spring Cloud Contract Testing or Pact for contract testing, and PiTest for mutation testing.

But it is not enough to just use those tools: They should be used as intended. You could take a hammer to a screw, but both the wood and the screw will be worse off.

Test automation is a part of your system and will need to be maintained alongside production code. Make sure those tests add value and consider the cost of running and maintaining them.

Tests should be reliable and increase confidence. If a test is flaky, either fix it or delete it. Don’t ignore it β€” you’ll waste time later wondering why that test is being ignored. Delete tests (and code) that are no longer valuable.

A failing test should tell you exactly what is wrong quickly, without you having to spend a lot of time analyzing the failure. This means:

  • Each test should test one thing.
  • Use meaningful, descriptive names. Don’t just describe what the test does either (we can read the code), tell us why it does this. This can help decide whether a test should be updated in line with changed functionality or whether an actual failure that should be fixed has been found.
  • Matcher libraries, such as HamCrest, can help provide detailed information about the difference between expected and actual result.
  • Never trust a test you haven’t seen fail.

Not everything can (or should) be automated. No tool can tell you what it’s actually like to use your application. Don’t be afraid to fire up your application and explore; humans are way better at noticing things that are slightly β€œoff” than machines. And besides, not everything will be worth the effort of automating.

Testing should give you the right feedback at the right time, to provide enough confidence to take the next step in your software development life cycle, from committing to merging to deploying and unlocking features. Doing this well will help you deliver better software faster.

Managing state in Cucumber-JVM using Spring

This article was first published onΒ Medium.

A frequently asked question about Cucumber-JVM is how to share state between steps. This post describes how to use Spring with Cucumber to help you share objects between steps, without accidentally sharing state between scenarios (a common cause of flaky scenarios).

First of all, why not use static variables?

A solution often seen to share variables or objects between steps, is to declare static variables at the top of the step definitions file. However, there are several downsides this approach:

  1. As your project grows, you’ll probably want to divide your step definitions into different classes, divided into meaningful groups. At that point, you can no longer use the static variables declared in one step definition file.
  2. Using static variables might cause your tests to accidentally leak state into other scenarios, making them unreliable. Test automation is only any good if it is reliable.

Using Dependency Injection

The recommended solution to share state is to use Dependency Injection (DI). Cucumber supports several DI frameworks, including PicoContainer, Spring and Guice.

Using Dependency Injection with Cucumber will bring you several benefits:

  • You can safely share state between your steps
  • It will help improve the design of your test code; making it easier to share state between different step definition classes (so you can split them into meaningful groups).
  • It will create and manage instances of classes for you.

Choosing a Dependency Injection framework

If your project already uses Dependency Injection, you could use the framework you already have. If not, consider using PicoContainer; it’s very light-weight and easy to use (at least, this is my personal experience in side projects).

Since my team already uses Spring in our projects, we’ve opted to use Spring with Cucumber.

Adding the cucumber-spring dependency

In order to use Spring with Cucumber, we need to add a cucumber-spring dependency to our pom.xml:

<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-spring</artifactId>
<version>2.4.0</version>
<scope>test</scope>
</dependency>

Other dependencies

If you are using Spring and Cucumber in your project, you will already have those dependencies. Otherwise, you should add those as well.

Annotation based configuration

If you are using annotation based configuration, you can register your classes as Beans to be managed by Spring:

  • Add the @Component annotation to each of the classes you’d like Spring to manage for you.
  • Add an annotation to specify the scope: the @Scope("cucumber-glue"), in order to have cucumber-spring remove them after each scenario!
  • Add the location of your classes to the @ComponentScan of your (test) configuration.
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan("your.package")
public class Config {
}

This approach works for any classes that you have defined yourself. This may include:

  • domain objects (like a β€œCustomer” or an β€œAccount”)
  • any other step definition files (if you have split your files)
  • any β€œservices” or β€œhelper” classes you extract to represent parts of the system your tests interact with (like PageObjects, REST API’s, etc.). The steps can then call these services/helpers, instead of each other.

If you are using classes that you have not defined yourself, you cannot annotate them as Components for Spring to find with the component scan. You will have to explicitly register them as Beans in your (test) configuration.

Here is an example, using Selenium WebDriver:

import org.openqa.selenium.WebDriver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;

@Configuration
@ComponentScan("your.package")
public class Config {
    @Bean
    @Scope("cucumber-glue")
    public WebDriver webDriver() {
        // return a driver with desired capabilities
    }
}

In our latest projects, we are using SpringBoot. We can annotate our StepDefinitions class with @SpringBootTest(classes = [Config.class]);.

XML configuration

Alternatively, you can specify your beans in a .cucumber.xml file, like this. Here is an example (again using Selenium WebDriver; this could also be one of your own classes):

<bean class="org.openqa.selenium.WebDriver" scope="cucumber-glue" />

You can annotate your StepDefinitions with @ContextConfiguration("classpath:cucumber.xml").

Using the Beans

You can now use the Beans, by autowiring them where you need them.

For example, you can Autowire your WebDriver to your PageObject:

import org.openqa.selenium.WebDriver;
import org.springframework.beans.factory.annotation.Autowired;

public class PageObject {
    @Autowired
    WebDriver driver;    // the rest of your page object
}

Conclusion

As you can see, it is relatively easy to add Cucumber-Spring to manage state. You are now able to safely share state between your steps, as well as split your step definition files into meaningful groups.

This will make your project easier to understand and maintain, and your tests more reliable.

Getting started with Cucumber in Java β€” A 10 minute tutorial

This article was first published onΒ Medium.

This tutorial will tell you how to get started with Cucumber-jvm in Java. It is intended as a brief, easy guide. For more examples on how to use Cucumber with Java or Kotlin, check the links at the bottom of this tutorial.

Prerequisites

To get started with Cucumber in Java, you will need the following:

  • Java SE β€” Java 8 (Java 9 is not yet supported by Cucumber)
  • Maven β€” Version 3.3.1 or higher
  • An IDE editor, for example IntelliJ IDEA (which will be used in this introduction; the community edition is free!)
  • A Cucumber plugin for your IDE, for example IntelliJ IDEA Cucumber for Java plugin to go with IntelliJ IDEA

Setting up the project

First, we need to set up the project so we can use Cucumber.

Create a Maven project

In this tutorial, we’re using Maven to import external dependencies. We start by creating a new Maven project, which will automatically create some of the directories and files we will need.

To create a new Maven project in IntelliJ IDEA:

  1. Click the menu option File > New > Project
  2. In the New project dialog box, select Maven on the left (if it isn’t already selected)
  3. Make sure that the Project SDK is selected (for instance, Java 1.8) and click Next
  4. Specify a GroupId and ArtifactId for your project and click Next
  5. Specify a Project name and Project location for your project (if needed) and click Finish

You should now have a project with the following structure:

β”œβ”€β”€ pom.xml
└── src
β”œβ”€β”€ main
β”‚ └── java (marked as sources root)
β”‚ └── resources (marked as resources root)
└── test
└── java (marked as test sources root)

Add Cucumber to your project

Add Cucumber to your project by adding a dependency to your pom.xml:

<dependencies>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-java</artifactId>
<version>2.3.1</version>
<scope>test</scope>
</dependency>
</dependencies>

In addition, we need the following dependencies to run Cucumber with JUnit:

<dependencies>
...
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-junit</artifactId>
<version>2.3.1</version>
<scope>test</scope>
</dependency> <dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>

If you have IntelliJ IDEA configured to auto-import dependencies, it will automatically import them for you. Otherwise, you can manually import them by opening the Maven Projects menu on the right and clicking the Reimport all Maven Projects icon on the top left of that menu. To check if your dependencies have been downloaded, you can open the External Libraries in the left Project menu in IntelliJ IDEA.

To make sure everything works together correctly, open a terminal and navigate to your project directory (the one containing the pom.xml file) and enter mvn clean test.

You should see something like the following:

[INFO] Scanning for projects...
[INFO]
[INFO] -------------------------------------------------------------
[INFO] Building cucumber-tutorial 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------- <progress messages....>[INFO] -------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] -------------------------------------------------------------
[INFO] Total time: <time> s
[INFO] Finished at: <date> <time>
[INFO] Final Memory: <X>M/<Y>M
[INFO] -------------------------------------------------------------

Your project builds correctly, but nothing is tested yet as you have not specified any behaviour to test against.

Specifying Expected Behaviour

We specify the expected behaviour by defining features and scenarios.

The feature describes (part of) a feature of your application, and the scenarios describe different ways users can use this feature.

Creating the Feature Directory

Features are defined in .feature files, which are stored in the src/test/resources/ directory (or a sub-directory).

We need to create this directory, as it was not created for us. In IntelliJ IDEA:

  1. In the Test folder, create a new directory called resources.
  2. Right click the folder and select Mark directory as > Test Resources Root.
  3. You can add sub-directories as needed. Create a sub-directory with the name of your project in src/test/resources/

Our project structure is now as follows:

β”œβ”€β”€ pom.xml             (containing Cucumber and JUnit dependencies)
└── src
    β”œβ”€β”€ main
    β”‚   └── java        (marked as sources root)
    β”‚   └── resources   (marked as resources root)
    └── test
        β”œβ”€β”€ java        (marked as test sources root)
        └── resources   (marked as test resources root)
                └── <project>

Creating a Feature

To create a feature file:

  1. Open the project in your IDE (if needed) and right-click on the src/test/resources/<project> folder.
  2. Select New > File
  3. Enter a name for your feature file, and use the .feature extension. For instance, belly.feature.

Our project structure is now as follows:

β”œβ”€β”€ pom.xml             (containing Cucumber and JUnit dependencies)
└── src
β”œβ”€β”€ main
β”‚ └── java (marked as sources root)
β”‚ └── resources (marked as resources root)
└── test
β”œβ”€β”€ java (marked as test sources root)
└── resources (marked as test resources root)
└── <project>
└── belly.feature

Files in this folder with an extension of .feature are automatically recognized as feature files. Each feature file describes a single feature, or part of a feature.

Open the file and add the feature description, starting with the Feature keyword and an optional description:

Feature: Belly
Optional description of the feature

Creating a Scenario

Scenarios are added to the feature file, to define examples of the expected behaviour. These scenarios can be used to test the feature. Start a scenario with the Scenario keyword and add a brief description of the scenario. To define the scenario, you have to define all of its steps.

Defining Steps

These all have a keyword (GivenWhen, and Then) followed by a step. The step is then matched to a step definition, which map the plain text step to programming code.

The plain text steps are defined in the Gherkin language. Gherkin allows developers and business stakeholders to describe and share the expected behaviour of the application. It should not describe the implementation.

The feature file contains the Gherkin source.

  • The Given keyword precedes text defining the context; the known state of the system (or precondition).
  • The When keyword precedes text defining an action.
  • The Then keyword precedes text defining the result of the action on the context (or expected result).

The scenario will look this:

Scenario: a few cukes
Given I have 42 cukes in my belly
When I wait 1 hour
Then my belly should growl

Running the test

To run the tests from JUnit we need to add a runner to our project.

Create a new Java class in your src/test/java/<project> directory, called RunCucumberTest.java:

package <project>;

import cucumber.api.CucumberOptions;
import cucumber.api.junit.Cucumber;
import org.junit.runner.RunWith;

@RunWith(Cucumber.class)
@CucumberOptions(plugin = {"pretty"})
public class RunCucumberTest{
}

Our project structure is now as follows:

β”œβ”€β”€ pom.xml             (containing Cucumber and JUnit dependencies)
└── src
β”œβ”€β”€ main
β”‚ └── java (marked as sources root)
β”‚ └── resources (marked as resources root)
└── test
β”œβ”€β”€ java (marked as test sources root)
β”‚ └── <project>
β”‚ └── RunCucumberTest.java
└── resources (marked as test resources root)
└── <project>
└── belly.feature

The JUnit runner will by default use classpath:package.of.my.runner to look for features. You can also specify the location of the feature file(s) and glue file(s) you want Cucumber to use in the @CucumberOptions.

You can now run your test by running this class. You can do so by right-clicking the class file and selecting RunCucumberTest from the context menu.

You should get something like the following result:

1 Scenarios (1 undefined)
3 Steps (3 undefined)
0m0.015s
You can implement missing steps with the snippets below:@Given("^I have (\\d+) cukes in my belly$")
public void i_have_cukes_in_my_belly(int arg1) throws Exception {
// Write code here that turns the phrase above into concrete actions
throw new PendingException();
}@When("^I wait (\\d+) hour$")
public void i_wait_hour(int arg1) throws Exception {
// Write code here that turns the phrase above into concrete actions
throw new PendingException();
}@Then("^my belly should growl$")
public void my_belly_should_growl() throws Exception {
// Write code here that turns the phrase above into concrete actions
throw new PendingException();
}
Process finished with exit code 0

As we can see, our tests have run, but have not actually done anything β€” because they are not yet defined.

Define Snippets for Missing Steps

We now have one undefined scenario and three undefined steps. Luckily, Cucumber has given us examples, or snippets, that we can use to define the steps.

To add them to a Java class in IntelliJ IDEA:

  1. Create a new Java class in your src/test/java/<project> folder (for instance, StepDefinitions.java)
  2. Paste the generated snippets into this class

IntelliJ IDEA will not automatically recognize those symbols (like @Given@When@Then), so we’ll need to add import statements. In IntelliJ IDEA:

  • Add import statements for @Given@When@Then (underlined in red)

In IntelliJ IDEA you can do so by putting your cursor on the @Given symbol and press ALT + ENTER, then select Import class.

Our project structure is now as follows:

β”œβ”€β”€ pom.xml             (containing Cucumber and JUnit dependencies)
└── src
β”œβ”€β”€ main
β”‚ └── java (marked as sources root)
β”‚ └── resources (marked as resources root)
└── test
β”œβ”€β”€ java (marked as test sources root)
β”‚ └── <project>
β”‚ └── RunCucumberTest.java
β”‚ └── StepDefinitions.java
└── resources (marked as test resources root)
└── <project>
└── belly.feature

Now, when you run the test, these step definitions should be found and used.

Note: Run configurations

If this does not work, select Run > Edit Configurations, select Cucumber java from the Defaults drop-down, and add the project name to the Glue field on the Configuration tab.

Your result will include something like the following:

cucumber.api.PendingException: TODO: implement me
at skeleton.Stepdefs.i_have_cukes_in_my_belly(Stepdefs.java:10)
at ✽.I have 42 cukes in my belly(/Users/maritvandijk/IdeaProjects/cucumber-java-skeleton/src/test/resources/skeleton/belly.feature:4)

The reason for this is that we haven’t actually implemented this step; it throws a PendingException telling you to implement the step.

Implement the steps

We will need to implement all steps to actually do something.

  • Update your StepDefinitions.java class to implement the step definition.

The step can be implemented like this:

@Given("^I have (\\d+) cukes in my belly$")
public void I_have_cukes_in_my_belly(int cukes) throws Throwable {
Belly belly = new Belly();
belly.eat(cukes);
}

To make this step compile we also need to implement a class Belly with a method eat().

  • Implement the class Belly.java inside your src/main/java/<project> folder; create your <project> directory here (if needed)

Our project structure is now as follows:

β”œβ”€β”€ pom.xml             (containing Cucumber and JUnit dependencies)
└── src
β”œβ”€β”€ main
β”‚ └── java (marked as sources root)
β”‚ β”‚ └── <project>
β”‚ └── Belly.java
β”‚ └── resources (marked as resources root)
└── test
β”œβ”€β”€ java (marked as test sources root)
β”‚ └── <project>
β”‚ └── RunCucumberTest.java
β”‚ └── StepDefinitions.java
└── resources (marked as test resources root)
└── <project>
└── belly.feature

Now you run the test and implement the code to make the step pass. Once it does, move on to the next step and repeat!

PendingException

Once you have implemented this step, you can remove the statement throw new PendingException(); from the method body. The step should no longer thrown a PendingException, as it is no longer pending.

Result

Once you have implemented all your step definitions (and the expected behaviour in your application!) and the test passes, the summary of your results should look something like this:

Tests run: 5, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.656 secResults :Tests run: 5, Failures: 0, Errors: 0, Skipped: 0[INFO] -------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] -------------------------------------------------------------
[INFO] Total time: 5.688 s
[INFO] Finished at: 2017-05-22T15:43:29+01:00
[INFO] Final Memory: 15M/142M
[INFO] -------------------------------------------------------------

Examples

To get started with a working project, try the cucumber-java skeleton project which is available from GitHub.

For more examples of how to use Cucumber, have a look at the examples provided in the cucumber-jvm project on GitHub.

If you’d like to try Cucumber with Kotlin, have a look at my blog post.

Note: This tutorial was originally written as part of the new Cucumber documentation. You can find the cucumber documentation project on GitHub. It was adapted a little here to make a stand-alone tutorial.

Take-aways from European Testing Conference 2018 β€”Β Do try this at home!

This article was first published on Medium.

This week I was lucky enough to attend European Testing Conference in Amsterdam. To get an impression of the conference, you can visit their website, read up on the #EuroTestConf hashtag on Twitter, or check any of the links at the end of this post.

In this blogpost, I’d like to share some of the take-aways from this conference. Do try this at home!

  1. β€œPainless Visual Testing” β€” Gojko Adzic

During the first keynote, Gojko Adzic likened visual testing to traveling with children: β€œalways more painful and expensive than expected”.

Tools can help you collect data, but cannot determine whether the result looks β€œgood”. We will often have to visually inspect the UI. Existing tests break when we makes changes to the UI.

To deal with this, Gojko introduced the idea of visual approval testing and gave a demo of a tool built for that purpose: https://github.com/AppraiseQA/appraise

It would be fun to play with this tool, or at least this idea, to see how this could help make visual testing easier.

An earlier version of this talk can be found here.

2. β€œHow Would You Test a Text Field?” β€” Maaret PyhΓ€jΓ€rvi

Maaret used the interview question β€œ How Would You Test a Text Field?” to generate ideas on how to test a text field, and illustrate how the types of answers people give indicate their level of test experience and mindset.

Some resources mentioned in the talk:

3. Writing Better BDD Scenarios β€” Seb Rose and GΓ‘spΓ‘r Nagy

During the workshop, Seb mentioned Example Mapping as described by Matt Wyne

4. β€œGenerating Test Scenarios” β€” Llewellyn Falco

Llewellyn showed us how to quickly increase test coverage by generating test cases. This is also something I’d love to play with!

Of course, he had to first show us his infamous sparrow deck:

Also, the BugMagnet tool was mentioned:

Having just recently heard of BugMagnet, I definitely plan to use this at work!

For more on approval testing: http://approvaltests.com/

5. β€œAutomating repetitive communication tasks with a Bot” β€” Pooja Shah

Pooja Shaj showed us chatbot Alice. You can find the repository on GitHub: https://github.com/moengage/alice

6. Interactive sessions

The conference made a point of being interactive; a lot of the insights came from great keynotes and workshops, as well as fellow attendees.

The interactive parts of the conference included a speed meet (talk to different people for 5 minutes each), lean coffee (facilitated discussion) and open space (free format to present, discuss or ask for help).

More on Lean Coffee: http://leancoffee.org/

One of the questions raised in our group was β€œHow to motivate developers to test?”. Apart from the obvious β€œmanaging programmers is like herding cats”, one of the ideas mentioned was to have a bug bash.

Wrap up

The conference ended with a retrospective.

As you can see, we had a lot of fun, learned a lot and went home with new ideas to try out!

Read more:

If you want to read more about the conference, check out the following (especially the sketch notes by @marianneduijst and @KatjaBudnikov):

Conferences as a change toolMaaret PyhΓ€jΓ€rvi

European Testing Conference 2018 – Coming HomeLisi Hocke

ETC 2018, it was simply awesomeAmit Wertheimer

ETC 2018, did I say it was awesome?Amit Wertheimer

European Testing Conference 2018Markus Tacker

What I learned on first day of European Testing Conference 2018 – Karlo Smid

Arena lifeSeb Rose

Jackpot!Seb Rose

European Testing Conference 2018 #EuroTestConfKatja Budnikov

Collection of Sketchnotes of #EuroTestConfMarianne Duijst