Example Object

Context

You want to explore questions about domain objects that are in particular execution states.

Problem:

How do you create an object in a particular state to start a moldable development task?

Forces

— Concrete examples are needed for many purposes, such as documentation, testing, and explo- ration.

— Examples can be complex to set up.

— Unit tests consume examples, but they are only accessible if a test fails.

Solution:

Wrap examples as (instance) methods that optionally evaluate some tests (assertions), and return the example instance.

Steps

Each example may also use one or more examples as the initial setup for the new example. To start, you need a modified unit testing framework in which tests return the exercised fixture, namely, an example.

Examples

In GT, you create an example by defining a parameterless method that has a <gtExample> pragma and returns an object. Here is a simple example method that creates a fresh instance of the gtLudoGame class, asserts a few basic facts ( i.e. , that the game is not yet over, no one has won yet, and so on). It resembles a classical unit test in all respects except one: it returns the instance of the unit under test, i.e. , the game instance.

GtLudoGameExamples>>#emptyGame
	<gtExample>
	| game |
	game := self gameClass new.
	self assert: game isOver not.
	self assert: game winner equals: 'No one'.
	self assert: game currentPlayer name equals: 'A'.
	self assert: game playerToRoll.
	self assert: game playerToMove not.
	^ game
        

Unlike normal test methods, examples are designed to be composed. Below we see a second example, playerArolls6, that starts from the emptyGame example, rolls a 6, asserts a few facts, and returns the modified game instance.

GtLudoGameExamples>>#playerArolls6
	<gtExample>
	| game |
	game := self emptyGame.
	game roll: 6.
	self assert: game currentPlayer name equals: 'A'.
	self assert: game playerToRoll not.
	self assert: game playerToMove.
	self
		assert: (game tokensToMove collect: #name) asSet
		equals: { 'A'. 'a' } asSet.
	^ game
        

Here's a simple example that returns a parse node for the parsed string '3+4', and asserts that the result is not fully reduced (evaluated).

a3plus4
	<gtExample>
	| result |
	result := self parseExpression: '3+4'.
	self assert: result isReduced not.
	^ result
    

Unlike normal test methods, examples can be composed. For example, this example method performs an evaluation step on the previous example, and returns the fully reduced expression node, asserting that no more evaluation steps can be performed.

a3plus4is7
	<gtExample>
	| result context |
	context := self a3plus4 asContext.
	result := context step.
	self assert: result isSPLValue.
	self assert: result value equals: 7.
	^ result
    

Examples such as these can be embedded into notebook pages and used as moldable objects for further development tasks, or as documentation, as seen in Executable domain-driven design: the Ludo game case study.

Consequences

Examples can be run just like classical unit tests.

When an example fails, its dependent examples do not need to be run.

When an example succeeds, it can be inspected, used as a moldable object to start coding, or embedded as a live example snippet within a notebook page to illustrate some point.

When you are searching for usages of an API, not only do you find examples that illustrate the usage, but by running the example you obtain a live instance that you can explore.

Known Uses

Subtext supports example-centric programming by placing live examples at the focus of the development process. JExample is a Java-based testing framework in which tests return examples.

Related patterns

An example can be used as a Moldable Object to start a new exploration activity. Examples can also be embeddded within a Project Diary notebook page to illustrate a particular documentation point.