How Sure Works

Automated Testing Library

Standard Behavior

See also

The section Assertion Builder Reference presents a vast set of practical examples

At the most basic level, Sure provides “assertion builder” which take a “source” object and contain methods to articulate different forms of comparison against a “destination” object.

The value of each type of comparison depends upon the value types of entire objects themselves or a portion of them, each case depends both on the value type and the chosen method of comparison. More on that soon.

Before explaining the behavior and mechanics of assertion builders at length it might be valuable to acquaint oneself with the list of all of Sure’s builtin assertion builders exemplified in Python import statements below:

from sure import assert_that
from sure import it
from sure import expect
from sure import expects
from sure import that
from sure import the
from sure import these
from sure import this
from sure import those

Each of the imports above are, in fact, instances of the class AssertionBuilder.

Instances of AssertionBuilder provide a vast set of assertion methods that, as mentioned previously, are composed by two objects to be compared, referred to as either “source” and “destination” objects in this documentation or X and Y identifiers at the error message introductions of failed assertions and should be literally understood in terms of logical variables - placeholders for values which are not yet known or clear at the moment of test verification - that intend to render intelligible and indicate clearly the the differences between values by themselves or within data-structures such as sequences, mappings or iterable objects.

In this latter case, the logical variables X and Y MAY be accompanied by what resembles Python’s slicing notation, indicating the location of different values within their corresponding data-structures.

A Detailed Example of Assertion Builders

import unittest
from sure import assert_that, that

class AssertionBuilderExample(unittest.TestCase):
    def test_numerical_value_comparison(self):
        """the statements below should be reasonably equivalent in some aspects"""

        assert_that(2).should.be.lower_than(3)

        assert_that(2 < 3).should.equal(True)

        assert_that(2 < 3).should.be.true

 if __name__ == "__main__":
     unittest.run()

Notice that every assertion in the example above is performing the same logical comparison: that the numerical value 2 is arithmetically lower than 3.

The first assertion performed with assert_that take the value 2 (a int object) as the “source” object: assert_that(2) at which point that particular instance of AssertionBuilder requires an assertion to be built upon itself, accomplished in the rest of that statement - .should.be.lower_than(3) - where the value 3 is the “destination” object.

The two remaining examples in this particular example take boolean values (bool objects) as “source” and “destination” objects.

The statement assert_that(2 < 3).should.equal(True) the assertion builder takes the expression 2 < 3 as the “source” object and the literal value True as the destination object.

Because the expected value is clearly provided in both of the cases above, it is correct to think of destination objects as “explicit”.

The third and last statement, in contrast to the first two just explained, relies on the internal mechanics of the .should.be.true statement, which ends with a call to the true which is a python:property()-decorated function that checks for logical proof that the “source” object exactly equivalent to the bool True.

Special Syntax

The Sure module presents the concept of “special syntax” defined as the optional feature of, during runtime, extending every object in Python’s runtime with properties that are themselves instances of AssertionBuilder binding the object in case as its “source” object. That effectively allows performing assertions directly on values with the purpose of enabling a kind of fluent writing of automated tests.

The sequence of instructions below demonstrate in practical terms how enabling the special syntax changes the behavior of Python during runtime (and hopefully bring to light some initial evidence of why this feature could cause unintended consequences if used in production code)

>>> value = 3.14
>>> [attr for attr in dir(value) if not attr.startswith('__')]
['as_integer_ratio', 'conjugate', 'fromhex', 'hex', 'imag', 'is_integer', 'real']

>>> import sure
>>> sure.enable_special_syntax()
>>> del value
>>> value = 3.14
>>> [attr for attr in dir(value) if not attr.startswith('__')]
['do', 'do_not', 'does', 'does_not', 'doesnt', 'dont', 'must', 'must_not', 'mustnt', 'should', 'should_not', 'shouldnt', 'when']

As can be observed in the examples above, there are two kinds of properties: positives and negatives

Positive Assertion Properties

Used for building assertions wherewith the comparison to a “destination” object resolves to True

The list of properties presently available upon enabling the special syntax are:

  • do

  • does

  • must

  • should

  • when

Example:

>>> import sure
>>> sure.enable_special_syntax()
>>> source = {
...     "structured information": [
...         "string",
...         {
...             "first key": 75,
...             "second key": 107,
...         },
...         [0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, {
...             "outmost": "list",
...         }],
...     ]
... }
>>> destination = {
...     "structured information": [
...         "string",
...         {
...             "first key": 107,
...             "second key": 75,
...         },
...         [0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, {
...             "outmost": "list",
...         }],
...     ]
... }
>>> source.should.equal(destination)
AssertionError:
X = {'structured information': ['string', [110, 117, 109, 98, 101, 114, {'outmost': 'list'}], {'first key': 75, 'second key': 107}]}
    and
Y = {'structured information': ['string', [110, 117, 109, 98, 101, 114, {'outmost': 'list'}], {'first key': 76, 'second key': 107}]}
X['structured information'][1]['first key'] is 75 whereas Y['structured information'][1]['first key'] is 107

Negative Assertion Properties

Used for building assertions wherewith the comparison to a “destination” object resolves to False

The list of properties presently available upon enabling the special syntax are:

  • do_not

  • dont

  • does_not

  • doesnt

  • must_not

  • mustnt

  • should_not

  • shouldnt

>>> import sure
>>> sure.enable_special_syntax()
>>>
>>> (42).should_not.equal(42)
AssertionError: expecting 42 to be different of 42

A bit of history

From Sure’s absolute ideation, its original author - Gabriel Falcão - had envisioned to somehow expand Python’s object with assertion methods during test runtime so that software engineers, coders or developers in general could benefit from somewhat more human-friendly and fluent assertions in the sense of literal writing fluency. At any rate, after much brainstorming, the best solution Gabriel could come up with was to provide a Python class - sure.AssertionBuilder - where and whence friendly assertions could be built upon.

Gabriel crafted the sure.AssertionBuilder in such way that its usage could seem like verbs or adverbs so as to work with or without Python’s assert statement. But even more so than that, the during the crafting of the sure.AssertionBuilder it was kept in mind that if it were possible to “hack” Python’s syntax to inject methods such as .should, .should_not, .must, .must_not, .shouldnt and .mustnt into object during test runtime only, then sure.AssertionBuilder could be almost effortlessly leveraged within those method’s implementations.

To be sure - pun intended - Gabriel crafted the sure.AssertionBuilder such that its assertion methods always returned True so that assert statements such as assert that(X).is_not(Y) where X = False and Y = True, would return True even in an occasion when, in this case, both X and Y were either True or False.

Gabriel’s purpose was not to allow or enable abuse of assertions but to prevent Python from raising a AssertionError with no details and instead bring as much detail as possible in the occasion of such exception, to the point of doing its best to show at what key or what index there is a difference in the case of testing equality between the datastructures dict or list, respectivelly in this case. (See sure.Explanation for more)

Gabriel’s initial idea came from believing that other programming languages suchs as Ruby or Javascript had tools or libraries such as RSpec or Should.js which provided a kind of syntax-sugar that seemed much more appealing or inviting for developers, making the process of writing tests more pleasant, rewarding or fun in sort of way.

At the time of Sure’s inception, so to speak, which was around the middle of the year of 2010, the testing tools for the Ruby programming language seemed much more mature and the market seemed to be booming with innovative, stable and resilient products crafted by practicioners of Agile Methodologies

Around the year of 2012 Gabriel Falcão was working at a startup in NYC and recruited two colleagues, one of whom was Lincoln Clarete which had been known to Gabriel to know quite a bit about the internals of the Python language. Then Gabriel not so much as asked whether it was possible to inject methods into object during runtime but actually challenged Lincoln to try and do so.

As Gabriel imagined, it wouldn’t take long for Lincoln Clarete to achieve that goal, he then presently wrote most if not all the code currently present inside sure.special and also took the idea forward and evolvend it, ultimately resulting in the publishing of the Python Package forbidden fruit.

Nevertheless, there is a caveat regarding the functionallity provided by such Special Syntax: it is primarily supposed to work only with cpython, the standard implementation of the Python programming language in the C programming language. This is because Sure depends on the ctypes module to gain write-access to the __dict__ member of object during (test) runtime.

More precisely, it is worth noting that whether the ctypes library or an equivalent is available to other implementations of Python such as Jython, only the CPython provide `ctypes.pythonapi <https://docs.python.org/library/ctypes#loading-shared-libraries>`__ the features required by Sure.

Test Runner

Sure provides the command-line tool sure which takes one or more test paths as positional arguments, locates test*.py files in those paths, then load loads and executes all functions matching the regular expression ^(Ensure|Test|Spec|Scenario)[\w_]+$.

Example:

sure --special-syntax --immediate path/to/tests

The option -s or --special-syntax enables the Special Syntax

The option -i or --immediate causes the session to fail fast which can be particularly useful, for example, when testing large codebases or slow tests.

sure --special-syntax --immediate path/to/tests

Use the --help option for a full list of options

Coverage Support

Test coverage is supported through the coverage and can be enabled adwith the option --with-coverage

The options --cover-branches and --cover-module=<module_name> further configures the test execution.

Example:

sure --with-coverage --cover-branches --cover-module=yourmodulename tests

Further Help

sure --help
Usage: sure [OPTIONS] [PATHS]...

Options:
  -c, --with-coverage
  -s, --special-syntax
  -f, --log-file TEXT             path to a log file. Default to SURE_LOG_FILE
  -l, --log-level [debug|info|warning|error]
                                  default='info'
  -i, --immediate
  -r, --reporter [logger|feature|test]
                                  default=feature
  --cover-branches
  --cover-module TEXT             specify module names to cover
  --help                          Show this message and exit.