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.
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)
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
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"
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.
There are three different kind of steps: Regular, Table and Py string.
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
))
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
))
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
))
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")
Ecukes provides four different hooks
Runs once before anything runs.
(Setup
;; Run code
)
Runs once before each step runs.
(Before
;; Run code
)
Runs once after each step runs.
(After
;; Run code
)
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.
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 ...
Report on Github Issues.