A Lua framework that allows to run BDD-style user stories.
LuaBehave is a BDD (Behavior-Driven Development) testing framework that parses and runs BDD stories based on the Gherkin language.
Gherkin is a plain-text language with a simple structure. It is designed to be easy to learn by non-programmers, yet structured enough to allow concise description of test scenarios and examples to illustrate business rules in most real-world domains.
Here is an example:
Story: Guess the word
# The first example has two steps
Scenario: Maker starts a game
When the Maker starts a game
Then the Maker waits for a Breaker to join
# The second example has three steps
Scenario: Breaker joins a game
Given the Maker has started a game with the word "silky"
When the Breaker joins the Maker's game
Then the Breaker must guess a word with 5 characters
I was too lazy to search for a suitable BDD-framework on the Internet, so I wrote my own. I know about the Cucumber project, but I don't like its Lua implementation...
git clone [email protected]:slavaz/luabehave.git
cd luabehave
luarocks make --local
or
luarocks make
The BDD-framework is based (but not strictly followed) on Gherkin language: https://cucumber.io/docs/gherkin/reference/#keywords
For usage examples, see the bdd/steps and bdd/stories directories.
Usage: luabehave [-h] [-c <file>] [--suites <suite1>,<suite2>,...]
[--ignore-unimplemented] [--log-level <level>]
LuaBehave is a BDD (Behavior-Driven Development) testing framework that parses
and runs BDD stories based on the Gherkin language.
Options:
-h, --help Show this help message and exit.
-c <file>, Path to the configuration file (default: .luabehave)
--config <file>
--suites <suite1>,<suite2>,...
Comma-separated list of suites to run
--ignore-unimplemented
Assume that all steps are implemented (exit code will be 0)
--log-level <level> Set the log level. Mya be one of: trace, debug, info, warning, error (default: info)
The following global functions are declared for registering functions that implement steps:
Given("step_name", function(args)
-- a step implementation
end)
When("step_name", function(args)
-- a step implementation
end)
Then("step_name",function(args)
-- a step implementation
end)
BeforeScenario(function(args)
-- is running before each scenario
end)
AfterScenario(function(args)
-- is running after each scenario
end)
BeforeStory(function(args)
-- is running before each sttory
end)
AfterStory(function(args)
-- is running after each story
end)
BeforeSuite(function(args)
-- is running before each suite
end)
AfterSuite(function(args)
-- is running after each suite
end)
The main difference from classic Gherkin language is arguments definition. Steps are looks like:
Given a book written by {author=Name Surname} with {title=Very cool story}
And a book written by {author=Name2 Surname2} with {title=Very very cool story, indeed}
both steps are handled by one step definition:
Given("a book written by {author} with {title}", function(args)
print (args.author)
print (args.title)
end
)
Suite as fact it's a tag assigned to a scenario:
Suite: Some suite, Suite1, suiteN
Story: a story
Scenario: ....
By default, all test suites will be executed. The list of test suites can be specified using the command line parameter:
--suites='comma separated list'
Tables have 3 keywords: '|', '!|' and '~|'
keyword '~|' means table name Keyword '!|' means 'header definitions' or keys in 'key-value' pairs And keyword '|' means values separator. Below examples.
The table:
|val1|val2|
will be parsed to:
args = {
{"val1", "val2"}
}
The table:
|val1|val2|
|val3|val4|
will be parsed to:
args = {
{"val1", "val2"},
{"val3", "val4"},
}
The table:
!|header1|header2|
|val1|val2|
will be parsed to:
args = {
header1 = "val1",
header2 = "val2",
}
The table:
!|header1|header2|
|val1|val2|val3|
will be parsed to:
args = {
header1 = "val1",
header2 = "val2",
"val3"
}
The table:
!|header1|header2|
|val1|val2|
|val3|val4|
will be parsed to:
args = {
{header1 = "val1", header2 = "val2"},
{header1 = "val3", header2 = "val4"},
}
The table:
!|header1|header2|
|val1|val2|
|val3|val4|val5|
will be parsed to:
args = {
{header1 = "val1", header2 = "val2"},
{header1 = "val3", header2 = "val4", "val5"},
}
The table:
!|header1|header2|header3|
|val1|val2|
|val3|val4|val5|
will be parsed to:
args = {
{ header1 = "val1", header2 = "val2" },
{ header1 = "val3", header2 = "val4", header3 = "val5" },
}
and finally, the table:
~|table_name|
!|header1|header2|header3|
|val1|val2|
|val3|val4|val5|
will be parsed to:
args = {
table_name = {
{ header1 = "val1", header2 = "val2" },
{ header1 = "val3", header2 = "val4", header3 = "val5" },
}
}
Some notes here:
- each suite has own isolated execution environment for step definitions.
- each story (feature) has own isolated execution environment based on suite env.
- each scenario has own isolated execution environment based on story env.
It's possible to gain access to the 'parent' environment. For example, if a step wants to access the story-level environment, it can do so via the following call:
local parent_env = get_parent_environment()
Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.
Please make sure to update tests as appropriate.
MIT (https://choosealicense.com/licenses/mit/)
Slava Zanko - [email protected]
Project Link: https://github.com/slavaz/luabehave