Categories
Uncategorized

Using TDD to start out with the MMF User Stories

Housekeeping: starting a new project

Old-timers might reminisce mvn archetype:generate. It’s too laborious in these times (updating pom compiler target by hand?) and we usually prefer our favorite IDE or Spring Initializr.

1. With intelliJ IDEA Community Edn, that’s: File > Create New Project > Gradle > Java

2. create a README.MD (Markdown wiki format):

$ cat <<EOT >> README.MD
//project antecedents and platform decisions

3. One could follow Spring Boot Gradle Plugin Reference to add Spring Boot to the project.

plugins { id 'org.springframework.boot' version '2.2.6.RELEASE' } 
apply plugin: 'java'
apply plugin: 'io.spring.dependency-management' 

Without Spring Boot, one would add these test dependencies in build.gradle. Applying the ‘io.spring.dependency-management’ plugin resolves the dependency versions from the Spring Boot dependencies BOM (using the properties defined there), so we omit versions:

dependencies {
 testImplementation('org.junit.jupiter:junit-jupiter-api') //5.4.2
 testImplementation('org.junit.jupiter:junit-jupiter-engine')
 testImplementation('org.assertj:assertj-core') //:3.15.0
 testImplementation('org.mockito:mockito-core') //:3.3.3
}

But with Spring Boot, you just need:
   testImplementation('org.springframework.boot:spring-boot-starter-test') {
   exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}

Also add the lombok plugin:
id 'io.freefair.lombok' version '5.0.0'

4. Setup version control with Git

a) create a .gitignore to avoid checking in .gradle etc, one directory or file per line:

$ cat <<EOT >> .gitignore
.gradle
.idea
gradle
build
lombok.config
EOT

b) initialize a git repo for the project, add all changes to staging and make initial commit:

$ git init
$ git add -A; git status
$ git commit -m “Initial commit.”

c) Make it safe to experiment and commit: create a new branch (checkout a tag) to get in ‘detached HEAD’ state:

$ git checkout -b TDD_Kindle_Clip_Parser #creates new branch
$ git status

Outside-In Approach

Let’s start by identifying high-level system interfaces, modules and boundaries. The domain suggests a Kindle device interface, a ClipFile reader/parsing module, a persistence module for storing clips across cycles, a driver module for selecting books and outputting to different formats, a module for each format to be exported, a Calibre interface module.

First Feature: As a user, I want to parse all the clips in my clip file so that I can import and process them later.

I start by parsing a single clip, add the Test for that:

@DisplayName("A ClipFile Parser")
public class ClipFileParserSpec {

  @Nested
  @DisplayName("parses")
  class ParseFileContent {

    @Test
    @DisplayName("a block of five lines into a valid Clipping")
    void parsesBlockOfFiveLinesIntoValidClippingObject() {
      ClipFileParser clipfileParser = new ClipFileParser();
      Clipping clip = clipfileParser.parseClip(scanner);
      assertAll("Should return a valid clipping object",
        () -> assertNotNull(clip, "Clipping object is null"),
        () -> assertNotNull(clip.documentTitle(), "Title missing"),
        () -> assertNotNull(clip.documentAuthor(), "Author missing"),
        () -> assertNotNull(clip.type(), "Clip type not set"),
        () -> assertTrue(clip.location()[0].isPresent(), "Start location missing")
      );
    }
  }
}

What I like most about this inner class mechanism for nesting, despite it’s clumsiness, is it makes the test result more readable and accessible by way of hierarchical presentation:

Test Results:
    * A ClipFile Parser
         * parses
              *  a block of five lines into a valid Clipping

When a test fails, it’s much more intuitive to decipher why.