We might be interested in starting to incorporate a unit test suite to our project, which is coded in Python (and it uses Redis, PostgreSQL and some third-party libraries, if that bears into the solution). The benefits we want to achieve are:
- Once the project is up to speed, we want to be able to specify new behavior quickly and efficiently, to help improve communication and team productivity. Read: We want BDD, but have already started coding without it.
- Excellent textual support (i.e. the tests can be read almost as natural text), so as to make it easier to write and read tests.
- Ability to automatically run tests (at the very least, to be able to include the running in a Bash script or something like that). We use Windows and Linux for development, and Linux servers, so Linux support is our primary requirement.
- Integration with github. I don’t know if it is possible, but I’ve seen some projects in github which have a “Passing” or “Failing” status on them and it would be great to have that for our project.
What are good tools / libraries that can help us do this, and, most importantly, what are good ways to incorporate BDD into a Python project?
Thank you very much! Any suggestions are appreciated.
2
Try out pyspecs. Making tests easy to read and constantly running during development were two of my main goals in creating this project.
Test Code:
from pyspecs import given, when, then, and_, the, this
with given.two_operands:
a = 2
b = 3
with when.supplied_to_the_add_function:
total = a + b
with then.the_total_should_be_mathmatically_correct:
the(total).should.equal(5)
with and_.the_total_should_be_greater_than_either_operand:
the(total).should.be_greater_than(a)
the(total).should.be_greater_than(b)
with when.supplied_to_the_subtract_function:
difference = b - a
with then.the_difference_should_be_mathmatically_correct:
the(difference).should.equal(1)
Console Output:
# run_pyspecs.py
| • given two operands
| • when supplied to the add function
| • then the total should be mathmatically correct
| • and the total should be greater than either operand
| • when supplied to the subtract function
| • then the difference should be mathmatically correct
(ok) 6 passed (6 steps, 1 scenarios in 0.0002 seconds)
2
While there are BDD frameworks like lettuce or behave (which you better choose by their documentation and examples), you mentioned that you’ve started without that. So my answer will be related more to this situation.
Original BDD has started with fork of JUnit, which, instead of asking you to name your tests with “testSomething” asked you to name them “shouldDoSomething”. That’s how you were supposed to be kept focused on things.
So I went the same way in my project. I just started to name tests starting with “test_should_…” and try to give them descriptive names. That’s how you’re still doing BDD, you’re still focused on what exactly should that test do, and it’s easy to write something that will convert your test names into normal sentences (if you’ll need to, of course). I find this approach a silver bullet since (especially in scripted languages) I never want to waste time on useless names (like test name in case of those BDD frameworks on top).
2
I’ve been working on a BDD framework called Contexts, inspired by C#’s Machine.Specifications.
Contexts encourages you to write descriptive user-centric sentences for your tests (which is what BDD is all about, really) by spreading out test-cases across a whole class. The arrange/act/assert phases of the test are put in individual short methods, and Contexts figures out which method is which based on their names.
A picture speaks a thousand words, so here’s an example from the readme:
import requests
class WhenRequestingAResourceThatDoesNotExist:
def given_that_we_are_asking_for_a_made_up_resource(self):
self.uri = "http://www.github.com/itdontexistman"
self.session = requests.Session()
def because_we_make_a_request(self):
self.response = self.session.get(self.uri)
def the_response_should_have_a_status_code_of_404(self):
assert self.response.status_code == 404
def the_response_should_have_an_HTML_content_type(self):
assert self.response.headers['content-type'] == 'text/html'
def cleanup_the_session(self):
self.session.close()
Contexts also has a number of modern features like parametrised tests, ‘assertion-rewriting’ (inspired by py.test), and a plugin framework.
I’ve used Contexts to test itself, as well as on a few other projects (even non-Python projects), and found it to yield understandable, behavioural tests at all levels of the testing triangle (unlike unittest which was not designed for acceptance testing, or Lettuce which was not designed for unit testing).
1
I think you should try Morelia:
https://morelia.readthedocs.org/en/latest/
It passes your 4-point requirements list:
- It’s BDD tool built on Python’s unittests module
- Executed tests are textual description.
- If you can automatically run traditional Unit Tests then Morelia can be run same way.
- I think the most common CI for github is Travis-CI which can run unit tests so that it can run Morelia.
1