Demonstrations

Standard Sections

QED demos are light-weight specification documents, highly suitable to interface-driven design. The documents are divided up into clauses separated by blank lines. Clauses that are flush to the left margin are always explanation or comment clauses. Indented clauses are always executable code.

Each code section is executed in order of appearance, within a rescue wrapper that captures any failures or errors. If neither a failure or error occur then the code gets a “pass”.

For example, the following passes:

    (2 + 2).assert == 4

While the following would “fail”, as indicated by the raising of an Assertion error:

    expect Assertion do
      (2 + 2).assert == 5
    end

And this would have raised a NameError:

    expect NameError do
      nobody_knows_method
    end

Neutral Code Blocks

There is no means of specifying that a code clause is neutral code, i.e. that it should be executed but not tested. Thus far, such a feature has proven to be a YAGNI.

Defining Custom Assertions

The context in which the QED code is run is a self-extended module, thus reusable macros can be created simply by defining a method.

    def assert_integer(x)
      x.assert.is_a? Integer
    end

Now lets try out our new macro definition.

    assert_integer(4)

Let’s prove that it can also fail:

    expect Assertion do
      assert_integer("IV")
    end

Advice

Advice are wrapper methods that augment demonstrations. They are used to keep demonstrations clean of extraneous, repetitive and merely adminstrative code that the reader does not need to see over and over.

Before and After

QED supports before and after clauses in a specification through the use of Before and After code blocks. These blocks are executed at the beginning and at the end of each indicated step.

We use a before clause if we want to setup some code at the start of each code step.

    a, z = nil, nil

    Before do
      a = "BEFORE"
    end

And an after clause to teardown objects after a code step.

    After do
      z = "AFTER"
    end

Notice we assigned a and z before the block. This was to ensure their visibility in the scope later. Now, lets verify that the before and after clauses work.

    a.assert == "BEFORE"

    a = "A"
    z = "Z"

And now.

    z.assert == "AFTER"

There can be more than one before and after clause at a time. If we define a new before or after clause later in the document, it will be appended to the current list of clauses in use.

As a demonstration of this:

    b = nil

    Before do
      b = "BEFORE AGAIN"
    end

We will see it is the case.

    b.assert == "BEFORE AGAIN"

Only use before and after clauses when necessary —specifications are generally more readable without them. Indeed, some developers make a policy of avoiding them altogether. YMMV.

Caveats of Before and After

Instead of using Before and After clauses, it is wiser to define a reusable setup method. For example, in the helper if we define a method such as #prepare_example.

  def prepare_example
    "Hello, World!"
  end

Then we can reuse it in later code blocks.

  example = prepare_example
  example.assert == "Hello, World!"

The advantage to this is that it gives the reader an indication of what is going on behind the scenes, rather the having an object just magically appear.

Event Targets

There is a small set of advice targets that do not come before or after an event, rather they occur upon a particular event. These include :load and :unload, for when a new helper is loaded; :pass, :fail and :error for when a code block passes, fails or raises an error; and :tag which is a low-level target triggered upon parsing each element in the HTML conversion of the demonstration.

These event targets can be advised by calling the When method with the target type as an argument along with the code block to be run when the event is triggered.

  x = []

  When(:tag) do |element|
    x << element.text.strip if element.name == 'li'
  end

Not let see it is worked:

So x should now contain these three list samples.

  x.assert == [ 'SampleA', 'SampleB', 'SampleC' ]

Pattern Matchers

QED also supports comment match triggers. With the When method one can define procedures to run when a given pattern matches comment text. For example:

    When 'given a setting @a equal to (((\d+)))' do |n|
      @a = n.to_i
    end

Now, @a will be set to 1 whenever a comment like this one contains, “given a setting @a equal to 1”.

    @a.assert == 1

A string pattern is translated into a regular expression. In fact, you can use a regular expression if you need more control over the match. When using a string all spaces are converted to \s+ and anything within double-parenthesis is treated as raw regular expression. Since the above example has (((\d+))), the actual regular expression contains (\d+), so any number can be used. For example, “given a setting @a equal to 2”.

    @a.assert == 2

Typically you will want to put triggers is helper files, rather then place them directly in the demonstration document.

This concludes the basic overview of QED’s specification system, which is itself a QED document. Yes, we eat our own dog food.

Helpers

There are two ways to load advice scripts. Either per demonstration or globally. Per demonstration helpers apply only to the current demonstration. Global helpers apply to all demonstrations.

Global Helpers

Global helpers are loaded at the start of a session and apply equally to all demonstrations in a suite.

Local Helpers

Helper scripts can be written just a demonstration scripts, or they can be defined as pure Ruby scripts. Either way they are loaded per-demonstration by using specially marked links.

For example, because this link, Advice, begins with require:, it will be used to load a global helper. We can see this with the following:

  pudding.assert.include?('load advice.rb')

No where in the demonstration have we defined pudding, but it has been defined for us in the advice.rb helper script.

We can also see that the generic When clause in our advice helper is keeping count of descriptions. Since the helper script was loaded three paragraphs back, the count will be 3.

  count.assert == 3

Helpers are vital to building test-demonstration suites for applications. But here again, only use them as necessary. The more helpers you use the more difficult your demos will be to follow.

Fixtures

Flat-file Data

When creating testable demonstrations, there are times when sizable chunks of data are needed. It is convenient to store such data in separate files. The Data method makes is easy to load such files.

    Data('fixtures/data.txt').assert =~ /dolor/

All files are found relative to the location of current document.

Tabular Data

The Table method is similar to the Data method except that it expects a YAML file, and it can take a block to iterate the data over. This makes it easy to test tables of examples.

The arity of the table block corresponds to the number of columns in each row of the table. Each row is assigned in turn and run through the coded step. Consider the following example:

Every row in the table.yml table will be assigned to the block parameters and run through the subsequent assertion.

    Table 'fixtures/table.yml' do |x, y|
      x.upcase.assert == y
    end