ISpec

From IokeWiki
Jump to: navigation, search

ISpec is a testing framework designed to enable behavior-driven development which takes its inspiration from RSpec, a similar Ruby framework. The current Ioke test suite is completely written in ISpec, and it seems to be a capable environment.

Usage

A full test file utilizing most of the parts of ISpec looks like this:

use("ispec")

describe(Foo,
  it("should have the correct kind",
    Foo should have kind("Foo")
    Foo kind should == "Foo"
    Foo kind should match(#/Fo+/)
  )

  it("should be possible to mimic",
    m = Foo mimic
    m should have kind("Foo")
    m should not be same(Foo)
    m should mimic(Foo)
  )

  describe("aMethod",
    it("should not return nil",
      Foo aMethod should not be nil
    )

    it("should return a number that can be multiplied",
      (Foo aMethod * 2) should == 12
    )
  )

  describe("aBadMethod",
    it("should signal a condition",
      fn(Foo aBadMethod) should signal(Condition Error BadBadBed)
    )
  )
)

This code first makes sure to use ISpec, then describes Foo. The describe method takes either kinds or texts describing what's under test. This can be nested arbitrarily deep. A test is defined with the "it" method, which takes a text describing the test first, and the implementation of the test as the second argument. If the second argument is left out, the test is considered pending.

Assertions are made using the "should" method. This returns an expectation that can check several different things against the original receiver. Using == is the simplest expectation and checks that a value equals another. By adding the not method call inbetween, the expectation is inversed. There are some predefined expectations. Except for ==, these are mimic, match and signal. The signal expectation makes sure that a condition is signalled in the code. The match expectation will check a text value against a regular expression. The mimic expectation checks whether an object mimics another.

The words "be" and "have" are ignored in the expectations. They are so called fluff words - that are only there to make it more readable.

If an expectation receives a message it doesn't know about, it uses pass to check a dynamic property. For example, something like "foo should be same(x)" will end up calling "same?" with x as an argument. The same thing happens in the above code to check for kind. There exists no kind expectation. Instead the kind checking will go check for "kind?".

Mocking and stubbing

Ioke, as a prototype-based language, naturally supports replacement of cells for stubbing purposes. Sometimes, however, you want to do things like assert that particular cells are called a certain number of times, or ensure that the old values of cells are restored upon completion of a particular test. Enter mocking and stubbing.

ISpec supports the creation of mocks and stubs either on existing objects or through the creation of new objects designed to stand in for that purpose. In their simplest form, you can specify them as simple should assertions:

foo should receive bar twice

More complex cases allow you to specify key-value pairs:

foo should receive(bar: 5, baz: "qux")

Finally, you can get arbitrarily complex:

foo should receive(
  baz(6) andReturn(7) atLeastOnce, 
  qux never, 
  mu(foo: "bar") andReturn(6, 7, 8)
)

Failure to satisfy the expectations will result in a test failure, signaling ISpec UnexpectedInvocation.

See the specs for ISpec mocking for more detailed usage examples.

Running an ISpec file

ispec -fs test_dir

This command will find all files ending in _spec.ik and run the specs defined in them. If a single file is specified, only the tests in that file will run. If more than one directory or file is specified on the command line, all the tests will be run together. Provided the spec files all use the ispec module, the files can be run directly with the ioke-command too.

Other options:

-f Specify the output format. Currently three formats are supported: 
  - s[pecdoc] (textual output with example titles)
  - p[rogress] (one dot output for each test run)
  - h[tml] for use in continuous integration output and Textmate test runs.
-p Limit files loaded to those matching pattern.
-c Use colored output (default: true).
-e Only execute examples matching name.
-l Only execute examples defined at line_number.

Running ispec -h will provide more complete documentation.

Defining a custom assertion

All Ioke assertions are defined as cells on ShouldContext and NotShouldContext. The mimicked context receives a reference to the object you'd like to assert against through the realValue cell. Here's a simple example:

ISpec do(
  ShouldContext mimic = method(value,
  unless(realValue mimics?(value),
    error!(ISpec ExpectationNotMet, text: "expected #{realValue inspect} to mimic #{value kind}", shouldMessage: self shouldMessage))
  )
)

This allows you to make the following assertion:

[] should mimic(List)

Note that ShouldContext must re-map the mimic method to __mimic__ to allow this to occur.

ShouldContext cells can be either methods or macros and can become rather complex. For example, the receive assertion (for constructing mocks) builds messages that get stored on ISpec's StubWrangler for checking after each spec is run.