What is Ecukes?

Ecukes is a testing tool for Emacs based on Cucumber. With Ecukes, you can write Cucumber-like tests for your Emacs packages.

Step definitions for Ecukes is separated to another package called Espuds.

Index

Example

Example from feature in Drag Stuff.

user@lap ~/Code/drag-stuff $ ecukes features/line.feature
Feature: Drag line
  • In order to move a line up and down
  • As an Emacs user
  • I want to drag it
Background:
  • Given I am in the buffer "*drag-stuff*"
  • And the buffer is empty
  • And I insert
    • """
    • line 1
    • line 2
    • """
Scenario: Drag line up
  • When I go to line "2"
  • And I press "<M-up>"
  • Then I should see:
    • """
    • line 2
    • line 1
    • """
Scenario: Drag line down
  • When I go to line "1"
  • And I press "<M-down>"
  • Then I should see:
    • """
    • line 2
    • line 1
    • """
  • 2 scenarios (0 failed, 2 passed)
  • 11 steps (0 failed, 11 passed)

Projects using Ecukes

Get it!

The source for Ecukes is available on Github.

http://github.com/rejeep/ecukes

As well as the source for Espuds.

http://github.com/rejeep/espuds

Installation

Download Ecukes and put the files somewhere in your filesystem. In Ecukes root directory, there is an executable called ecukes. Make sure it's in your PATH:

$ export PATH="$PATH:/path/to/ecukes"

Usage

When you have the ecukes binary available, you can create the Ecukes files for an Emacs project. Lets say you have a project called super-duper that you want to test with Ecukes.

$ cd /path/to/your/emacs/project
$ ecukes --new
Ecukes creates the features directory with these files:
$ ls super-duper

super-duper/
    |-- README
    |-- features
    |   |-- step-definitions
    |   |   `-- super-duper-steps.el
    |   |-- super-duper.feature
    |   `-- support
    |       `-- env.el
    `-- super-duper.el

Files in the support directory will be loaded (env.el first, then the rest) each time Ecukes run. All files that ends with -steps.el in the step-definitions directory will be loaded as step definitions.

Steps

There are three different kind of steps: Regular, Table and Py string.

Regular Step

A regular step always only consumes one line. The most basic step looks like this:

Given a known state

Corresponding step definition:

(Given "a known state"
     (lambda ()
       ;; Do something
       ))

The second argument could also have been the symbol of a function name:

(Given "a known state" 'do-something)

Steps can take arbitrary many arguments. The arguments sent to the step definition function are all regular expression match groupings from the matching of the step name and the match string.

Single argument:

Given I am in buffer "buffer-name"

Corresponding step definition:

(Given "I am in buffer \"\\(.+\\)\""
   (lambda (buffer-nane)
     ;; Do something with buffer
     ))

Multiple arguments:

Given I am in buffer "buffer-name" with text "Foo"

Corresponding step definition:

(Given "I am in buffer \"\\(.+\\)\" with text \"\\(.+\\)\""
   (lambda (buffer text)
     ;; Do something with buffer and text
     ))

Table Step

A table step looks like this:

Given these meals:
  | meal      | price |
  | Hamburger | $4.50 |
  | Pizza     | $5.30 |

Corresponding step definition:

(Given "these meals:"
       (lambda (meals)
         ;; Do something with text
         ))

The argument meals is a simple list where the car of the list is the header and the cdr or the list is the rows.

The header would in the above case be:

("meal" "price") And the rows would be:
(("Hamburger" "$4.50")
 ("Pizza" "$5.30"))

To pick out the header and rows attribute from a table you do:

(let* ((table ...)
       (header (car table))
       (rows   (cdr table)))
  ;; Do something with header and rows
  )

It is also possible to send arguments to a table step. The table list will be the last argument.

Given these meals at "fast food":
  | meal      | price |
  | Hamburger | $4.50 |
  | Pizza     | $5.30 |

Corresponding step definition:

(Given "these meals at \"\\(.+\\)\":"
       (lambda (restaurant meals)
         ;; Do something with restaurant and meals
         ))

Py-string Step

A Py-String (or Python string) step looks like this:

Given the following text:
  """
  some text
  """

Corresponding step definition:

(Given "the following text:"
       (lambda (text)
         ;; Do something with text
         ))

It is also possible to send arguments to a Py-string step. The Py-string will be the last argument.

Given the following text in buffer "buffer-name":
  """
  some text
  """

Corresponding step definition:

(Given "the following text in buffer \"\\(.+\\)\":"
       (lambda (buffer text)
         ;; Do something with buffer and text
         ))

Calling steps from steps

In order to keep your steps DRY, you can call steps from other steps like this:

(Given "I go to a"
       (lambda ()
         ;; Go to a
         ))

(Given "I go to b"
       (lambda ()
         ;; Go to b
         ))

(Given "I go from a to b"
       (lambda ()
         (Given "I go to a")
         (Given "I go to b")))

You can also pass arguments when calling other steps.

(Given "I am in buffer \"%s\"" "buffer-name")

Hooks

Ecukes provides four different hooks

Setup

Runs once before anything runs.

(Setup
 ;; Run code
 )

Before

Runs once before each step runs.

(Before
 ;; Run code
 )

After

Runs once after each step runs.

(After
 ;; Run code
 )

Teardown

Runs once when everything is done.

(Teardown
 ;; Run code
 )

Hooks are useful if you test your program and change the state in some feature. Since all scenarios execute in the same environment, the state change will affect all scenarios after. You can solve that by resetting the state in a before or after hook.

Hooks should be placed in any file in the project features/support directory.

Contribution

All contributions are much welcome and appreciated!

Before submitting a patch, make sure to write a test for it (if possible). Ecukes is tested with a testing framework called Ert (Emacs Lisp Regression Testing).

To run the tests, you have to fetch two packages: ert and el-mock.

$ cd /path/to/ecukes
$ git submodule init
$ git submodule update

Then run the tests with:

$ /path/to/ecukes/test/ecukes-test

You can also run specific tests with:

$ /path/to/ecukes/test/ecukes-test some-test.el ...

Issues/Bugs/Feature Requests

Report on Github Issues.

Fork me on GitHub