A small library for testing your code, made by tester for testers
Project description
README
A simple library for testing your own python code.
Key Features:
- no third-party dependencies, only standard library is used
- no need to inherit from any classes
- no need to name your files and/or tests with a prefix or a postfix called 'test'
- it is possible to interract with native python assert as well as library asserts
- simple and clear work with tests, data providers, checks, etc.
- ability to run based on a file with settings or passing arguments on command line
- automatic search for all tests within a current folder and subfolders
- flexible configuration of both tests and their groups, the ability to group tests and run only selected groups
- the ability to use both the built-in results processing tool and write your own one
- the ability to group, stop test by timeout, parallel launch, mocking without installing extra plugins
Installation
Just use your pip
pip install checking
First test
Simple example:
from checking import *
def my_function_to_test(a,b)
return a + b
@test
def any_name_you_like():
# Check 1+1=2
equals(2, my_function_to_test(1,1))
if __name__ == '__main__':
# Runs all tests in current module
start()
Only functions marked with @test annotation can be considered as a test and will be ran, you can name your tests whatever you feel like do, the main point is to put @test annotation
Basic Asserts
You can manipulate with simple Python assert if you want, but it is recommended to use simple and readable library asserts.
Standard checks:
#!python
@test
def checks_basic_asserts():
is_true('1', 'Error message') # checks, if value is True
is_false([], 'Error message') # checks, if value is False
equals(1, 1, 'Error message') # checks, if two objects are equal (==)
not_equals(1, 2, 'Error message') # checks, if two objects are equal (!=)
is_none(None, 'Error message') # checks, if object is None
not_none('1', 'Error message') # checks, if object is not None
contains(1, [1, 2, 3], 'Error message') # checks, if the second argument contains the first arg
not_contains(4, [1, 2, 3], 'Error message') # checks, if the second argument does not contains the first arg
Messages in all asserts are optional, but it strongly recommended to use them!
Work with or without exceptions
If you need to check the exception raises and its message you can write test like:
#!python
@test
def check_with_exception():
with waiting_exception(ZeroDivisionError) as e:
x = 1 / 0 # Here exception will be raised!
assert e.message == 'division by zero' # Check message (using python assert)
If no exception will be raised or it will be exception of another type then test will fail. Pay attention that you should check message (if you need) after exiting context manager, but not inside its scope!
You cannot use BaseException here and it strongly recommended not to use Exception as parent of all exceptions!
In some cases you have just need to run some code and make sure no exception raised. There is a special way for that:
#!python
@test
def no_exceptions_bad():
do_something() # Bad way! No asserts here, is that a test???
@test
def check_no_exception_raises():
with no_exception_expected(): # Explicitly say that we are not waiting exceptions
do_something() # Good way!
If any exception will be raised then test will fail
Managing test during execution
Sometimes, you need to fail or brake test in execution time on some reason (wrong OS, parameters, etc.)
#!python
@test
def must_fail_on_condition():
if some_condition():
test_fail('Expected fail!')
@test
def must_be_broken():
if some_condition():
test_brake('Expected brake!')
Soft and Fluent Asserts
Soft Assert
Soft Assert is a convenient way to check a few conditions before a failure. Standard test is preferably fail fast, and if some checks fail then the test stops. But sometimes you need to check a list of conditions, and check them to fail only at the end of the test, with all information what checks were failed. For instance, you have a json object and want to checks all its fields, but also do not want to stop test at first failed check because you want to know the state of all other fields!
#!python
@test
def check_all_json_fields():
my_json = get_my_json_somethere()
soft_assert = SoftAssert() # Creates an object of soft assert
soft_assert.check(lambda : equals(1, my_json['field1'], 'message')) # Check field, test will not fail here!
soft_assert.check(lambda : equals('text', my_json['field2'], 'message'))
soft_assert.contains(1,my_json['list'])
soft_assert.assert_all() # If something wrong, test will fail here!
Attention! You always should use assert_all() at the end of the test, only at the moment all exception (if something went wrong) will be raise.
Fluent Assert
Fluent assert is just a sugar to make chains of checks for the object; they are simple, readable, but it is NOT a soft asserts! If one of the checks will fail - test stops! Fluent asserts have analogues of the basic asserts, but also have their own types; you can find them all in checking/classes/fluent_asserts.py
#!python
@test
def check_list_valid():
my_list = get_my_list_somethere()
verify(my_list).is_not_none().AND.is_a(list).AND.contains(2).AND.is_sorted()
Data Providers
Often you need to run the same test with different data, there is @data annotation for that target. Mark function you want with @data annotation and you can use it in your tests. The function for data-provider should not have arguments and it has to return iterable, sequence or generator.
Important! Name of the provider has to be unique, you can specify it in parameter @data(name='provider') or it takes the function name by default It it not necessary to have data-provider with the test (in the same module)
Data-provider takes values one by one and pushes it to your test.
#!python
# Create data-provider
@data
def pairs():
return [(1, 1, 2), (2, 2, 4)] # Returns list of tuples
@test(data_provider='pairs') # Specify what provider to use
def check_sum(it): # Here we must have 1 argument for values of data-provider
equals(it[0] + it[1], it[2]) # Checks sum of first and second elements of tuple equal to third element
Test Parameters
Test is a function that marked with @test annotation, you can manage them with bunch of parameters:
enabled (bool) - if it is False then test will not be run, and all other parameters ignored. By default enabled = True
name (str) - name of the test, if not specify the function name will be used
data_provider (str) - name of the provider to use with test. If specified, test should have one argument, to get values from provider. If no providers found with that name then exception will raise!
retries (int) - how many times to run the test if it is failed. If test does not fail, no more runs attempted. By defaut it is 1
groups (Tuple[str]) - tuple of group names test belongs to, each test is a part of a some group, by default group is the module name, where test places It is the way to manage and collect tests to any groups.
priority (int) - priority of the test, by default it is 0. The higher value means that test will execute later. Priority is a way to run tests in some order.
timeout (int) - amount of time to wait test ends. If time is over, thread of the test will be interrupted and test will be mark as broken. Should be used carefully because of potential memory leaks!
only_if (Callable[None, bool]) - function which will be run before the test, and should return bool. Test will be execute only then if function returns 'True'! It is a way to make condition for some test, for instance, run only if the OS is Linux.
Fixtures
Each test group or all test-suite can have preconditions and post-actions. For example, open DB connection before test starts and close it after that. You can easily make it with before/after fixtures. The function that marked with before/after should be without arguments.
@before - run function before EACH test in group, by default group is current module, but you can specify it with parameter
@after - run function after EACH test in group, by default group is current module, but you can specify it with parameter. This function will not be run if there is @before and it failed!
#!python
@before(group_name='api')
def my_func():
do_some_precondition()
@after(group_name='api')
def another_func():
do_post_actions()
@before_group - function run once before running test in group, by default group is current module, but you can specify it with parameter.
@after_group - function run once after running all test in group, by default group is current module, but you can specify it with parameter. This function will not be run if there is @before_group and it failed, except using parameter always_run = True
#!python
@before_group(name='api')
def my_func():
do_some_precondition_for_whole_group()
@after_group(name='api', always_run =True)
def another_func():
do_post_actions_for_whole_group()
@before_suite - function runs once before any group at start of the test-suite
@after_suite - function run once after all groups, at the end of the test-suite. This function will not be run if there is @before_suite and it failed, except using parameter 'always_run = True'
#!python
@before_suite
def my_func():
print('start suite!')
@after_suite(always_run =True)
def another_func():
print('will be printed, even if before_suite failed!')
Mock and Spy
For testing purposes you sometimes need to fake some behaviour or to isolate your application from any other classes/libraries etc.
If you need your test to use fake object, without doing any real calls, yoy can use mocks:
1. Fake one of the builtin function.
Let say you need to test function which is using standard input() inside.
But you cannot wait for real user input during the test, so fake it with mock object.
#!python
def our_weird_function_with_input_inside():
text = input()
return text.upper()
@test
def mock_builtins_input():
with mock_builtins('input', lambda : 'test'): # Now input() just returns 'test', it is not to wait for user input
result_text = our_weird_function_with_input_inside()
equals('TEST', result_text)
2. Fake function of the 3-d party library
For working with other modules and libraries in test module, you need to import this module and to mock it function.
For example, you need to test function, which is using requests.get inside, but you do not want to make real http request. Let it mock
some_module_to_test.py
#!python
import requests
def func_with_get_inside(url):
response = requests.get(url)
return response.text
our_tests.py
#!python
import requests # need to import it for mock!
from some_module_to_test import func_with_get_inside
@test
def mock_requests_get():
# Lets create fake object
class Fake:
def __init__(self):
self.text = 'test'
with mock(requests, 'get', lambda x:Fake()): # Mock real requests with fake object
equals('test', func_with_get_inside('https://yandex.ru')) # Now no real requests be performed!
3. Spy object
Spy is the object which has all attributes of original, but spy not performed any action, all methods return None (if not specified what to return). Therefore, spy log all actions and arguments. It can be useful if your code has inner object and you need to test what functions were called.
#!python
def function_with_str_inside(value):
# Suppose we need to check upper was called here inside
return value.upper()
@test
def spy_for_str():
spy = Spy('it is a string') # Spy, which is like str, but it is not str!
function_with_str_inside(spy) # Send our spy instead a str
is_true(spy.was_function_called('upper')) # Verify upper was called
You can even specify what to return when some function of the spy will be called!
#!python
def function_with_str_inside(value):
# Suppose we need to check upper was called here inside
return value.upper()
@test
def spy_with_return():
spy = Spy('string')
spy.when_call_function_returns('upper', 'test') # Tells what to return, when upper will be call
result = function_with_str_inside(spy)
is_true(spy.was_function_called('upper'))
equals('test', result) # verify our spy returns 'test'
Contact me
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.