Writing a Failing Acceptance Test with Cucumber

This is the seventh post in the first Build-Measure-Learn cycle.
In the last post, I introduced the Hexagonal Architecture as regulative power driving the application towards a domain centric design. In this blog post, I describe the first implementation step. It covers the implementation of an acceptance test with Cucumber guided by the Hexagonal Architecture style. I will pick up the first scenario for a puncture of the automated tests. As a result, there will be a failing acceptance test.

Refinement of the Acceptance Criteria

In the previous post I described the acceptance criterion and some testing scenarios. Now, I am going to use the first test scenario as my starting point.

The acceptance test criterion looks like this:

Given the user has selected a text file
When he starts the import of the selected file
Then the file will be imported, and the text is in the system available

And the 1st test scenario looks like this:

Scenario 1: A single text passage in a text file
Given a text file which contains a single text passage
When the user starts the import of the given file
Then the file will be imported, and the text is in the system available

The scenario of successfully importing a text file with a single passage is still quite general. E.g. it does not take the different document formats into account. Therefore, I am going to refine the scenario to be more specific on the document format.

This is the refined scenario 1a:

Scenario 1a: A single text passage in a Word® document
Given a Word® file which contains a single text passage
When the user starts the import of the given file
Then the file will be imported, and the text is in the system available

The refinement has two important aspects. From the user or customer perspective the requirement is stated in terms that are more concrete. It has been clarified that a text file can be in Word® format. From the testing perspective, the scenario description has been improved because the test case is now more detailed.

Implementing Scenario 1a as Cucumber Test

I am going to describe the implementation of the first acceptance test. I will present the project setup and describe the initial Hexagonal Architecture draft with the various layers and their code elements. Before talking about the technical details of the application I am going to show the test setup.

Cucumber Artifacts

The implementation of an acceptance test with Cucumber is based on two artifacts, see [CucumberReference]. The feature file named simple-text-passage-in-word-document.docx defines the executable specification of the test. Additionally, the step definitions translate the text steps of the feature file into executable code.

The feature file looks like this:

Feature file named simple-text-passage.docx
Feature file named  simple-text-passage-in-word-document.feature

This feature file contains a single scenario in the style of the Behavior Driven Development, see [BDDWiki]. A scenario consists of the steps Given, When and Then. Cucumber uses Gherkin to describe the steps in the feature file, see [Gherkin].

The step definitions are implemented by a Scala class:

Step definitions implemented in Scala
Step definitions implemented in Scala

With the “Given” clause we set the system to a defined state. We identify the source of the file. In the “When” clause we execute the key action which is the subject under test. The “Then” clause contains the tests to ensure the expected outcomes. Here we expect that the save operation of the repository was called with a document.

The Example File

Beside the Cumber artifacts, there is the example Word® file, which contains a single passage. The Word® file is named simple-text-passage.docx.

Word file example simple-text-passage.docx with a single text passage
Word® file example simple-text-passage.docx with a single text passage

Implementation

The Project Setup

The following table is a summary of the technologies and tools, which I use in my implementation.

Technology Version  
Scala 2.11.11 A genius language for maximum productivity.
Sbt 0.13.13 Probably the best build tool one can use.
Scalatest 3.0.1 The best test framework for Scala.
Cucumber for Scala 1.2.5 A popular BDD framework.

For this project, I am using Scala as programming language and Sbt as build tool. Unit tests will be implemented with the help of the test framework Scalatest. Cucumber is a framework for Behavior Driven Design, it is used for the automation of the acceptance tests.

The code is available at GitHub, https://github.com/GeorgiosAndreadakis/TextAnalyzerPlatform.

Code Design

In the previous post I described a rough design for the implementation of the automated acceptance test. Domain-driven Design (DDD) builds the foundation of the design, which is illustrated by the following picture.

Hexagonal design for the first acceptance test
Hexagonal design for the first acceptance test

The layers of the Hexagonal Architecture are implemented as different Sbt modules: acceptancetests, application and domain. The module acceptancetests contains test code and is part of the Framework layer.

A Domain Service as an Interface to the Domain

The interface to the domain model is a domain service, implemented as Scala trait called DocumentImportService. It coordinates the collaboration of the repository and the parser.

Domain Service DocumentImportService
Domain Service DocumentImportService

Both collaborators, the repository and the parser, are given by the import context. The parser takes the input stream of the text file and creates a Document instance. The newly created Document instance is saved by the repository.

The trait uses an implementation construct named self-type annotation. It is a way to declare the dependencies required by the domain service, see [Self-Type Annotation].

The Document and the Subparts

The Document class represents a text or document in the system and acts as Aggregate root entity, see [Fowler_DDD_Aggregate]. A document consists of a list of subordinated elements which are modelled by class DocElement. This initial implementation of a data structure for subordinated elements is primitive but sufficient for the current needs.

Document class
Document class

Elements can be paragraphs, tables, lists, enumerations or pictures. To satisfy the current requirements I have implemented the DocElement and its specialisation Paragraph.

Class DocElement and case class Paragraph
Class DocElement and case class Paragraph

The Repository

To provide a storage location for Document aggregates I introduced a Repository. A Repository is another tactical DDD pattern, which provides the illusion of an in-memory collection of all documents. The trait DocumentRepository defines typical methods to add, remove, update and select instances of Document. At present, the DocumentRepository needs a method for saving a document.

Trait DocumentRepository
Trait DocumentRepository

The Application Layer

The application layer contains logic for the application issues. The domain logic should be the same for all applications. Whereas the application logic can vary from application to application, although the application logic is not bound to a concrete framework or library.

For the import, I am using the tactical DDD pattern Application Service. In general, Application Services are the clients of the domain model, responsible for use case flows, transactions and security. The DocImporter is an Application Service which is responsible for the import of a document into the system.

Application Service DocImporter
Application Service DocImporter

The importer is initialized with an implementation of the DocumentRepository and the DocumentParser. With those instances, it calls the domain service using an anonymous implementation of the DocumentImportContext.

The Framework Layer and the Acceptance Test

The framework layer communicates with the outside world. The components implemented there are bound to a concrete framework or library. For the first acceptance test, we need a test framework, also concrete implementations of the DocumentRepository and DocumentParser.

For the automation of the acceptance test, I am using Cucumber for Scala. In the above section named “Cucumber Artifacts and Example File” there is a first introduction to the implementation with Cucumber. Beside the Scala class SimpleTextPassageInWordFileStepDefs, there are also mock implementations for the repository and the parser, just enough for a failing test.

Mocks for Repository and Parser
Mocks for Repository and Parser

The mock for the repository is used to support the test. The field #savedCalled is used to check whether the save method is called. In addition, the field #doc stores the document instance which is given as parameter to the save method. This field is used then for further checks of a parsed document instance. You can see in figure “Step definitions implemented in Scala”, that both fields are used to check a successful import.

The mock for the document parser doesn’t create a new Document instance, it is returning just null. That’s the reason why the acceptance test fails.

What’s next?

In the next blog post I will fix the failing acceptance test. Furthermore, I will enhance the acceptance test with a more specific test condition. Besides checking the document parameter of the repository, I will also check for a paragraph with a specific text passage. Therefore, I will engage Apache Tika as Library, to be able to read the Word® file and the text passage.

Trade Mark Acknowledgements

“Word” is a registered trade mark of Microsoft Corporation in the United States.

Resources

[BDDWiki] Wikipedia; ‘Behavior-driven development’, <https://en.wikipedia.org/wiki/Behavior-driven_development>
accessed May 11th, 2017.

[CucumberReference] Reference Cucumber
<https://cucumber.io/docs/reference> accessed November 15th, 2017

[CucumberScala] Cucumber JVM-Cucumber Scala
<https://cucumber.io/docs/reference/jvm#scala> accessed August 14th, 2017

[Evans2003] Eric Evans; Domain Driven Design: Tackling Complexity in the Heart of Software (1st edition, Addison-Wesley 2003).

[Fowler_DDD_Aggregate] Martin Fowler, ‘DDD_Aggregate’;
<https://www.martinfowler.com/bliki/DDD_Aggregate.html>
accessed October 25th, 2017

[Odersky/Spoon/Venners 2016] Martin Odersky, Lex Spoon, and Bill Venners; Programming in Scala, A comprehensive step-by-step guide
(3rd edition, Artima Press 2016)

[Rose/Wynne/Hellesoy2015] Seb Rose, Matt Wynne, Aslak Hellesoy; The Cucumber for Java Book: Behaviour-Driven Development for Testers and Developers
(1st edition, Pragmatic Bookshelf 2015)

[Sbt] Sbt – The interactive build tool
<http://www.scala-sbt.org/> accessed July 26th, 2017

[Scala] The Scala Programming Language
<http://www.scala-lang.org/> accessed July 26th, 2017

[ScalaTest] ScalaTest – A testing tool for Scala and Java developers
<http://www.scalatest.org/> accessed August 14th, 2017

[Self-Type Annotation] Section 29.4 “Splitting modules into traits”
in [Odersky/Spoon/Venners 2016]

[TAP] Text Analyzer Platform – Gather, analyse and utilise information rich text!
<https://github.com/GeorgiosAndreadakis/TextAnalyzerPlatform>

Leave a Reply