Invading the Product Landscape: A Metaphor

TIM Group development has gone through a major change over the last year. Our primary product has crossed over the peak of the adoption curve, and as a company we’ve been pushing new products for new markets. Our product development habits have had to change from techniques for fighting for market share to techniques for proving that a market exists. The human challenge is how do we move away from behavior that has been successful for the past several years?

One tool we’ve used to help make this shift inside our Product and Engineering teams has been a metaphor: Commando, Infantry, and Police. This metaphor has helped us explain how we needed to transform our product development ways, not because they were wrong, but because we now have a new mission.

While you can follow the Atwood link above (or follow from there to Cringely) to learn where we got our metaphor inspiration, the quick version of the metaphor is to compare a product capturing a market to an army capturing territory. The invading army will first send in Commando forces to secure a beachhead. Then, once the beachhead is established and high value targets are under control, you can send in the Infantry to exploit the the opportunity and take over the rest of the territory. When control over the territory is established, you can send in Police to keep the peace. To draw the comparison, “territory” is the new product landscape itself. First, you establish a way into the market by selectively solving the highest value problems in the product space. Then, with your first visionary customers happy, you can roll out more product features to bring in the early majority and start building out market share. As you gain dominance, you can focus product development more on keeping the existing market happy and building scale.

This has enabled us to rethink the role that we want our Product and Engineering teams to have while developing new products, as well as while sustaining our established core products. We expect different behaviors out of Commandos than we do the Police. Commando Devs and PMs should be ready to react quickly and make trade-offs to get the high value targets as quickly as possible. A new product could fail in these early stages, and we don’t want to be building out the whole product just to learn that the market isn’t interested. Moreover, what we learn building out the early product may help us to “pivot” and focus in a new product direction.

We were able to put a frame of reference around our previous product experience. We were an Infantry organization. We had focused on high efficiency, smooth logistics, and regular pace to keep our product expansion in order. By contrast, with our focus on new products, we need to react quickly (and likely erratically) to take advantage of market opportunities when they arise.

See Part 2 in the series about the trade-offs we have made and how we have been “lean”.

Narrative-style testing with Bildungsroman

Bildungsroman, n. A novel whose principal subject is the moral, psychological, and intellectual development of a usually youthful main character.

Narrative is a framework we developed at TIM Group in order to make it easier to write acceptance-level tests in Java. An example Narrative test looks like this:

@Test public void
adds_two_numbers() {
    Given.the( operator).was_able_to( press('2'))
                        .was_able_to( press('+'))
                        .was_able_to( press('2'));

    When.the( operator).attempts_to( press('='));

    Then.the( operator).expects_that( the_displayed_value(), is("4"));
}

One essential idea here is that tests are broken up into “Given”, “When” and “Then” phases. Another is that when we are writing behaviour-driven tests we are often concerned with the actions of some particular agent – a user, whose credentials and preferences affect how the system will respond to their requests. Finally, this style of testing involves writing re-usable definitions of the various operations a user can perform. We trade off the slight cumbersomeness of extracting test steps into definitions against the usefulness of having these definitions available for re-use at a later stage.

Bildungsroman is an adaptation of the Narrative framework for use in Scala projects. In Bildungsroman, the above example might look like this:

describe("A calculator") {
  it("adds two numbers") {
    calculatorContext.verify(for {
      _ <- givenThe(operator) { presses('2') }
                   .andThen   { presses('+') }
                   .andThen   { presses('2') }

      _ <- whenThe(operator)  { presses('=') }

      _ <- thenThe(operator)  { seesTheDisplayedValue('4') }
    } yield ())
  }
}

There is some additional noise here, imposed by the use of a for-comprehension to thread the steps together, but the fundamental concepts remain the same.

The step definitions for the above code might look like this:

val calculatorRef = Ref[Calculator]("the calculator")

def presses(key: Char) = { operator: User =>
  calculatorRef.map(_.press(operator, key))
}

def seesTheDisplayedValue(display: String) = { operator: User =>
  calculatorRef.map(_.readDisplay(operator))
}

A calculatorRef is a key into the context that all steps share. A Ref is a type-safe way to interact with this context, either by reading a value from it or by writing a value to it. Here’s an example, taking two numbers from the context and writing their sum back into the context:

for {
  n1 <- numberRef1
  n2 <- numberRef2
  sum  <- numberRef3 := (n1 + n2)
} yield sum

Note the use of the := operator to assign a value to the slot indicated by the Ref. Refs have map and flatMap defined on them, which enables them to be assembled together into for-comprehensions like the above. They are in fact the building-blocks of a state monad.

The state monad is an abstraction which enables us to thread a context through a series of operations that each have the opportunity to “modify” that context. An important point here is that the context’s data is in fact immutable: operations “modify” it by returning a new, updated context, rather than by mutating the context that is passed to them. This gives us the ability to do “imperative programming in the small”, or to model mutable variables in an immutable environment.

In the case of the calculator example above, we need a calculator object to hand in order to carry out the steps of pressing buttons and reading the display. This object is supplied through the context, which we pre-populate:

val calculatorContext = Context(calculatorRef -> new Calculator())

calculatorContext.validate(canAddTwoNumbers)

An interesting point here is that we can define a single Narrative-style test, such as canAddTwoNumbers, and execute it against multiple contexts – a useful technique for testing invariants, e.g. the toy calculator and the scientific calculator both add numbers in the same way.

Contexts are also designed for re-use and extension. Suppose we have a standard context for writing tests involving users and groups:

val userGroupContext = Context(
  userRepositoryRef -> new UserRepository(),
  groupRepositoryRef -> new GroupRepository()
)

If we want to extend that context to include a Roles repository, we can use the original userGroupContext as a basis:

val userGroupAndRoleContext = userGroupContext.extend(
  roleRepositoryRef -> new RoleRepository()
)

Equally importantly, narratives themselves compose. The type of a narrative test which finally yields a value of type A is GWTState[A]. If we have two tests, one which yields an A and one which yields a B, we can compose them in a for-comprehension to get a test yielding (A, B), which has the type GWTState[(A, B)] and can in turn be composed with other tests:

for {
  valueOfTypeA <- testYieldingA
  valueOfTypeB <- testYieldingB
} yield (valueOfTypeA, valueOfTypeB)

The second test will inherit the context returned by the first, and return its own modified context. This offers a powerful way of composing larger test steps out of smaller ones.

Bildungsroman is free and open source, hosted at the youDevise/TIM Group GitHub account, and ready for hacking. We’ll be experimenting with it over the coming weeks to see if it offers a good Scala substitute for the benefits we used to get by using Narrative in Java, – readability of tests, re-usability of actions and a structured framework for behaviour-driven test development.