Skip to main content

Literate BDD assertions in Python with no magic

Project description

ensure is a set of simple assertion helpers that let you write more expressive, literate, concise, and readable Pythonic code for validating conditions. It’s inspired by should.js, expect.js, and builds on top of the unittest/JUnit assert helpers.

Because ensure is a standalone library (not part of a test framework), doesn’t monkey-patch anything or use DSLs, and doesn’t use the assert statement (which is liable to be turned off with the -O flag), it can be used to validate conditions in production code, not just for testing.

Aside from better looking code, a big reason to use ensure is that it provides more consistent, readable, and informative error messages when things go wrong. See Motivation and Goals for more.

Installation

pip install ensure

Synopsis

from ensure import ensure

ensure({1: {2: 3}}).equals({1: {2: 3}})
ensure({1: {2: 3}}).is_not_equal_to({1: {2: 4}})
ensure(True).does_not_equal(False)
ensure(1).is_an(int)
ensure(1).is_in(range(10))
ensure(True).is_a(bool)
ensure(True).is_(True)
ensure(True).is_not(False)
ensure(range(8)).contains(5)
ensure(["spam"]).contains_none_of(["eggs", "ham"])
ensure(["train", "boat"]).contains_one_of(["train"])
ensure("abcdef").contains_some_of("abcxyz")
ensure("abcdef").contains_one_or_more_of("abcxyz")
ensure("abcdef").contains_all_of("acf")
ensure("abcd").contains_only("dcba")
ensure("abc").does_not_contain("xyz")
ensure([1, 2, 3]).contains_no(float)
ensure(1).is_in(range(10))
ensure("z").is_not_in("abc")
ensure(None).is_not_in([])
ensure(dict).has_attribute('__contains__').which.is_callable()
ensure({1: "a"}).has_key(1).whose_value.has_length(1)
ensure(1).is_true()
ensure(0).is_false()
ensure(None).is_none()
ensure(1).is_not_none()
ensure("").is_empty()
ensure([1, 2]).is_nonempty()
ensure(1.1).is_a(float)
ensure(KeyError()).is_an(Exception)
ensure({x: str(x) for x in range(5)}).is_a_nonempty(dict).of(int).to(str)
ensure({}).is_an_empty(dict)
ensure(None).is_not_a(list)
import re
ensure("abc").matches("A", flags=re.IGNORECASE)
ensure([1, 2, 3]).is_an_iterable_of(int)
ensure([1, 2, 3]).is_a_list_of(int)
ensure({1, 2, 3}).is_a_set_of(int)
ensure({1: 2, 3: 4}).is_a_mapping_of(int).to(int)
ensure({1: 2, 3: 4}).is_a_dict_of(int).to(int)
ensure({1: 2, 3: 4}).is_a(dict).of(int).to(int)
ensure(10**100).is_numeric()
ensure(lambda: 1).is_callable()
ensure("abc").has_length(3)
ensure(1).is_greater_than(0)
ensure(0).is_less_than(1)
ensure(1).is_greater_than_or_equal_to(1)
ensure(0).is_less_than_or_equal_to(0)
ensure(1).is_positive()
ensure(1.1).is_a_positive(float)
ensure(-1).is_negative()
ensure(-1).is_a_negative(int)
ensure(0).is_nonnegative()
ensure(0).is_a_nonnegative(int)
ensure("{x} {y}".format).called_with(x=1, y=2).equals("1 2")
ensure(int).called_with("1100101", base=2).returns(101) # returns is a synonym for equals only for called_with()
ensure("{x} {y}".format).with_args(x=1, y=2).is_a(str)
ensure(dict).called_with(1, 2).raises(TypeError)
with ensure().raises(ZeroDivisionError):
    1/0
with ensure().raises_regex(NameError, "'w00t' is not defined"):
    w00t

Raising custom exceptions

from ensure import Ensure

class MyException(Exception):
    def __init__(self, e):
        pass

ensure = Ensure(error_factory=MyException)
ensure("w00t").is_an(int)

def build_fancy_exception(original_exception):
    return MyException(original_exception)

ensure = Ensure(error_factory=build_fancy_exception)
ensure("w00t").is_an(int)

Motivation and goals

Many BDD assertion libraries suffer from an excess of magic, or end up having to construct statements that don’t parse as English easily. ensure is deliberately kept simple to avoid succumbing to either issue. The source is easy to read and extend.

Work remains to make error messages raised by ensure even more readable, informative, and consistent. Going forward, ability to introspect exceptions to extract structured error information will be a major development focus. You will be in control of how much information is presented in each error, which context it’s thrown from, and what introspection capabilities the exception object will have.

The original use case for ensure is as an I/O validation helper for API endpoints, where the client needs to be sent a very clear message about what went wrong, some structured information (such as an HTTP error code and machine-readable reference to a failing element) may need to be added, and some information may need to be hidden from the client. To further improve on that, we will work on better error translation, marshalling, message formatting, and schema validation helpers.

License

Licensed under the terms of the Apache License, Version 2.0.

https://travis-ci.org/kislyuk/ensure.png https://pypip.in/v/ensure/badge.png https://pypip.in/d/ensure/badge.png

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

ensure-0.1.0.tar.gz (6.9 kB view details)

Uploaded Source

File details

Details for the file ensure-0.1.0.tar.gz.

File metadata

  • Download URL: ensure-0.1.0.tar.gz
  • Upload date:
  • Size: 6.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No

File hashes

Hashes for ensure-0.1.0.tar.gz
Algorithm Hash digest
SHA256 abbee817f217fdd63d40c617732258f7ec26f4f456ff85a4f81cdcc1618511f2
MD5 646c9b94a1e9fcbc44f68f3de2f69851
BLAKE2b-256 79ab0a1c95c43095487fab9c255ed66674c3a5608163bb83054e881c144ecd0b

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page