summaryrefslogtreecommitdiff
path: root/python-checking.spec
diff options
context:
space:
mode:
Diffstat (limited to 'python-checking.spec')
-rw-r--r--python-checking.spec1068
1 files changed, 1068 insertions, 0 deletions
diff --git a/python-checking.spec b/python-checking.spec
new file mode 100644
index 0000000..a003487
--- /dev/null
+++ b/python-checking.spec
@@ -0,0 +1,1068 @@
+%global _empty_manifest_terminate_build 0
+Name: python-checking
+Version: 0.9.1
+Release: 1
+Summary: A small library for unit-testing
+License: MIT License
+URL: https://github.com/kotolex/checking
+Source0: https://mirrors.nju.edu.cn/pypi/web/packages/29/e1/679f236f513ac33f52e5f4bd6e36f733e9313c5a858ec1a7d40731c47472/checking-0.9.1.tar.gz
+BuildArch: noarch
+
+
+%description
+Test "__main__.check_cat" [Cat from 140288585437776] SUCCESS!
+```
+If you want to use a text file as a data source, you can use `DATA_FILE` helper function to skip the file handling boilerplate code:
+```python
+from checking import *
+DATA_FILE('files/data.txt', name='provider') # Use the file located at <module folder>/files/data.txt
+@test(data_provider='provider')
+def try_prov(it):
+ print(it)
+ is_true(it)
+```
+The helper lazy-loads specified data file line by line.
+Raises FileNotFoundError if the file is not found.
+Also, you can transform all the lines before feeding them into the test,
+for example delete trailing newlines at the end of each line:
+```python
+from checking import *
+DATA_FILE('files/data.txt', name='provider', map_function=str.rstrip) # Feed each line through str.rstrip()
+@test(data_provider='provider')
+def try_prov(it):
+ is_true(it)
+```
+If you don't specify provider_name for the DATA_FILE helper, file_path will be used:
+```python
+from checking import *
+DATA_FILE('data.txt') # Use text file located at the module folder. Note, that no provider_name is specified.
+@test(data_provider='data.txt') # Use the specified file_name parameter for provider lookup
+def try_prov(it):
+ is_true(it)
+```
+If your test suite uses a data provider more than once, you might want to avoid the IO overhead,
+if this provider fetches the data from some external source (database, file system, http request etc.).
+You can use the `cached` parameter to force the provider to fetch the data only once and store it into memory.
+Please, be varied of the memory consumption, because the cache persists until the whole suite is done running.
+Also, be careful when using the cache when running tests in parallel.
+DATA_FILE helper can use this parameter too.
+```python
+from checking import *
+DATA_FILE('data.csv', name='csv', cached=True) # Enable caching
+@test(data_provider='csv') # First provider use -- data is fetched from the file and stored into memory
+def check_one(it):
+ not_none(it)
+@test(data_provider='csv') # Second use -- no file reads, cached data is used
+def check_two(it):
+ not_none(it)
+if __name__ == '__main__':
+ start(0)
+```
+If your provider is a simple one-liner (string, list comprehension, generator expression, etc.),
+you can use the CONTAINER helper function to avoid full function definition boilerplate:
+```python
+from checking import *
+CONTAINER([e for e in range(10)], name='range') # Provide data from a listcomps, set provider name to 'range'
+@test(data_provider='range')
+def try_container(it):
+ is_true(it in range(10))
+```
+'name' parameter is optional, 'container' is used by default,
+but it's strongly recommended using a unique name:
+```python
+from checking import *
+CONTAINER((e for e in range(10))) # Provide data from a genexps
+@test(data_provider='container')
+def try_container(it):
+ is_true(it in range(10))
+```
+**Important!** You must define DATA_FILE or CONTAINER providers at the module scope, not in the fixtures and tests.
+### Test Parameters ###
+You can manage the test execution mode by passing a number of parameters to the @test decorator:
+**enabled** (bool) - if set to False, the test will be skipped, all other parameters are ignored. By default, set to True.
+**name** (str) - the name of the test. Is bound to the decorated function name if not specified.
+**description** (str) - test description. If absent, the test function docstring is used.
+If both description and docstring are present, description takes precedence.
+**data_provider** (str) - the name of the data provider to use with the test.
+If specified, the test function must take one argument to be fed with the data from the provider.
+Raises UnknownProviderName if no providers with the specified name found.
+**retries** (int) - the number of times to run the failing test. If test does not fail, no more runs attempted. By default, set to 1.
+**groups** (Tuple[str]) - a tuple of strings, representing the test group names a test is a part of.
+All tests belong to some test group, the default group holds all tests from the current module and is named after the module.
+Use this parameter to manage test execution groups.
+**priority** (int) - test priority. The higher the value the later the test will be executed.
+Use this parameter to fine tune test run order. By default, set to 0.
+**timeout** (int) - amount of time to wait for the test to end.
+If the time runs out, the thread running the test is terminated and the test is marked as "broken".
+Use sparingly due to potential memory leaks.
+**only_if** (Callable[None, bool]) - boolean predicate, which is evaluated before the test execution.
+The test will be executed only if the predicate evaluates to True.
+Use this parameter for conditional test execution e.g. run only if the OS is Linux, etc.
+## 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, Double, Stub 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, you 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 does not wait for user input.
+ result_text = our_weird_function_with_input_inside()
+ equals('TEST', result_text)
+```
+More convenient way is to use mock_input or mock_print for simple and most common cases.
+From code above we can test our_weird_function this way
+```python
+@test
+def check_input():
+ with mock_input(['test']): # Now input() just returns 'test', it does not wait for user input.
+ result_text = our_weird_function_with_input_inside()
+ equals('TEST', result_text)
+```
+Now let's say we have simple function with print inside and need to test it:
+```python
+def my_print(x):
+ print(x)
+@test
+def check_print():
+ with mock_print([]) as result: # now print just collects all to list result
+ my_print(1)
+ my_print('1')
+ equals([(1,), ('1',)], result) # checks all args are in result list
+```
+and more complicated case, when our function works forever, printing all inputs, until gets 'exit':
+```python
+def use_both():
+ while True:
+ word = input('text>>>')
+ if word == 'exit':
+ break
+ print(word)
+@test
+def check_print_and_input():
+ # you can see inputs will get 'a','b' and 'exit' to break cycle, all args will
+ # be collected to result list
+ with mock_input(['a', 'b', 'exit']), mock_print([]) as result:
+ use_both()
+ equals([('a',), ('b',)], result)
+```
+**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():
+ stub = Stub(text='test') # create simple stub, with attribute text equals to 'test'
+ with mock(requests, 'get', lambda x: stub): # Mock real requests with stub object
+ equals('test', func_with_get_inside('https://yandex.ru')) # Now no real requests be performed!
+```
+**3. Mock read/write to file**
+If you need to mock open function, push data to read from file and gets back with write to file, you can use
+mock_open context-manager
+```python
+def my_open():
+ # We read from one file, uppercase results and write to another file
+ with open('my_file.txt', encoding='utf-8') as f, open('another.txt', 'wt') as f2:
+ f2.write(f.readline().upper())
+@test
+def mock_open_both():
+ # Here we specify what we must "read from file" ('test') and where we want to get all writes(result)
+ with mock_open(on_read_text='test') as result:
+ my_open()
+ equals(['TEST'], result) # checks we get test uppercase
+```
+**4. 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.upper.was_called()) # 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.upper.returns('test') # Tells what to return, when upper will be call
+ result = function_with_str_inside(spy)
+ is_true(spy.upper.was_called())
+ equals('test', result) # verify our spy returns 'test'
+```
+Spy object can be created without original inner object and can be call itself, it can be useful when you need
+some dumb object to know it was called.
+```python
+@test
+def check_spy():
+ spy = Spy() # Create "empty" spy
+ spy() # Call it
+ is_true(spy.was_called()) # Checks spy was called
+```
+**5. TestDouble object**
+Test-Double object is like the Spy, but it saves original object behaviour, so its methods returns
+real object methods results if not specified otherwise.
+```python
+@test
+def check_double():
+ spy = TestDouble("string") # Create str double-object
+ equals(6, len(spy)) # Len returns 6 - the real length of original object ("string")
+ spy.len.returns(100) # Fake len result
+ equals(100, len(spy)) # Len now returns 100
+```
+**Important!** Both spy and TestDouble override **isinstance**, so they emulate type of the original object. It can be useful for
+testing functions, which has isinstance check inside. For example:
+```python
+def function_that_checks_class(obj):
+ if isinstance(obj, str): # check for argument type (string)
+ return "OK"
+ return "Not OK"
+@test
+def isinstance_check():
+ spy = Spy("fake string") # fake the real string
+ result = function_that_checks_class(spy) # get "OK" here, cause function thinks it's a string, not Spy
+ equals("OK", result)
+```
+**6. Stub object**
+Stub object is just a helper for testing, its purpose not to check or assert something, but to give data
+and perform some simple action, when application under test need it. Unlike spy or double, Stub
+is not remember calls, it just a simple replacement for some object with minimum or no logic inside.
+Let's say we have a function which gets some object, take its attribute, calculates something and
+return result. We wish to isolate our testing from real objects, just test important behaviour, besides
+this data-object can be hard to create or complicated.
+```python
+from checking import *
+# Our function to test, it get some object and use it attribute and method, but we just
+# need to test how it works!
+def function(some_object)->int:
+ initial_value = some_object.value
+ result = 2 + some_object.complicate_function()*initial_value # Some calculation we need to test
+ return result
+@test
+def check_with_stub():
+ stub = Stub(value=2) # Creates stub with attribute value=2
+ stub.complicate_function.returns(2) # Says, when complicate_function will be called returns 2
+ equals(6, function(stub)) # Asserts 6 == 2+(2*2)
+```
+Pay attention - when you look for some attribute in stub - it always has it! But it will be a wrapper to use with
+expression like `stub.any_attribute.returns('test')`.
+So, if you need to have some attribute (not method) on stub, you just use `stub.attr=10`, but for methods just use expression above.
+### Function start() to runs test at module ###
+You can execute all test at current module using function start(). For example:
+```python
+from checking import *
+@test
+def some_check():
+ equals(4, 2+2)
+if __name__ == '__main__':
+ start(3) # Here we run our test function some_check
+```
+There are parameters to run your tests in different ways:
+**suite_name** - name of the test-suite, to use in reports or in logs
+**listener** - object of Listener class, test listener, is the way to work with test results and execution
+DefaultListener is used by default. If set, then the verbose parameter is ignored (the one in the listener is used).
+**verbose** is the report detail, 0 - briefly (only dots and 1 letter), 1 - detail, indicating only failed
+tests, 2 - detail, indicating successful and fallen tests, 3 - detail and at the end, a list of fallen and broken ones
+If verbose is not between 0 and 3, then 0 is accepted
+Example (name and verbose)
+```python
+from checking import *
+@test
+def some_check():
+ equals(4, 2 + 2)
+@test
+def some_check_two():
+ equals(2, 1 + 1)
+@test
+def failed():
+ equals(5, 2 + 2) # Will fail
+@test
+def broken():
+ int('a') # Will be broken
+if __name__ == '__main__':
+ start(suite_name='My Suite', verbose=0)
+```
+This code will gave output (mention dots and chars!):
+```text
+
+%package -n python3-checking
+Summary: A small library for unit-testing
+Provides: python-checking
+BuildRequires: python3-devel
+BuildRequires: python3-setuptools
+BuildRequires: python3-pip
+%description -n python3-checking
+Test "__main__.check_cat" [Cat from 140288585437776] SUCCESS!
+```
+If you want to use a text file as a data source, you can use `DATA_FILE` helper function to skip the file handling boilerplate code:
+```python
+from checking import *
+DATA_FILE('files/data.txt', name='provider') # Use the file located at <module folder>/files/data.txt
+@test(data_provider='provider')
+def try_prov(it):
+ print(it)
+ is_true(it)
+```
+The helper lazy-loads specified data file line by line.
+Raises FileNotFoundError if the file is not found.
+Also, you can transform all the lines before feeding them into the test,
+for example delete trailing newlines at the end of each line:
+```python
+from checking import *
+DATA_FILE('files/data.txt', name='provider', map_function=str.rstrip) # Feed each line through str.rstrip()
+@test(data_provider='provider')
+def try_prov(it):
+ is_true(it)
+```
+If you don't specify provider_name for the DATA_FILE helper, file_path will be used:
+```python
+from checking import *
+DATA_FILE('data.txt') # Use text file located at the module folder. Note, that no provider_name is specified.
+@test(data_provider='data.txt') # Use the specified file_name parameter for provider lookup
+def try_prov(it):
+ is_true(it)
+```
+If your test suite uses a data provider more than once, you might want to avoid the IO overhead,
+if this provider fetches the data from some external source (database, file system, http request etc.).
+You can use the `cached` parameter to force the provider to fetch the data only once and store it into memory.
+Please, be varied of the memory consumption, because the cache persists until the whole suite is done running.
+Also, be careful when using the cache when running tests in parallel.
+DATA_FILE helper can use this parameter too.
+```python
+from checking import *
+DATA_FILE('data.csv', name='csv', cached=True) # Enable caching
+@test(data_provider='csv') # First provider use -- data is fetched from the file and stored into memory
+def check_one(it):
+ not_none(it)
+@test(data_provider='csv') # Second use -- no file reads, cached data is used
+def check_two(it):
+ not_none(it)
+if __name__ == '__main__':
+ start(0)
+```
+If your provider is a simple one-liner (string, list comprehension, generator expression, etc.),
+you can use the CONTAINER helper function to avoid full function definition boilerplate:
+```python
+from checking import *
+CONTAINER([e for e in range(10)], name='range') # Provide data from a listcomps, set provider name to 'range'
+@test(data_provider='range')
+def try_container(it):
+ is_true(it in range(10))
+```
+'name' parameter is optional, 'container' is used by default,
+but it's strongly recommended using a unique name:
+```python
+from checking import *
+CONTAINER((e for e in range(10))) # Provide data from a genexps
+@test(data_provider='container')
+def try_container(it):
+ is_true(it in range(10))
+```
+**Important!** You must define DATA_FILE or CONTAINER providers at the module scope, not in the fixtures and tests.
+### Test Parameters ###
+You can manage the test execution mode by passing a number of parameters to the @test decorator:
+**enabled** (bool) - if set to False, the test will be skipped, all other parameters are ignored. By default, set to True.
+**name** (str) - the name of the test. Is bound to the decorated function name if not specified.
+**description** (str) - test description. If absent, the test function docstring is used.
+If both description and docstring are present, description takes precedence.
+**data_provider** (str) - the name of the data provider to use with the test.
+If specified, the test function must take one argument to be fed with the data from the provider.
+Raises UnknownProviderName if no providers with the specified name found.
+**retries** (int) - the number of times to run the failing test. If test does not fail, no more runs attempted. By default, set to 1.
+**groups** (Tuple[str]) - a tuple of strings, representing the test group names a test is a part of.
+All tests belong to some test group, the default group holds all tests from the current module and is named after the module.
+Use this parameter to manage test execution groups.
+**priority** (int) - test priority. The higher the value the later the test will be executed.
+Use this parameter to fine tune test run order. By default, set to 0.
+**timeout** (int) - amount of time to wait for the test to end.
+If the time runs out, the thread running the test is terminated and the test is marked as "broken".
+Use sparingly due to potential memory leaks.
+**only_if** (Callable[None, bool]) - boolean predicate, which is evaluated before the test execution.
+The test will be executed only if the predicate evaluates to True.
+Use this parameter for conditional test execution e.g. run only if the OS is Linux, etc.
+## 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, Double, Stub 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, you 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 does not wait for user input.
+ result_text = our_weird_function_with_input_inside()
+ equals('TEST', result_text)
+```
+More convenient way is to use mock_input or mock_print for simple and most common cases.
+From code above we can test our_weird_function this way
+```python
+@test
+def check_input():
+ with mock_input(['test']): # Now input() just returns 'test', it does not wait for user input.
+ result_text = our_weird_function_with_input_inside()
+ equals('TEST', result_text)
+```
+Now let's say we have simple function with print inside and need to test it:
+```python
+def my_print(x):
+ print(x)
+@test
+def check_print():
+ with mock_print([]) as result: # now print just collects all to list result
+ my_print(1)
+ my_print('1')
+ equals([(1,), ('1',)], result) # checks all args are in result list
+```
+and more complicated case, when our function works forever, printing all inputs, until gets 'exit':
+```python
+def use_both():
+ while True:
+ word = input('text>>>')
+ if word == 'exit':
+ break
+ print(word)
+@test
+def check_print_and_input():
+ # you can see inputs will get 'a','b' and 'exit' to break cycle, all args will
+ # be collected to result list
+ with mock_input(['a', 'b', 'exit']), mock_print([]) as result:
+ use_both()
+ equals([('a',), ('b',)], result)
+```
+**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():
+ stub = Stub(text='test') # create simple stub, with attribute text equals to 'test'
+ with mock(requests, 'get', lambda x: stub): # Mock real requests with stub object
+ equals('test', func_with_get_inside('https://yandex.ru')) # Now no real requests be performed!
+```
+**3. Mock read/write to file**
+If you need to mock open function, push data to read from file and gets back with write to file, you can use
+mock_open context-manager
+```python
+def my_open():
+ # We read from one file, uppercase results and write to another file
+ with open('my_file.txt', encoding='utf-8') as f, open('another.txt', 'wt') as f2:
+ f2.write(f.readline().upper())
+@test
+def mock_open_both():
+ # Here we specify what we must "read from file" ('test') and where we want to get all writes(result)
+ with mock_open(on_read_text='test') as result:
+ my_open()
+ equals(['TEST'], result) # checks we get test uppercase
+```
+**4. 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.upper.was_called()) # 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.upper.returns('test') # Tells what to return, when upper will be call
+ result = function_with_str_inside(spy)
+ is_true(spy.upper.was_called())
+ equals('test', result) # verify our spy returns 'test'
+```
+Spy object can be created without original inner object and can be call itself, it can be useful when you need
+some dumb object to know it was called.
+```python
+@test
+def check_spy():
+ spy = Spy() # Create "empty" spy
+ spy() # Call it
+ is_true(spy.was_called()) # Checks spy was called
+```
+**5. TestDouble object**
+Test-Double object is like the Spy, but it saves original object behaviour, so its methods returns
+real object methods results if not specified otherwise.
+```python
+@test
+def check_double():
+ spy = TestDouble("string") # Create str double-object
+ equals(6, len(spy)) # Len returns 6 - the real length of original object ("string")
+ spy.len.returns(100) # Fake len result
+ equals(100, len(spy)) # Len now returns 100
+```
+**Important!** Both spy and TestDouble override **isinstance**, so they emulate type of the original object. It can be useful for
+testing functions, which has isinstance check inside. For example:
+```python
+def function_that_checks_class(obj):
+ if isinstance(obj, str): # check for argument type (string)
+ return "OK"
+ return "Not OK"
+@test
+def isinstance_check():
+ spy = Spy("fake string") # fake the real string
+ result = function_that_checks_class(spy) # get "OK" here, cause function thinks it's a string, not Spy
+ equals("OK", result)
+```
+**6. Stub object**
+Stub object is just a helper for testing, its purpose not to check or assert something, but to give data
+and perform some simple action, when application under test need it. Unlike spy or double, Stub
+is not remember calls, it just a simple replacement for some object with minimum or no logic inside.
+Let's say we have a function which gets some object, take its attribute, calculates something and
+return result. We wish to isolate our testing from real objects, just test important behaviour, besides
+this data-object can be hard to create or complicated.
+```python
+from checking import *
+# Our function to test, it get some object and use it attribute and method, but we just
+# need to test how it works!
+def function(some_object)->int:
+ initial_value = some_object.value
+ result = 2 + some_object.complicate_function()*initial_value # Some calculation we need to test
+ return result
+@test
+def check_with_stub():
+ stub = Stub(value=2) # Creates stub with attribute value=2
+ stub.complicate_function.returns(2) # Says, when complicate_function will be called returns 2
+ equals(6, function(stub)) # Asserts 6 == 2+(2*2)
+```
+Pay attention - when you look for some attribute in stub - it always has it! But it will be a wrapper to use with
+expression like `stub.any_attribute.returns('test')`.
+So, if you need to have some attribute (not method) on stub, you just use `stub.attr=10`, but for methods just use expression above.
+### Function start() to runs test at module ###
+You can execute all test at current module using function start(). For example:
+```python
+from checking import *
+@test
+def some_check():
+ equals(4, 2+2)
+if __name__ == '__main__':
+ start(3) # Here we run our test function some_check
+```
+There are parameters to run your tests in different ways:
+**suite_name** - name of the test-suite, to use in reports or in logs
+**listener** - object of Listener class, test listener, is the way to work with test results and execution
+DefaultListener is used by default. If set, then the verbose parameter is ignored (the one in the listener is used).
+**verbose** is the report detail, 0 - briefly (only dots and 1 letter), 1 - detail, indicating only failed
+tests, 2 - detail, indicating successful and fallen tests, 3 - detail and at the end, a list of fallen and broken ones
+If verbose is not between 0 and 3, then 0 is accepted
+Example (name and verbose)
+```python
+from checking import *
+@test
+def some_check():
+ equals(4, 2 + 2)
+@test
+def some_check_two():
+ equals(2, 1 + 1)
+@test
+def failed():
+ equals(5, 2 + 2) # Will fail
+@test
+def broken():
+ int('a') # Will be broken
+if __name__ == '__main__':
+ start(suite_name='My Suite', verbose=0)
+```
+This code will gave output (mention dots and chars!):
+```text
+
+%package help
+Summary: Development documents and examples for checking
+Provides: python3-checking-doc
+%description help
+Test "__main__.check_cat" [Cat from 140288585437776] SUCCESS!
+```
+If you want to use a text file as a data source, you can use `DATA_FILE` helper function to skip the file handling boilerplate code:
+```python
+from checking import *
+DATA_FILE('files/data.txt', name='provider') # Use the file located at <module folder>/files/data.txt
+@test(data_provider='provider')
+def try_prov(it):
+ print(it)
+ is_true(it)
+```
+The helper lazy-loads specified data file line by line.
+Raises FileNotFoundError if the file is not found.
+Also, you can transform all the lines before feeding them into the test,
+for example delete trailing newlines at the end of each line:
+```python
+from checking import *
+DATA_FILE('files/data.txt', name='provider', map_function=str.rstrip) # Feed each line through str.rstrip()
+@test(data_provider='provider')
+def try_prov(it):
+ is_true(it)
+```
+If you don't specify provider_name for the DATA_FILE helper, file_path will be used:
+```python
+from checking import *
+DATA_FILE('data.txt') # Use text file located at the module folder. Note, that no provider_name is specified.
+@test(data_provider='data.txt') # Use the specified file_name parameter for provider lookup
+def try_prov(it):
+ is_true(it)
+```
+If your test suite uses a data provider more than once, you might want to avoid the IO overhead,
+if this provider fetches the data from some external source (database, file system, http request etc.).
+You can use the `cached` parameter to force the provider to fetch the data only once and store it into memory.
+Please, be varied of the memory consumption, because the cache persists until the whole suite is done running.
+Also, be careful when using the cache when running tests in parallel.
+DATA_FILE helper can use this parameter too.
+```python
+from checking import *
+DATA_FILE('data.csv', name='csv', cached=True) # Enable caching
+@test(data_provider='csv') # First provider use -- data is fetched from the file and stored into memory
+def check_one(it):
+ not_none(it)
+@test(data_provider='csv') # Second use -- no file reads, cached data is used
+def check_two(it):
+ not_none(it)
+if __name__ == '__main__':
+ start(0)
+```
+If your provider is a simple one-liner (string, list comprehension, generator expression, etc.),
+you can use the CONTAINER helper function to avoid full function definition boilerplate:
+```python
+from checking import *
+CONTAINER([e for e in range(10)], name='range') # Provide data from a listcomps, set provider name to 'range'
+@test(data_provider='range')
+def try_container(it):
+ is_true(it in range(10))
+```
+'name' parameter is optional, 'container' is used by default,
+but it's strongly recommended using a unique name:
+```python
+from checking import *
+CONTAINER((e for e in range(10))) # Provide data from a genexps
+@test(data_provider='container')
+def try_container(it):
+ is_true(it in range(10))
+```
+**Important!** You must define DATA_FILE or CONTAINER providers at the module scope, not in the fixtures and tests.
+### Test Parameters ###
+You can manage the test execution mode by passing a number of parameters to the @test decorator:
+**enabled** (bool) - if set to False, the test will be skipped, all other parameters are ignored. By default, set to True.
+**name** (str) - the name of the test. Is bound to the decorated function name if not specified.
+**description** (str) - test description. If absent, the test function docstring is used.
+If both description and docstring are present, description takes precedence.
+**data_provider** (str) - the name of the data provider to use with the test.
+If specified, the test function must take one argument to be fed with the data from the provider.
+Raises UnknownProviderName if no providers with the specified name found.
+**retries** (int) - the number of times to run the failing test. If test does not fail, no more runs attempted. By default, set to 1.
+**groups** (Tuple[str]) - a tuple of strings, representing the test group names a test is a part of.
+All tests belong to some test group, the default group holds all tests from the current module and is named after the module.
+Use this parameter to manage test execution groups.
+**priority** (int) - test priority. The higher the value the later the test will be executed.
+Use this parameter to fine tune test run order. By default, set to 0.
+**timeout** (int) - amount of time to wait for the test to end.
+If the time runs out, the thread running the test is terminated and the test is marked as "broken".
+Use sparingly due to potential memory leaks.
+**only_if** (Callable[None, bool]) - boolean predicate, which is evaluated before the test execution.
+The test will be executed only if the predicate evaluates to True.
+Use this parameter for conditional test execution e.g. run only if the OS is Linux, etc.
+## 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, Double, Stub 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, you 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 does not wait for user input.
+ result_text = our_weird_function_with_input_inside()
+ equals('TEST', result_text)
+```
+More convenient way is to use mock_input or mock_print for simple and most common cases.
+From code above we can test our_weird_function this way
+```python
+@test
+def check_input():
+ with mock_input(['test']): # Now input() just returns 'test', it does not wait for user input.
+ result_text = our_weird_function_with_input_inside()
+ equals('TEST', result_text)
+```
+Now let's say we have simple function with print inside and need to test it:
+```python
+def my_print(x):
+ print(x)
+@test
+def check_print():
+ with mock_print([]) as result: # now print just collects all to list result
+ my_print(1)
+ my_print('1')
+ equals([(1,), ('1',)], result) # checks all args are in result list
+```
+and more complicated case, when our function works forever, printing all inputs, until gets 'exit':
+```python
+def use_both():
+ while True:
+ word = input('text>>>')
+ if word == 'exit':
+ break
+ print(word)
+@test
+def check_print_and_input():
+ # you can see inputs will get 'a','b' and 'exit' to break cycle, all args will
+ # be collected to result list
+ with mock_input(['a', 'b', 'exit']), mock_print([]) as result:
+ use_both()
+ equals([('a',), ('b',)], result)
+```
+**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():
+ stub = Stub(text='test') # create simple stub, with attribute text equals to 'test'
+ with mock(requests, 'get', lambda x: stub): # Mock real requests with stub object
+ equals('test', func_with_get_inside('https://yandex.ru')) # Now no real requests be performed!
+```
+**3. Mock read/write to file**
+If you need to mock open function, push data to read from file and gets back with write to file, you can use
+mock_open context-manager
+```python
+def my_open():
+ # We read from one file, uppercase results and write to another file
+ with open('my_file.txt', encoding='utf-8') as f, open('another.txt', 'wt') as f2:
+ f2.write(f.readline().upper())
+@test
+def mock_open_both():
+ # Here we specify what we must "read from file" ('test') and where we want to get all writes(result)
+ with mock_open(on_read_text='test') as result:
+ my_open()
+ equals(['TEST'], result) # checks we get test uppercase
+```
+**4. 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.upper.was_called()) # 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.upper.returns('test') # Tells what to return, when upper will be call
+ result = function_with_str_inside(spy)
+ is_true(spy.upper.was_called())
+ equals('test', result) # verify our spy returns 'test'
+```
+Spy object can be created without original inner object and can be call itself, it can be useful when you need
+some dumb object to know it was called.
+```python
+@test
+def check_spy():
+ spy = Spy() # Create "empty" spy
+ spy() # Call it
+ is_true(spy.was_called()) # Checks spy was called
+```
+**5. TestDouble object**
+Test-Double object is like the Spy, but it saves original object behaviour, so its methods returns
+real object methods results if not specified otherwise.
+```python
+@test
+def check_double():
+ spy = TestDouble("string") # Create str double-object
+ equals(6, len(spy)) # Len returns 6 - the real length of original object ("string")
+ spy.len.returns(100) # Fake len result
+ equals(100, len(spy)) # Len now returns 100
+```
+**Important!** Both spy and TestDouble override **isinstance**, so they emulate type of the original object. It can be useful for
+testing functions, which has isinstance check inside. For example:
+```python
+def function_that_checks_class(obj):
+ if isinstance(obj, str): # check for argument type (string)
+ return "OK"
+ return "Not OK"
+@test
+def isinstance_check():
+ spy = Spy("fake string") # fake the real string
+ result = function_that_checks_class(spy) # get "OK" here, cause function thinks it's a string, not Spy
+ equals("OK", result)
+```
+**6. Stub object**
+Stub object is just a helper for testing, its purpose not to check or assert something, but to give data
+and perform some simple action, when application under test need it. Unlike spy or double, Stub
+is not remember calls, it just a simple replacement for some object with minimum or no logic inside.
+Let's say we have a function which gets some object, take its attribute, calculates something and
+return result. We wish to isolate our testing from real objects, just test important behaviour, besides
+this data-object can be hard to create or complicated.
+```python
+from checking import *
+# Our function to test, it get some object and use it attribute and method, but we just
+# need to test how it works!
+def function(some_object)->int:
+ initial_value = some_object.value
+ result = 2 + some_object.complicate_function()*initial_value # Some calculation we need to test
+ return result
+@test
+def check_with_stub():
+ stub = Stub(value=2) # Creates stub with attribute value=2
+ stub.complicate_function.returns(2) # Says, when complicate_function will be called returns 2
+ equals(6, function(stub)) # Asserts 6 == 2+(2*2)
+```
+Pay attention - when you look for some attribute in stub - it always has it! But it will be a wrapper to use with
+expression like `stub.any_attribute.returns('test')`.
+So, if you need to have some attribute (not method) on stub, you just use `stub.attr=10`, but for methods just use expression above.
+### Function start() to runs test at module ###
+You can execute all test at current module using function start(). For example:
+```python
+from checking import *
+@test
+def some_check():
+ equals(4, 2+2)
+if __name__ == '__main__':
+ start(3) # Here we run our test function some_check
+```
+There are parameters to run your tests in different ways:
+**suite_name** - name of the test-suite, to use in reports or in logs
+**listener** - object of Listener class, test listener, is the way to work with test results and execution
+DefaultListener is used by default. If set, then the verbose parameter is ignored (the one in the listener is used).
+**verbose** is the report detail, 0 - briefly (only dots and 1 letter), 1 - detail, indicating only failed
+tests, 2 - detail, indicating successful and fallen tests, 3 - detail and at the end, a list of fallen and broken ones
+If verbose is not between 0 and 3, then 0 is accepted
+Example (name and verbose)
+```python
+from checking import *
+@test
+def some_check():
+ equals(4, 2 + 2)
+@test
+def some_check_two():
+ equals(2, 1 + 1)
+@test
+def failed():
+ equals(5, 2 + 2) # Will fail
+@test
+def broken():
+ int('a') # Will be broken
+if __name__ == '__main__':
+ start(suite_name='My Suite', verbose=0)
+```
+This code will gave output (mention dots and chars!):
+```text
+
+%prep
+%autosetup -n checking-0.9.1
+
+%build
+%py3_build
+
+%install
+%py3_install
+install -d -m755 %{buildroot}/%{_pkgdocdir}
+if [ -d doc ]; then cp -arf doc %{buildroot}/%{_pkgdocdir}; fi
+if [ -d docs ]; then cp -arf docs %{buildroot}/%{_pkgdocdir}; fi
+if [ -d example ]; then cp -arf example %{buildroot}/%{_pkgdocdir}; fi
+if [ -d examples ]; then cp -arf examples %{buildroot}/%{_pkgdocdir}; fi
+pushd %{buildroot}
+if [ -d usr/lib ]; then
+ find usr/lib -type f -printf "/%h/%f\n" >> filelist.lst
+fi
+if [ -d usr/lib64 ]; then
+ find usr/lib64 -type f -printf "/%h/%f\n" >> filelist.lst
+fi
+if [ -d usr/bin ]; then
+ find usr/bin -type f -printf "/%h/%f\n" >> filelist.lst
+fi
+if [ -d usr/sbin ]; then
+ find usr/sbin -type f -printf "/%h/%f\n" >> filelist.lst
+fi
+touch doclist.lst
+if [ -d usr/share/man ]; then
+ find usr/share/man -type f -printf "/%h/%f.gz\n" >> doclist.lst
+fi
+popd
+mv %{buildroot}/filelist.lst .
+mv %{buildroot}/doclist.lst .
+
+%files -n python3-checking -f filelist.lst
+%dir %{python3_sitelib}/*
+
+%files help -f doclist.lst
+%{_docdir}/*
+
+%changelog
+* Thu May 18 2023 Python_Bot <Python_Bot@openeuler.org> - 0.9.1-1
+- Package Spec generated