Architecture & Agility: 05 - BDD

Core|UI & Behaviour Driven Development

In this article I want to demonstrate how simple BDD can be if the architecture is set up beneficial for that.

Modules, Modules, Modules…

This architectures simplicity when it comes to testing stems from the fact that everything in it can be treated as an independent module, with a module having one input, at maximum one output and a DSL to encode commands.

Because of its simplistic structure any of those modules is easily testable.

I don’t want this article to be a complete reference about what BDD is, for this scope I will just say it is like TDD but combines it with formalised acceptance testing. To achieve this, tests (or at least their skeleton) are not provided by the developers themselves, but by whoever commissioned a code or their representatives (product owner [PO]). At least in theory. In my experience customers and POs don’t want to be bothered with code, so I assume you would help your PO to create this.

As those tests are not only a tool for developers, some distinguish between developer test and BDD test by calling the later Specifications or Specs. I will do so too.

An Example

Let’s say, we are in a meeting with our product owner. He describes the TODO app we are about to write.

His description of a todo item:

Every item has

  • a custom text,
  • a random id,
  • a creation date, set to when ever it was instantiated,
  • a completion status (default: false),
  • a due date (default: nil),
  • an alter date (default: nil), recording the last time this item was altered

We now express this as executable tests.

Again, I will use Quick & Nimble for this. actually Q&N strength is to be used in BDD, I chose it for this.

First, we describe the default

class TodoItemSpec : QuickSpec {
    override func spec() {
        describe("TodoItem") {
            let orig = TodoItem(text:"Hey Ho")
            context("default") {
                it("has custom text"          ) { expect( orig.text         ) == "Hey Ho"    }
                it("has random id"            ) { expect( orig.id           ).toNot(beNil()) }
                it("is not completed"         ) { expect( orig.completed    ) == false       }
                it("has the creation date set") { expect( orig.creationDate ).toNot(beNil()) }
                it("has no due date set"      ) { expect( orig.dueDate      ).to   (beNil()) }
                it("has no alter date set"    ) { expect( orig.dueDate      ).to   (beNil()) }
            }
        }
    }
}

As we have not implemented any of this, this doesn’t compile, we now need to implement the TodoItem with the minimal effort.

A hint: if you don’t want to implement everything and then test it the first time, comment out test till you can actually use them. A test here is a single it("…") clause.

Let’s write the code to make it("has custom text") succeed, first we comment out the test that can’t compile yet:

class TodoItemSpecs: QuickSpec {
    override func spec() {
        describe("TodoItem") {
            let orig = TodoItem(text:"Hey Ho")
            context("default") {
                it("has custom text"          ) { expect( orig.text         ) == "Hey Ho"    }
//                it("has random id"            ) { expect( orig.id           ).toNot(beNil()) }
//                it("is not completed"         ) { expect( orig.completed    ) == false       }
//                it("has the creation date set") { expect( orig.creationDate ).toNot(beNil()) }
//                it("has no due date set"      ) { expect( orig.dueDate      ).to   (beNil()) }
//                it("has no alter date set"    ) { expect( orig.dueDate      ).to   (beNil()) }
            }
        }
    }
}

The minimal code to succeed is

struct TodoItem {
    let text: String
}

Now we want to implement the minimal code to have it("has random id") succeed, which we achieve by:

struct TodoItem: Identifiable {
    let text: String
    let id = UUID()
}

And to make it("is not completed") succeed:

struct TodoItem: Identifiable {
    let text: String
    let id = UUID()
    let completed = false
}

We continue till all default test succeed:

struct TodoItem: Identifiable {
    let text: String
    let id = UUID()
    let completed = false
    let creationDate = Date()
    let dueDate: Date? = nil
    let alterDate: Date? = nil
}

Now the instantiation of a TodoItem and its default values is fully described and implemented.

But of course, this is quite useless, we need to do something with it.

Let’s implement alteration

We start with the spec again, and we add the alter context and the rules to test if checking off an item works:

class TodoItemSpecs: QuickSpec {
    override func spec() {
        describe("TodoItem") {
            let orig = TodoItem(text:"Hey Ho")
            context("default") {
                it("has custom text"          ) { expect( orig.text         ) == "Hey Ho"    }
                it("has random id"            ) { expect( orig.id           ).toNot(beNil()) }
                it("is not completed"         ) { expect( orig.completed    ) == false       }
                it("has the creation date set") { expect( orig.creationDate ).toNot(beNil()) }
                it("has no due date set"      ) { expect( orig.dueDate      ).to   (beNil()) }
                it("has no alter date set"    ) { expect( orig.alterDate    ).to   (beNil()) }
            }
            context("altering") {
                let new = orig.alter(.completed(true))
                context("check") {
                    it("changes completed"           ) { expect( new.completed == true ) != orig.completed }
                    it("doesnt changes the id"       ) { expect( new.id           ) == orig.id           }
                    it("doesnt changes the text"     ) { expect( new.text         ) == orig.text         }
                    it("changes the alterDate"       ) { expect( new.alterDate    ).toNot(beNil()) ; expect( orig.alterDate ).to(beNil()) }
                    it("doesn't change the due date" ) { expect( new.dueDate      ).to   (beNil()) ; expect( orig.dueDate   ).to(beNil()) }
                    it("doesn't change creation date") { expect( new.creationDate ) == orig.creationDate }
                }
            }
        }
    }
}

This can’t compile as our TodoItem has no alter function yet. We add it, but we also have to learn that we cannot handle the default values as in the earlier approach. We will add a public init that only takes the text and a private init that takes all parameters.

Our next TodoItem version looks like:

struct TodoItem: Identifiable {
    let text: String
    let id: UUID
    let completed: Bool
    let creationDate: Date
    let dueDate: Date?
    let alterDate: Date?

    enum Change {
        case complete(Bool)
    }

    init(text: String) {
        self.init(text: text, id: UUID(), completed: false, creationDate: Date(), dueDate: nil, alterDate: nil)
    }
    private init(text: String, id: UUID, completed: Bool, creationDate: Date, dueDate: Date?, alterDate: Date?) {
        self.text = text
        self.id = id
        self.completed = completed
        self.creationDate = creationDate
        self.dueDate = dueDate
        self.alterDate = alterDate
    }
    func alter(_ change:Change) -> TodoItem {
        switch change {
        case .complete(let completed): return TodoItem(text: text, id: id, completed: completed, creationDate: creationDate, dueDate: dueDate, alterDate: alterDate)
        }
    }
}

We run it and all it-tests succeed, except it("changes the alterDate"), we change alter to deal with this.

We simply hide the alterDate property in alters scope:

func alter(_ change:Change) -> TodoItem {
    let alterDate = Date()
    switch change {
        case .complete(let completed): return TodoItem(text: text, id: id, completed: completed, creationDate: creationDate, dueDate: dueDate, alterDate: alterDate)
    }
}

Now all tests succeed.

But of course our PO wants TodoItem to do more, for example we also want to be able to set a completed item to be uncompleted.

class TodoItemSpecs: QuickSpec {
    override func spec() {
        describe("TodoItem") {
            let orig = TodoItem(text:"Hey Ho")
            context("default") {
                // ...
            }

            context("altering") {
                let new = orig.alter(.completed(true))
                context("check") {
                    it("changes completed"           ) { expect( new.completed == true ) != orig.completed }
                    it("doesnt changes the id"       ) { expect( new.id           ) == orig.id           }
                    it("doesnt changes the text"     ) { expect( new.text         ) == orig.text         }
                    it("changes the alterDate"       ) { expect( new.alterDate    ).toNot(beNil()) ; expect( orig.alterDate ).to(beNil()) }
                    it("doesn't change the due date" ) { expect( new.dueDate      ).to   (beNil()) ; expect( orig.dueDate   ).to(beNil()) }
                    it("doesn't change creation date") { expect( new.creationDate ) == orig.creationDate }
                }
                context("uncheck") {
                    let orig = new
                    let new = orig.alter(.complete(false))
                    it("changes completed"           ) { expect( new.completed    ) != orig.completed    }
                    it("doesnt changes the id"       ) { expect( new.id           ) == orig.id           }
                    it("doesnt changes the text"     ) { expect( new.text         ) == orig.text         }
                    it("changes the alterDate"       ) { expect( new.alterDate    ) >  orig.alterDate!   }
                    it("doesn't change the due date" ) { expect( new.dueDate      ).to (beNil()) ; expect( orig.dueDate ).to(beNil()) }
                    it("doesn't change creation date") { expect( new.creationDate ) == orig.creationDate }
                  }
            }
        }
    }
}

We run it and we see: We don’t need to change anything

But of course PO wants our users to be able to change the text:

class TodoItemSpec : QuickSpec {
    override func spec() {
        describe("TodoItem") {
            let orig = TodoItem(text:"Hey Ho")
            context("default") {
                it("has custom text"          ) { expect( orig.text         ) == "Hey Ho"    }
                it("has random id"            ) { expect( orig.id           ).toNot(beNil()) }
                it("is not completed"         ) { expect( orig.completed    ) == false       }
                it("has the creation date set") { expect( orig.creationDate ).toNot(beNil()) }
                it("has no due date set"      ) { expect( orig.dueDate      ).to   (beNil()) }
                it("has no alter date set"    ) { expect( orig.alterDate    ).to   (beNil()) }
            }
            context("altering") {
                let new = orig.alter(.completed(true))
                context("check") {
                    // ...
                }
                context("uncheck") {
                    // ...
                }
                context("change text") {
                    let new = orig.alter(.text("Let's Go"))
                    it("changes the text"            ) { expect( new.text         ) == "Let's Go"        }
                    it("doesnt change completed"     ) { expect( new.completed    ) == orig.completed    }
                    it("doesnt changes the id"       ) { expect( new.id           ) == orig.id           }
                    it("changes the alterDate"       ) { expect( new.alterDate    ).toNot(beNil()) ; expect( orig.alterDate ).to(beNil()) }
                    it("doesn't change the due date" ) { expect( new.dueDate      ).to   (beNil()) ; expect( orig.dueDate   ).to(beNil()) }
                    it("doesn't change creation date") { expect( new.creationDate ) == orig.creationDate }
                }
            }
        }
    }
}

To succeed with these tests we need to add a text case to the Change enum and pattern match for that in alter

enum Change {
    case text(String)
    case completed(Bool)
}

func alter(_ change:Change) -> TodoItem {
    let alterDate = Date()
    switch change {
        case .text     (let text     ): return TodoItem(text: text, id: id, completed: completed, creationDate: creationDate, dueDate: dueDate, alterDate: alterDate)
        case .completed(let completed): return TodoItem(text: text, id: id, completed: completed, creationDate: creationDate, dueDate: dueDate, alterDate: alterDate)
    }
}

Also dueDate:

class TodoItemSpec : QuickSpec {
    override func spec() {
        describe("TodoItem") {
            let orig = TodoItem(text:"Hey Ho")
            context("default") {
                // ..
            }
            context("altering") {
                let new = orig.alter(.completed(true))
                context("check") {
                    //..
                }
                context("uncheck") {
                    // ...
                }
                context("change text") {
                    //...
                }
                context("due date") {
                    let new = orig.alter(.due(Date.tomorrow.noon))
                    it("doesn't change the due date" ) { expect( new.dueDate      ).toNot(beNil()) ; expect( orig.dueDate ).to(beNil()) }
                    it("doesnt change the text"      ) { expect( new.text         ) == orig.text         }
                    it("doesnt change completed"     ) { expect( new.completed    ) == orig.completed    }
                    it("doesnt changes the id"       ) { expect( new.id           ) == orig.id           }
                    it("changes the alterDate"       ) { expect( new.alterDate    ).toNot(beNil()) ; expect( orig.alterDate ).to(beNil()) }
                    it("doesn't change creation date") { expect( new.creationDate ) == orig.creationDate }
                }
            }
        }
    }
}

Again, we need to add a case to the Change enum and pattern match for that in alter:

enum Change {
    case text(String)
    case completed(Bool)
    case due(Date?)
}

func alter(_ change:Change) -> TodoItem {
    let alterDate = Date()
    switch change {
        case .text     (let text     ): return TodoItem(text: text, id: id, completed: completed, creationDate: creationDate, dueDate: dueDate, alterDate: alterDate)
        case .completed(let completed): return TodoItem(text: text, id: id, completed: completed, creationDate: creationDate, dueDate: dueDate, alterDate: alterDate)
        case .due      (let dueDate  ): return TodoItem(text: text, id: id, completed: completed, creationDate: creationDate, dueDate: dueDate, alterDate: alterDate)
    }
}

We see that we have 2 aspects under test and specified now: creation and altering of TodoItems.

Now PO reports that users are asking for the functionality that you dub “forking”: creating new independent items from existing once with the ability to change some of the data on the fly. Forking is considered a non-altering action, so the original and the forked item need to have the same alterDate.

We first implement the forking without any changes:

class TodoItemSpec : QuickSpec {
    override func spec() {
        describe("TodoItem") {
            let orig = TodoItem(text:"Hey Ho")
            context("default") {
                /* ... */
            }
            context("altering") {
                context("uncheck") {
                  /* ... */
                }
                context("change text") {
                  /* ... */
                }
                context("due date") {
                  /* ... */
                }
                context("forking") {
                    let orig = orig.alter(.due(Date.tomorrow.noon))
                    context("without altering") {
                        let fork = orig.alter(.fork)
                        it("changes the id"              ) { expect( fork.id           ) != orig.id           }
                        it("doesn't change the alterDate") { expect( fork.alterDate    ) == orig.alterDate    }
                        it("doesn't change the text"     ) { expect( fork.text         ) == orig.text         }
                        it("doesn't change completed"    ) { expect( fork.completed    ) == orig.completed    }
                        it("doesn't change the due date" ) { expect( fork.dueDate      ) == orig.dueDate      }
                        it("doesn't change creation date") { expect( fork.creationDate ) == orig.creationDate }
                    }
                }
            }
        }
    }
}

We change TodoItems Change enum and alter methods to

enum Change {
    case text(String)
    case completed(Bool)
    case due(Date?)
    case fork
}

func alter(_ change:Change) -> TodoItem {
    let alterDate = Date()
    switch change {
        case .text     (let text     ): return TodoItem(text: text, id: id,     completed: completed, creationDate: creationDate, dueDate: dueDate, alterDate: alterDate)
        case .completed(let completed): return TodoItem(text: text, id: id,     completed: completed, creationDate: creationDate, dueDate: dueDate, alterDate: alterDate)
        case .due      (let dueDate  ): return TodoItem(text: text, id: id,     completed: completed, creationDate: creationDate, dueDate: dueDate, alterDate: alterDate)
        case .fork:                     return TodoItem(text: text, id: UUID(), completed: completed, creationDate: creationDate, dueDate: dueDate, alterDate: alterDate)
    }
}

But with this code it("doesn't change the alterDate") fails, we change alter to:

func alter(_ change:Change) -> TodoItem {
    let altered = Date()
    switch change {
    case .text     (let text     ): return TodoItem(text: text, id: id,     completed: completed, creationDate: creationDate, dueDate: dueDate, alterDate: altered)
    case .completed(let completed): return TodoItem(text: text, id: id,     completed: completed, creationDate: creationDate, dueDate: dueDate, alterDate: altered)
    case .due      (let dueDate  ): return TodoItem(text: text, id: id,     completed: completed, creationDate: creationDate, dueDate: dueDate, alterDate: altered)
    case .fork:                     return TodoItem(text: text, id: UUID(), completed: completed, creationDate: creationDate, dueDate: dueDate, alterDate: alterDate) // doesn't use the new altered date but read the property alterDate
    }
}

Now we want to implement the requirement that during forking any number of changes can be applied. To achieve this we start by implementing alter methods that can take more than one change.

func alter(_ changes: Change...) -> TodoItem { alter(changes) }
func alter(_ changes: [Change] ) -> TodoItem { changes.reduce(self) { $0.alter($1) } }

private func alter(_ change:Change) -> TodoItem {
    let altered = Date()
    switch change {
        case .text     (let text     ): return TodoItem(text: text, id: id,     completed: completed, creationDate: creationDate, dueDate: dueDate, alterDate: altered)
        case .completed(let completed): return TodoItem(text: text, id: id,     completed: completed, creationDate: creationDate, dueDate: dueDate, alterDate: altered)
        case .due      (let dueDate  ): return TodoItem(text: text, id: id,     completed: completed, creationDate: creationDate, dueDate: dueDate, alterDate: altered)
        case .fork:                     return TodoItem(text: text, id: UUID(), completed: completed, creationDate: creationDate, dueDate: dueDate, alterDate: alterDate)
    }
}

And now we adapt Change.fork to take a list of changes.

enum Change {
    case text(String)
    case completed(Bool)
    case due(Date?)
    case fork([Change])
}

We add tests to reflect changes during forking:

class TodoItemSpec : QuickSpec {
    override func spec() {
        describe("TodoItem") {
            let orig = TodoItem(text:"Hey Ho")
            context("default") { /* */ }
            context("altering") { /* */ }
                context("uncheck") { /* */ }
                context("change text") {
                    let new = orig.alter(.text("Let's Go"))
                    it("changes the text"            ) { expect( new.text         ) == "Let's Go"        }
                    it("doesnt change completed"     ) { expect( new.completed    ) == orig.completed    }
                    it("doesnt changes the id"       ) { expect( new.id           ) == orig.id           }
                    it("changes the alterDate"       ) { expect( new.alterDate    ).toNot(beNil()) ; expect( orig.alterDate ).to(beNil()) }
                    it("doesn't change the due date" ) { expect( new.dueDate      ).to   (beNil()) ; expect( orig.dueDate   ).to(beNil()) }
                    it("doesn't change creation date") { expect( new.creationDate ) == orig.creationDate }
                }
                context("due date") { /* */ }
                context("forking") {
                    let orig = orig.alter(.due(Date.tomorrow.noon), .completed(true))
                    context("without altering") {
                        let fork = orig.alter(.fork([]))
                        it("changes the id"              ) { expect( fork.id           ) != orig.id           }
                        it("doesn't change the alterDate") { expect( fork.alterDate    ) == orig.alterDate    }
                        it("doesn't change the text"     ) { expect( fork.text         ) == orig.text         }
                        it("doesn't change completed"    ) { expect( fork.completed    ) == orig.completed    }
                        it("doesn't change the due date" ) { expect( fork.dueDate      ) == orig.dueDate      }
                        it("doesn't change creation date") { expect( fork.creationDate ) == orig.creationDate }
                    }
                    context("altering text") {
                        let fork = orig.alter(.fork([.text("Let's go")]))
                        it("changes the text"            ) { expect( fork.text         ) != orig.text         }
                        it("changes the id"              ) { expect( fork.id           ) != orig.id           }
                        it("changes the alterDate"       ) { expect( fork.alterDate    ) != orig.alterDate    }
                        it("doesn't change the due date" ) { expect( fork.dueDate      ) == orig.dueDate      }
                        it("doesn't change completed"    ) { expect( fork.completed    ) == orig.completed    }
                        it("doesn't change creation date") { expect( fork.creationDate ) == orig.creationDate }
                    }
                    context("altering completed") {
                        let fork = orig.alter(.fork([.completed(!orig.completed)]))
                        it("changes completed"           ) { expect( fork.completed    ) != orig.completed    }
                        it("changes the id"              ) { expect( fork.id           ) != orig.id           }
                        it("changes the alterDate"       ) { expect( fork.alterDate    ) != orig.alterDate    }
                        it("doesn't change the text"     ) { expect( fork.text         ) == orig.text         }
                        it("doesn't change the due date" ) { expect( fork.dueDate      ) == orig.dueDate      }
                        it("doesn't change creation date") { expect( fork.creationDate ) == orig.creationDate }
                    }
                    context("altering duedate") {
                        let fork = orig.alter(.fork([.due(orig.dueDate?.dayAfter)]))
                        it("changes the due date"        ) { expect( fork.dueDate      ) != orig.dueDate      }
                        it("changes the id"              ) { expect( fork.id           ) != orig.id           }
                        it("changes the alterDate"       ) { expect( fork.alterDate    ) != orig.alterDate    }
                        it("doesn't change the text"     ) { expect( fork.text         ) == orig.text         }
                        it("doesn't change completed"    ) { expect( fork.completed    ) == orig.completed    }
                        it("doesn't change creation date") { expect( fork.creationDate ) == orig.creationDate }
                    }
                    context("altering duedate and completed") {
                        let fork = orig.alter(.fork([.due(orig.dueDate?.dayAfter), .completed(!orig.completed)]))
                        it("changes the due date"        ) { expect( fork.dueDate      ) != orig.dueDate      }
                        it("changes completed"           ) { expect( fork.completed    ) != orig.completed    }
                        it("changes the id"              ) { expect( fork.id           ) != orig.id           }
                        it("changes the alterDate"       ) { expect( fork.alterDate    ) != orig.alterDate    }
                        it("doesn't change the text"     ) { expect( fork.text         ) == orig.text         }
                        it("doesn't change creation date") { expect( fork.creationDate ) == orig.creationDate }
                    }
                    context("altering text and duedate") {
                        let fork = orig.alter(.fork([.due(orig.dueDate?.dayAfter), .text("Let's go")]))
                        it("doesn't change the text"     ) { expect( fork.text         ) != orig.text         }
                        it("changes the due date"        ) { expect( fork.dueDate      ) != orig.dueDate      }
                        it("changes the id"              ) { expect( fork.id           ) != orig.id           }
                        it("changes the alterDate"       ) { expect( fork.alterDate    ) != orig.alterDate    }
                        it("doesn't change completed"    ) { expect( fork.completed    ) == orig.completed    }
                        it("doesn't change creation date") { expect( fork.creationDate ) == orig.creationDate }
                    }
                    context("altering completed and duedate") {
                        let fork = orig.alter(.fork([.due(orig.dueDate?.dayAfter), .completed(!orig.completed)]))
                        it("changes completed"           ) { expect( fork.completed    ) != orig.completed    }
                        it("changes the due date"        ) { expect( fork.dueDate      ) != orig.dueDate      }
                        it("changes the id"              ) { expect( fork.id           ) != orig.id           }
                        it("changes the alterDate"       ) { expect( fork.alterDate    ) != orig.alterDate    }
                        it("changes the text"            ) { expect( fork.text         ) == orig.text         }
                        it("doesn't change creation date") { expect( fork.creationDate ) == orig.creationDate }
                    }
                    context("altering text, duedate and completed") {
                        let fork = orig.alter(.fork([.due(orig.dueDate?.dayAfter), .text("Let's go"), .completed(!orig.completed)]))
                        it("changes the text"            ) { expect( fork.text         ) != orig.text         }
                        it("changes the due date"        ) { expect( fork.dueDate      ) != orig.dueDate      }
                        it("changes completed"           ) { expect( fork.completed    ) != orig.completed    }
                        it("changes the id"              ) { expect( fork.id           ) != orig.id           }
                        it("changes the alterDate"       ) { expect( fork.alterDate    ) != orig.alterDate    }
                        it("doesn't change creation date") { expect( fork.creationDate ) == orig.creationDate }
                    }
                    context("altering due, completed, fork") {
                        let fork = orig.alter(.fork([.due(orig.dueDate?.dayAfter), .completed(!orig.completed), .fork([])]))
                        it("changes the due date"        ) { expect( fork.dueDate      ) != orig.dueDate      }
                        it("changes completed"           ) { expect( fork.completed    ) != orig.completed    }
                        it("changes the id"              ) { expect( fork.id           ) != orig.id           }
                        it("changes the alterDate"       ) { expect( fork.alterDate    ) != orig.alterDate    }
                        it("doesn't change creation date") { expect( fork.creationDate ) == orig.creationDate }
                        it("doesn't change the text"     ) { expect( fork.text         ) == orig.text         }
                    }
                    context("altering due, completed, fork with altered text") {
                        let fork = orig.alter(.fork([.due(orig.dueDate?.dayAfter), .completed(!orig.completed), .fork([.text("Let's go")])]))
                        it("changes the due date"        ) { expect( fork.dueDate      ) != orig.dueDate      }
                        it("changes completed"           ) { expect( fork.completed    ) != orig.completed    }
                        it("changes the id"              ) { expect( fork.id           ) != orig.id           }
                        it("changes the alterDate"       ) { expect( fork.alterDate    ) != orig.alterDate    }
                        it("changes the text"            ) { expect( fork.text         ) != orig.text         }
                        it("doesn't change creation date") { expect( fork.creationDate ) == orig.creationDate }
                    }
                }
            }
        }
    }
}

Quite some of those tests will fail as we haven’t changed alter yet.

private func alter(_ change:Change) -> TodoItem {
    let altered = Date()
    switch change {
        case .text     (let text     ): return TodoItem(text: text, id: id,     completed: completed, creationDate: creationDate, dueDate: dueDate, alterDate: altered)
        case .completed(let completed): return TodoItem(text: text, id: id,     completed: completed, creationDate: creationDate, dueDate: dueDate, alterDate: altered)
        case .due      (let dueDate  ): return TodoItem(text: text, id: id,     completed: completed, creationDate: creationDate, dueDate: dueDate, alterDate: altered)
        case .fork     (let changes)  : return TodoItem(text: text, id: UUID(), completed: completed, creationDate: creationDate, dueDate: dueDate, alterDate: alterDate).alter(changes)
    }
}

Now our Specs tests succeeds again. And not only do our tests give us the confidence that we would be notified if a bug would break any of them, but we also have specifications describing our system easily understandable.

Xcode does map the it-closure to tests in its interfaces

xcode

Did you notice the number in the top left? We have implemented 90 tests so far — I didn’t even notice that.

In my example code you will see that I also deal with the requirement that changes with the same value as set before should not result into an altered item. I won’t cover it here, but it is a good example of how to incrementally describe the desired behaviour.

Next…

In the next article I want to implement a simple feature with you.

Part 6: Implementing an example feature

Discuss

Please share your thoughts.