.. _tutorial-simple:
introduction
============
Lettuce_ is an extremely useful and charming tool for BDD_ (Behavior
Driven Development). It can execute plain-text functional descriptions
as automated tests for Python_ projects, just as Cucumber_ does for
Ruby_.
Lettuce_ makes the development and testing process really easy,
scalable, readable and - what is best - it allows someone who doesn't
program to describe the behavior of a certain system, without
imagining those descriptions will automatically test the system during
its development.
.. image:: ./flow.png
get lettuce
===========
Make sure you've got Python installed and then run from the terminal:
.. highlight:: bash
::
user@machine:~$ [sudo] pip install lettuce
define a problem
================
Let's choose a problem to lettuce:
**Given a number, what is its factorial?**
.. Note::
The factorial of a positive integer n, denoted by n!, is the
product of all positive integers less than or equal to n. The
factorial of 0 is
project structure
=================
Build the directory tree bellow such as the files `zero.feature` and `steps.py` are empty.
.. highlight:: bash
::
/home/user/projects/mymath
| tests
| features
- zero.feature
- steps.py
lettuce it!
===========
Lets begin to describe and solve our problem...
first round
-----------
[a] describe behaviour
~~~~~~~~~~~~~~~~~~~~~~
Start describing the expected behaviour of factorial in `zero.feature` using English:
.. highlight:: ruby
::
Feature: Compute factorial
In order to play with Lettuce
As beginners
We'll implement factorial
Scenario: Factorial of 0
Given I have the number 0
When I compute its factorial
Then I see the number 1
.. Note::
zero.feature must be inside features directory and its extension must
be .feature. However, you're free to choose its name.
[b] define steps in python
~~~~~~~~~~~~~~~~~~~~~~~~~~
Now, let's define the steps of the scenario, so Lettuce acan
understand the behaviour description. Write `steps.py` file using
Python:
.. highlight:: python
::
from lettuce import *
@step('I have the number (\d+)')
def have_the_number(step, number):
world.number = int(number)
@step('I compute its factorial')
def compute_its_fatorial(step):
world.number = factorial(world.number)
@step('I see the number (\d+)')
def check_number(step, expected):
expected = int(expected)
assert world.number == expected, \
"Got %d" % world.number
def factorial(number):
return -1
.. Note::
`steps.py` must be inside features directory, but the names doesn't
need to be `steps.py`, it can be any `.py` terminated file,
Lettuce_ will look for python files recursively within features
dir.
Ideally, factorial will be defined somewhere else. However, as this is
just a first example, we'll implement it inside steps.py, so you get
the idea of how to use Lettuce.
**Notice that, until now, we haven't defined the factorial function (it's returning -1).**
[c] run and watch it fail
~~~~~~~~~~~~~~~~~~~~~~~~~
Go to the tests directory and run from the terminal:
.. highlight:: bash
::
user@machine:~/projects/mymath/tests$ lettuce
As you haven't implemented factorial, it is no surprise the behavior
won't be reached:
.. image:: ./screenshot1.png
Our only scenario failed :(
Let's solve it...
[d] write code to make it pass
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Well, by definition, we know that the factorial of 0 is 1. As our only
feature is this... we could force factorial to return 1.
.. highlight:: python
::
from lettuce import *
@step('I have the number (\d+)')
def have_the_number(step, number):
world.number = int(number)
@step('I compute its factorial')
def compute_its_fatorial(step):
world.number = factorial(world.number)
@step('I see the number (\d+)')
def check_number(step, expected):
expected = int(expected)
assert world.number == expected, \
"Got %d" % world.number
def factorial(number):
return 1
[e] run again and watch it pass
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Again, run from the terminal:
.. highlight:: bash
::
user@machine:~/projects/mymath/tests$ lettuce
And you'll be happy to see your factorial implementation passed all the behaviours expected:
.. image:: ./screenshot2.png
Great! :)
However, one test is not enough for checking the quality of our
solution... So let's lettuce it again!
second round
------------
Let's provide more tests so our problem is better described, and so we
provide a more accurate implementation of factorial:
[a] describe behaviour
~~~~~~~~~~~~~~~~~~~~~~
Let's provide two new scenarios, for numbers 1 and 2:
.. highlight:: ruby
::
Feature: Compute factorial
In order to play with Lettuce
As beginners
We'll implement factorial
Scenario: Factorial of 0
Given I have the number 0
When I compute its factorial
Then I see the number 1
Scenario: Factorial of 1
Given I have the number 1
When I compute its factorial
Then I see the number 1
Scenario: Factorial of 2
Given I have the number 2
When I compute its factorial
Then I see the number 2
[b] define steps in python
~~~~~~~~~~~~~~~~~~~~~~~~~~
As we haven't changed the definition, no need to make changes on this
step.
[c] run and watch it fail
~~~~~~~~~~~~~~~~~~~~~~~~~
.. highlight:: bash
::
user@machine:~/projects/mymath/tests$ lettuce
When running Letucce we realize that our previous implementation of
factorial works fine both for 0 and for 1, but not for 2 - it
fails. :(
.. image:: ./screenshot3.png
[d] write code to make it pass
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Let's provide a solution so we get the right factorial for all
scenarions, specially for number 2:
.. highlight:: python
::
from lettuce import *
@step('I have the number (\d+)')
def have_the_number(step, number):
world.number = int(number)
@step('I compute its factorial')
def compute_its_fatorial(step):
world.number = factorial(world.number)
@step('I see the number (\d+)')
def check_number(step, expected):
expected = int(expected)
assert world.number == expected, \
"Got %d" % world.number
def factorial(number):
number = int(number)
if (number == 0) or (number == 1):
return 1
else:
return number
[e] run again and watch it pass
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. highlight:: bash
::
user@machine:~/projects/mymath/tests$ lettuce
.. image:: ./screenshot4.png
Great! Three scenarios described and they are alright!
third round
-----------
Let's provide more tests so our problem is better described and we get
new errors so we'll be able to solve them.
[a] describe behaviour
~~~~~~~~~~~~~~~~~~~~~~
.. highlight:: ruby
::
Feature: Compute factorial
In order to play with Lettuce
As beginners
We'll implement factorial
Scenario: Factorial of 0
Given I have the number 0
When I compute its factorial
Then I see the number 1
Scenario: Factorial of 1
Given I have the number 1
When I compute its factorial
Then I see the number 1
Scenario: Factorial of 2
Given I have the number 2
When I compute its factorial
Then I see the number 2
Scenario: Factorial of 3
Given I have the number 3
When I compute its factorial
Then I see the number 6
Scenario: Factorial of 4
Given I have the number 4
When I compute its factorial
Then I see the number 24
[b] define steps in python
~~~~~~~~~~~~~~~~~~~~~~~~~~
As we haven't changed the definition, no need to make changes on this
step.
[c] run and watch it fail
~~~~~~~~~~~~~~~~~~~~~~~~~
.. highlight:: bash
::
user@machine:~/projects/mymath/tests$ lettuce
.. image:: ./screenshot5.png
[d] write code to make it pass
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. highlight:: python
::
from lettuce import *
@step('I have the number (\d+)')
def have_the_number(step, number):
world.number = int(number)
@step('I compute its factorial')
def compute_its_fatorial(step):
world.number = factorial(world.number)
@step('I see the number (\d+)')
def check_number(step, expected):
expected = int(expected)
assert world.number == expected, \
"Got %d" % world.number
def factorial(number):
number = int(number)
if (number == 0) or (number == 1):
return 1
else:
return number*factorial(number-1)
[e] run again and watch it pass
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. highlight:: bash
::
user@machine:~/projects/mymath/tests$ lettuce
.. image:: ./screenshot6.png
forth round
-----------
All steps should be repeated as long as you can keep doing them - the
quality of your software depends on these.
Have a nice lettuce...! ;)
.. _Lettuce: http://lettuce.it
.. _Python: http://python.org
.. _Cucumber: http://cukes.info
.. _Ruby: http://ruby-lang.org/
.. _BDD: http://en.wikipedia.org/wiki/Behavior_Driven_Development