Sure’s Documentation¶
Contents:
Introduction¶
Sure is a python library for python that leverages a DSL for writing assertions.
In CPython it monkey-patches the object
type, adding some methods
and properties purely for test purposes.
Any python code writen after import sure
gains testing superpowers,
so you can write assertions like this:
import sure
def some_bratty_function(parameter):
raise ValueError("Me no likey {0}".format(parameter))
some_bratty_function.when.called_with("Scooby").should.throw(ValueError, "Me no likey Scooby")
Let’s get it started
Getting Started¶
Activating¶
Sure is activated upon importing it, unless the environment variable
SURE_DISABLE_NEW_SYNTAX
is set to any non-falsy value. (You could
just use true
)
For test code cleaningness it’s recommended to import sure only once in
the __init__.py
of your root test package.
Here is an example:
mymodule.py
tests/
tests/__init__.py # this is our guy
tests/unit/__init__.py
tests/unit/test_mymodule_unit1.py
tests/functional/__init__.py
tests/functional/test_mymodule_functionality.py
That is unless, of course, you want to explicitly import the assertion helpers from sure in every module.
Python version compatibility¶
Sure is continuously tested against python versions 2.6, 2.7 and 3.3, but its assertion API is most likely to work anywhere. The only real big difference of sure in cpython and even other implementations such as PyPy is that the monkey-patching only happens in CPython.
You can always get around beautifully with expect
:
from sure import expect
expect("this".replace("is", "at")).to.equal("that")
where in cpython you could do:
"this".replace("is", "at").should.equal("that")
Disabling the monkey patching¶
Just export the SURE_DISABLE_NEW_SYNTAX
environment variable before
running your tests.
export SURE_DISABLE_NEW_SYNTAX=true
How sure works¶
The class sure.AssertionBuilder
creates objects capable of doing
assertions. The AssertionBuilder simply arranges a vast set of possible
assertions that are composed by a source
object and a
destination
object.
Every assertion, even implicitly if implicitly like in
(2 < 3).should.be.true
, is doing a source/destination matching.
Chainability¶
Some specific assertion methods are chainable, it can be useful for short assertions like:
PERSON = {
"name": "John",
"facebook_info": {
"token": "abcd"
}
}
PERSON.should.have.key("facebook_info").being.a(dict)
Monkey-patching¶
Lincoln Clarete has written the module [sure/magic.py
] which I
simply added to sure. The most exciting part of the story is that
Lincoln exposed the code with a super clean API, it’s called forbidden
fruit
Why CPython-only ?¶
Sure uses the ctypes module to break in python protections against monkey patching.
Although ctypes might also be available in other implementations such as
Jython, only the CPython provide
`ctypes.pythonapi
<http://docs.python.org/library/ctypes#loading-shared-libraries>`__
the features required by sure.
API Reference¶
Equality¶
(number).should.equal(number)¶
import sure
(4).should.be.equal(2 + 2)
(7.5).should.eql(3.5 + 4)
(2).should.equal(8 / 4)
(3).shouldnt.be.equal(5)
(float).should.equal(float, epsilon)¶
import sure
(4.242423).should.be.equal(4.242420, epsilon=0.000005)
(4.01).should.be.eql(4.00, epsilon=0.01)
(6.3699999).should.equal(6.37, epsilon=0.001)
(4.242423).shouldnt.be.equal(4.249000, epsilon=0.000005)
Compare strings with diff¶
(string).should_not.be.different_of(string)¶
import sure
XML1 = '''<root>
<a-tag with-attribute="one">AND A VALUE</a-tag>
</root>'''
XML1.should_not.be.different_of(XML1)
XML2 = '''<root>
<a-tag with-attribute="two">AND A VALUE</a-tag>
</root>'''
XML2.should.be.different_of(XML1)
this will give you and output like
Difference:
<root>
- <a-tag with-attribute="one">AND A VALUE</a-tag>
? --
+ <a-tag with-attribute="two">AND A VALUE</a-tag>
? ++
</root>'''
{‘a’: ‘collection’}.should.equal({‘a’: ‘collection’}) does deep comparison¶
{'foo': 'bar'}.should.equal({'foo': 'bar'})
{'foo': 'bar'}.should.eql({'foo': 'bar'})
{'foo': 'bar'}.must.be.equal({'foo': 'bar'})
“A string”.lower().should.equal(“a string”) also works¶
"Awesome ASSERTIONS".lower().split().should.equal(['awesome', 'assertions'])
Similarity¶
should.look_like and should_not.look_like¶
"""
THIS IS MY loose string
""".should.look_like('this is my loose string')
"""this one is different""".should_not.look_like('this is my loose string')
Iterables¶
should.contain and should_not.contain¶
expect(collection).to.contain(item)
is a shorthand to
expect(item).to.be.within(collection)
"My bucket of text".should.contain('bucket')
"goosfraba".should_not.contain('anger')
['1.2.5', '1.2.4'].should.contain('1.2.5')
'1.2.3'.should.contain('2')
should.match and should_not.match matches regular expression¶
You can also use the modifiers:
`re.DEBUG
<http://docs.python.org/2/library/re.html#re.DEBUG>`__`re.I
andre.IGNORECASE
<http://docs.python.org/2/library/re.html#re.IGNORECASE>`__`re.M
andre.MULTILINE
<http://docs.python.org/2/library/re.html#re.MULTILINE>`__`re.S
andre.DOTALL
<http://docs.python.org/2/library/re.html#re.DOTALL>`__`re.U
andre.UNICODE
<http://docs.python.org/2/library/re.html#re.UNICODE>`__`re.X
andre.VERBOSE
<http://docs.python.org/2/library/re.html#re.VERBOSE>`__
import re
"SOME STRING".should.match(r'some \w+', re.I)
"FOO BAR CHUCK NORRIS".should_not.match(r'some \w+', re.M)
{iterable}.should.be.empty applies to any iterable of length 0¶
[].should.be.empty;
{}.should.be.empty;
set().should.be.empty;
"".should.be.empty;
().should.be.empty
range(0).should.be.empty;
## negate with:
[1, 2, 3].shouldnt.be.empty;
"Lincoln de Sousa".shouldnt.be.empty;
"Lincoln de Sousa".should_not.be.empty;
{number}.should.be.within(0, 10) asserts inclusive numeric range:¶
(1).should.be.within(0, 2)
(5).should.be.within(0, 10)
## negate with:
(1).shouldnt.be.within(5, 6)
{member}.should.be.within({iterable}) asserts that a member is part of the iterable:¶
"g".should.be.within("gabriel")
'name'.should.be.within({'name': 'Gabriel'})
'Lincoln'.should.be.within(['Lincoln', 'Gabriel'])
## negate with:
'Bug'.shouldnt.be.within(['Sure 1.0'])
'Bug'.should_not.be.within(['Sure 1.0'])
should.be.none and should_not.be.none¶
Assert whether an object is or not None
:
value = None
value.should.be.none
None.should.be.none
"".should_not.be.none
(not None).should_not.be.none
should.be.ok and shouldnt.be.ok¶
Assert truthfulness:
from sure import this
True.should.be.ok
'truthy string'.should.be.ok
{'truthy': 'dictionary'}.should.be.ok
And negate truthfulness:
from sure import this
False.shouldnt.be.ok
''.should_not.be.ok
{}.shouldnot.be.ok
Assert existence of properties and their values¶
class Basket(object):
fruits = ["apple", "banana"]
basket1 = Basket()
basket1.should.have.property("fruits")
.have.property().being allows chaining up¶
If the programmer calls have.property()
it returns an assertion
builder of the property if it exists, so that you can chain up
assertions for the property value itself.
class Basket(object):
fruits = ["apple", "banana"]
basket2 = Basket()
basket2.should.have.property("fruits").which.should.be.equal(["apple", "banana"])
basket2.should.have.property("fruits").being.equal(["apple", "banana"])
basket2.should.have.property("fruits").with_value.equal(["apple", "banana"])
basket2.should.have.property("fruits").with_value.being.equal(["apple", "banana"])
Assert existence of keys and its values¶
basket3 = dict(fruits=["apple", "banana"])
basket3.should.have.key("fruits")
.have.key().being allows chaining up¶
If the programmer calls have.key()
it returns an assertion builder
of the key if it exists, so that you can chain up assertions for the
dictionary key value itself.
person = dict(name=None)
person.should.have.key("name").being.none
person.should.have.key("name").being.equal(None)
Assert the length of objects with {iterable}.should.have.length_of(N)¶
[3, 4].should.have.length_of(2)
"Python".should.have.length_of(6)
{'john': 'person'}.should_not.have.length_of(2)
Assert the magnitude of objects with {X}.should.be.greater_than(Y) and {Y}.should.be.lower_than(X) as well as {X}.should.be.greater_than_or_equal_to(Y) and {Y}.should.be.lower_than_or_equal_to(X)¶
(5).should.be.greater_than(4)
(5).should_not.be.greater_than(10)
(1).should.be.lower_than(2)
(1).should_not.be.lower_than(0)
(5).should.be.greater_than_or_equal_to(4)
(5).should_not.be.greater_than_or_equal_to(10)
(1).should.be.lower_than_or_equal_to(2)
(1).should_not.be.lower_than_or_equal_to(0)
callable.when.called_with(arg1, kwarg1=2).should.throw(Exception)¶
You can use this feature to assert that a callable raises an exception:
import sure
from six import PY3
if PY3:
range.when.called_with(10, step=20).should.throw(TypeError, "range() does not take keyword arguments")
range.when.called_with("chuck norris").should.throw(TypeError, "'str' object cannot be interpreted as an integer")
else:
range.when.called_with(10, step="20").should.throw(TypeError, "range() takes no keyword arguments")
range.when.called_with(b"chuck norris").should.throw("range() integer end argument expected, got str.")
range.when.called_with("chuck norris").should.have.raised(TypeError)
range.when.called_with(10).should_not.have.raised(TypeError)
You can also match regular expressions with to the expected exception messages:
import re
range.when.called_with(10, step=20).should.throw(TypeError, re.compile(r'(does not take|takes no) keyword arguments'))
range.when.called_with("chuck norris").should.throw(TypeError, re.compile(r'(cannot be interpreted as an integer|integer end argument expected)'))
callable.when.called_with(arg1, kwarg1=2).should.throw(Exception)¶
You can use this feature to assert that a callable raises an exception:
import sure
from six import PY3
if PY3:
range.when.called_with(10, step=20).should.throw(TypeError, "range() does not take keyword arguments")
range.when.called_with("chuck norris").should.throw(TypeError, "'str' object cannot be interpreted as an integer")
else:
range.when.called_with(10, step="20").should.throw(TypeError, "range() takes no keyword arguments")
range.when.called_with(b"chuck norris").should.throw("range() integer end argument expected, got str.")
range.when.called_with("chuck norris").should.throw(TypeError)
range.when.called_with(10).should_not.throw(TypeError)
You can also match regular expressions with to the expected exception messages:
import re
range.when.called_with(10, step=20).should.throw(TypeError, re.compile(r'(does not take|takes no) keyword arguments'))
range.when.called_with("chuck norris").should.throw(TypeError, re.compile(r'(cannot be interpreted as an integer|integer end argument expected)'))
function.when.called_with(arg1, kwarg1=2).should.return_value(value)¶
This is a shorthand for testing that a callable returns the expected result
import sure
list.when.called_with([0, 1]).should.have.returned_the_value([0, 1])
this is the same as
value = range(2)
value.should.equal([0, 1])
there are no differences between those 2 possibilities, use at will
instance.should.be.a(‘typename’) and instance.should.be.an(‘typename’)¶
this takes a type name and checks if the class matches that name
import sure
{}.should.be.a('dict')
(5).should.be.an('int')
## also works with paths to modules
range(10).should.be.a('collections.Iterable')
instance.should.be.a(type) and instance.should.be.an(type)¶
this takes the class (type) itself and checks if the object is an instance of it
import sure
from six import PY3
if PY3:
u"".should.be.an(str)
else:
u"".should.be.an(unicode)
[].should.be.a(list)
instance.should.be.above(num) and instance.should.be.below(num)¶
assert the instance value above and below num
import sure
(10).should.be.below(11)
(10).should.be.above(9)
(10).should_not.be.above(11)
(10).should_not.be.below(9)
Static assertions with it, this, those and these¶
Whether you don’t like the object.should
syntax or you are simply
not running CPython, sure still allows you to use any of the assertions
above, all you need to do is wrap the object that is being compared in
one of the following options: it
, this
, those
and these
.
Too long, don’t read¶
All those possibilities below work just as the same¶
from sure import it, this, those, these
(10).should.be.equal(5 + 5)
this(10).should.be.equal(5 + 5)
it(10).should.be.equal(5 + 5)
these(10).should.be.equal(5 + 5)
those(10).should.be.equal(5 + 5)
Also if you prefer using the assert keyword in your tests just go ahead an do it!¶
from sure import it, this, those, these, expect
assert (10).should.be.equal(5 + 5)
assert this(10).should.be.equal(5 + 5)
assert it(10).should.be.equal(5 + 5)
assert these(10).should.be.equal(5 + 5)
assert those(10).should.be.equal(5 + 5)
expect(10).to.be.equal(5 + 5)
expect(10).to.not_be.equal(8)
(lambda: None).should.be.callable¶
Test if something is or not callable
import sure
range.should.be.callable
(lambda: None).should.be.callable;
(123).should_not.be.callable
A note about the assert keyword¶
you can use or not the assert
keyword, sure internally already
raises an appropriate AssertionError
with an assertion message so
that you don’t have to specify your own, but you can still use
assert
if you find it more semantic
Example:
import sure
"Name".lower().should.equal('name')
## or you can also use
assert "Name".lower().should.equal('name')
## or still
from sure import this
assert this("Name".lower()).should.equal('name')
## also without the assert
this("Name".lower()).should.equal('name')
Any of the examples above will raise their own AssertionError
with a
meaningful error message.
Synonyms¶
Sure provides you with a lot of synonyms so that you can pick the ones that makes more sense for your tests.
Note that the examples below are merely illustrative, they work not only with numbers but with any of the assertions you read early in this documentation.
Positive synonyms¶
(2 + 2).should.be.equal(4)
(2 + 2).must.be.equal(4)
(2 + 2).does.equals(4)
(2 + 2).do.equals(4)
Negative synonyms¶
from sure import expect
(2).should_not.be.equal(3)
(2).shouldnt.be.equal(3)
(2).doesnt.equals(3)
(2).does_not.equals(3)
(2).doesnot.equals(3)
(2).dont.equal(3)
(2).do_not.equal(3)
expect(3).to.not_be.equal(1)
Chain-up synonyms¶
Any of those synonyms work as an alias to the assertion builder:
be
being
to
when
have
with_value
from sure import expect
{"foo": 1}.must.with_value.being.equal({"foo": 1})
{"foo": 1}.does.have.key("foo").being.with_value.equal(1)
Equality synonyms¶
(2).should.equal(2)
(2).should.equals(2)
(2).should.eql(2)
Positive boolean synonyms¶
import sure
(not None).should.be.ok
(not None).should.be.truthy
(not None).should.be.true
Negative boolean synonyms¶
import sure
False.should.be.falsy
False.should.be.false
False.should_not.be.true
False.should_not.be.ok
None.should_not.be.true
None.should_not.be.ok
Holy guacamole, how did you implement that feature ?¶
Differently of ruby python doesn’t have
open
classes,
but Lincoln de Sousa came out with a
super sick
code
that uses the ctypes module to create a pointer to the __dict__
of
builtin types.
Yes, it is dangerous, non-pythonic and should not be used in production code.
Although sure
is here to be used ONLY in test code, therefore it
should be running in ONLY possible environments: your local machine
or your continuous-integration server.
-
sure.
assertion
(func)¶ Extend sure with a custom assertion method.
-
sure.
chain
(func)¶ Extend sure with a custom chaining method.
-
sure.
chainproperty
(func)¶ Extend sure with a custom chain property.
-
class
sure.core.
Anything
¶ Represents any possible value.
About sure¶
The assertion library is 100% inspired be the awesomeness of should.js which is simple, declarative and fluent.
Sure strives to provide everything a python developer needs in an assertion:
- Assertion messages are easy to understand
- When comparing iterables the comparation is recursive and shows exactly where is the error
- Fluency: the builtin types are changed in order to provide awesome simple assertions