.. _Assertions: Assertions ********** Introduction ============ Multitest's assertion logic can be accessed via the ``result`` argument of the testcase methods. Assertion methods can be called directly from the result object (e.g. ``result.`` or from its namespaces ``result..``. The content below contains testcase snippets, for complete examples please see please see :ref:`here `. Execution Behavior ================== In Testplan, the testcase execution does **NOT** stop after a failing assertion. This is because in our experience, assertions are used to check for correctness of output values rather than determining which execution path was taken. Consequently, we find that it is more efficient to execute all assertions within a testcase, because it avoids the typical problem of fixing one assertion simply to find another one later on. If some assertions rely on the result of previous ones and does not make sense to be executed if the previous failed, the ``passed`` attribute of the returned assertion entry can be used like this example: .. code-block:: python @testcase def sample_testcase(self, env, result): item = get_item() entry = result.true(isinstance(item, dict), description='Check if dict') if entry.passed is True: result.contain('key', item.keys(), description='.keys() used') If the test should be stopped on an assertion failure, an exception can be raised or use a *return* statement like this example: .. code-block:: python @testcase def sample_testcase(self, env, result): entry = result.true(isinstance(5, float), description='Check if float') if entry.passed is False: raise RuntimeError('5 is not a float.') # Or result.log('5 is not a float. Aborting testcase.') return Basic Assertions ================ Basic assertions can be used for common test cases, and accessible directly from the ``result`` object. :py:meth:`result.true ` ---------------------------------------------------------------------- Checks if the ``value`` is `truthy`. .. code-block:: python @testcase def sample_testcase(self, env, result): result.true(isinstance(5, int), description='Truthiness check') Sample output: .. code-block:: bash $ test_plan.py --verbose ... Truthiness check - Pass ... :py:meth:`result.false ` ------------------------------------------------------------------------ Checks if the ``value`` is `falsy`. .. code-block:: python @testcase def sample_testcase(self, env, result): result.false(isinstance(5, str), description='Falsiness check') Sample output: .. code-block:: bash $ test_plan.py --verbose ... Falsiness check - Pass ... :py:meth:`result.fail ` ---------------------------------------------------------------------- Creates an explicit failure, a common use case is to use it with conditions. .. code-block:: python @testcase def sample_testcase(self, env, result): ... if unexpected_result: result.fail('Invalid outcome, result: {}'.format(unexpected_result)) Sample output: .. code-block:: bash $ test_plan.py --verbose ... Invalid outcome, result: ... - Fail ... :py:meth:`result.equal / result.eq ` ------------------------------------------------------------------------------------ Equality assertion, checks if ``reference`` is equal to the ``value``. .. code-block:: python @testcase def sample_testcase(self, env, result): result.equal('foo', 'foo', description='Equality example') Sample output: .. code-block:: bash $ test_plan.py --verbose ... Equality example - Pass foo == foo ... :py:meth:`result.not_equal / result.ne ` -------------------------------------------------------------------------------------------- Inequality assertion, checks if ``reference`` is not equal to the ``value``. .. code-block:: python @testcase def sample_testcase(self, env, result): result.equal('foo', 'bar', description='Inequality example') Sample output: .. code-block:: bash $ test_plan.py --verbose ... Inequality example - Pass foo != bar ... :py:meth:`result.less / result.lt ` ---------------------------------------------------------------------------------- Comparison assertion, checks if ``reference`` is less than the ``value``. .. code-block:: python @testcase def sample_testcase(self, env, result): result.less(2, 12, description='Less comparison example') Sample output: .. code-block:: bash $ test_plan.py --verbose ... Less comparison example - Pass 2 < 12 ... :py:meth:`result.less_equal / result.le ` ---------------------------------------------------------------------------------------------- Comparison assertion, checks if ``reference`` is less than or equal to the ``value``. .. code-block:: python @testcase def sample_testcase(self, env, result): result.less_equal(2, 12, description='Less equal comparison example') Sample output: .. code-block:: bash $ test_plan.py --verbose ... Less equal comparison example - Pass 2 <= 12 ... :py:meth:`result.greater / result.gt ` ---------------------------------------------------------------------------------------- Comparison assertion, checks if ``reference`` is greater than the ``value``. .. code-block:: python @testcase def sample_testcase(self, env, result): result.greater(10, 5, description='Greater comparison example') Sample output: .. code-block:: bash $ test_plan.py --verbose ... Greater comparison example - Pass 10 > 5 ... :py:meth:`result.greater_equal / result.ge ` ---------------------------------------------------------------------------------------------------- Comparison assertion, checks if ``reference`` is greater than or equal the ``value``. .. code-block:: python @testcase def sample_testcase(self, env, result): result.greater_equal(10, 5, description='Greater equal comparison example') Sample output: .. code-block:: bash $ test_plan.py --verbose ... Greater equal comparison example - Pass 10 >= 5 ... :py:meth:`result.isclose ` ---------------------------------------------------------------------------- Checks if ``first`` is close to ``second`` without requiring them to be exactly equal. .. code-block:: python @testcase def sample_testcase(self, env, result): result.isclose(100, 101, rel_tol=0.01, abs_tol=0.0, description='Approximate equality example') Sample output: .. code-block:: bash $ test_plan.py --verbose ... Approximate equality example - Pass 100 ~= 101 (rel_tol: 0.01, abs_tol: 0.0) ... :py:meth:`result.contain ` ---------------------------------------------------------------------------- Membership assertion, checks if ``member`` is in the ``container``. .. code-block:: python @testcase def sample_testcase(self, env, result): result.contain('foo', ['foo', 'bar', 'baz'], description='List membership example') Sample output: .. code-block:: bash $ test_plan.py --verbose ... List membership example - Pass 'foo' in ['foo', 'bar', 'baz'] ... :py:meth:`result.not_contain ` ------------------------------------------------------------------------------------ Membership assertion, checks if ``member`` is not in the ``container``. .. code-block:: python @testcase def sample_testcase(self, env, result): result.not_contain('foo', {'bar': 1, 'baz': 2}, description='Dict membership example') Sample output: .. code-block:: bash $ test_plan.py --verbose ... Dict membership example - Pass 'foo' not in {'bar': 1, 'baz': 2} ... :py:meth:`result.equal_slices ` -------------------------------------------------------------------------------------- Equality assertion on iterable slices, checks if slices of ``reference`` is equal to slices of the ``value``. .. code-block:: python @testcase def sample_testcase(self, env, result): result.equal_slices( [1, 2, 3, 4, 5, 6, 7, 8], ['a', 'b', 3, 4, 'c', 'd', 7, 8], slices=[slice(2, 4), slice(6, 8)], description='Comparison of slices' ) Sample output: .. code-block:: bash $ test_plan.py --verbose ... Comparison of slices - Pass slice(2, 4, None) Actual: [3, 4] Expected: [3, 4] slice(6, 8, None) Actual: [7, 8] Expected: [7, 8] ... :py:meth:`result.equal_exclude_slices ` ------------------------------------------------------------------------------------------------------ Equality assertion on iterables, checks if the items of ``reference`` and ``value`` which are outside the given slices match. .. code-block:: python result.equal_exclude_slices( [1, 2, 3, 4, 5], ['a', 'b', 3, 4, 5], slices=[slice(0, 2)], description='Comparison of slices (exclusion)' ) Sample output: .. code-block:: bash $ test_plan.py --verbose ... Comparison of slices (exclusion) - Pass slice(0, 2, None) Actual: [3, 4, 5] Expected: [3, 4, 5] ... :py:meth:`result.raises ` -------------------------------------------------------------------------- Should be used as a context manager, checks if the block of code raises any of the given error types. Supports additional checks via ``pattern`` and ``func`` arguments. .. code-block:: python @testcase def sample_testcase(self, env, result): with result.raises(KeyError): {'foo': 3}['bar'] # Exception message pattern check (`re.search` is used implicitly) with result.raises( ValueError, pattern='foobar', description='Exception raised with custom pattern.' ): raise ValueError('abc foobar xyz') # Custom function check (func should accept # exception object as a single arg) class MyException(Exception): def __init__(self, value): self.value = value def custom_func(exc): return exc.value % 2 == 0 with result.raises( MyException, func=custom_func, description='Exception raised with custom func.' ): raise MyException(4) Sample output: .. code-block:: bash $ test_plan.py --verbose ... Exception Raised - Pass instance of KeyError Exception raised with custom pattern. - Pass instance of ValueError Pattern: foobar Exception message: abc foobar xyz Exception raised with custom func. - Pass instance of MyException Function: ... :py:meth:`result.not_raises ` ---------------------------------------------------------------------------------- Should be used as a context manager, checks if the block of code `does not` raise any of the given error types. Supports additional checks via ``pattern`` and ``func`` arguments, meaning it can also check if a certain type of exception has been raised without matching the given ``pattern`` or ``func``. .. code-block:: python @testcase def sample_testcase(self, env, result): class MyException(Exception): def __init__(self, value): self.value = value def custom_func(exc): return exc.value % 2 == 0 # `not_raises` passes when raised exception # type does match any of the declared exception classes # It is logically inverse of `result.raises`. with result.not_raises(TypeError): {'foo': 3}['bar'] # `not_raises` can also check if a certain exception has been raised # WITHOUT matching the given `pattern` or `func` # Exception type matches but pattern does not -> Pass with result.not_raises( ValueError, pattern='foobar', description='Exception not raised with custom pattern.' ): raise ValueError('abc') # Exception type matches but func does not -> Pass with result.not_raises( MyException, func=custom_func, description='Exception not raised with custom func.' ): raise MyException(5) Sample output: .. code-block:: bash $ test_plan.py --verbose ... Exception Not Raised - Pass not instance of TypeError Exception not raised with custom pattern. - Pass not instance of ValueError Pattern: foobar Exception message: abc Exception not raised with custom func. - Pass not instance of MyException Function: ... :py:meth:`result.diff ` ---------------------------------------------------------------------- Line diff assertion. Checks if textual content ``first`` and ``second`` have difference with given options. If difference found, generates a list of strings showing the delta. .. code-block:: python @testcase def sample_testcase(self, env, result): first, second = '', '' with open('1.txt', 'r') as f1: first = f1.read() with open('2.txt', 'r') as f2: second = f2.read() result.diff( first, second, unified=3, description='Compare 1.txt and 2.txt in unified mode' ) result.diff( ['bacon\r\n', 'eggs\r\n', 'ham\r\n', 'guido\r\n'], ['python\n', 'eggy\n', 'h a m\n', 'monty\n', '\tguido\n'], ignore_whitespaces=True, description='Compare 2 lists of text with whitespaces ignored' ) Sample output: .. code-block:: bash $ test_plan.py --verbose ... Compare 1.txt and 2.txt in unified mode - Pass a.text: aaa bbb ccc ddd eee [truncated]... b.text: aaa bbb ccc ddd eee [truncated]... a.text == b.text Compare 2 lists of text with whitespaces ignored - Fail File: /d/d1/shared/yitaor/ets.testplan/ets/testplan/testplan/run/test_script.py Line: 49 a.text: bacon eggs ham guido b.text: python eggy h a m monty guido Differences ( -w ): 1,2c1,2 < bacon < eggs --- > python > eggy 3a4 > monty ... :py:meth:`result.log ` -------------------------------------------------------------------- Add a log entry in the console output and the report to make the output more human readable. .. code-block:: python @testcase def sample_testcase(self, env, result): result.log( 'Start driver "{}"'.format(env.db.cfg.name)) result.log( 'Database file "{}" of driver "{}" created at "{}"'.format( env.db.cfg.db_name, env.db.cfg.name, env.db.db_path), description='Details of database file')) data = {100: 'foo', 200: ['bar', 'baz']} result.log(data, description='Log of raw data') .. code-block:: bash $ test_plan.py --verbose ... Start driver "db" Details of database file Database file "mydb" of driver "db" created at "path/to/mydb" Log of raw data {100: 'foo', 200: ['bar', 'baz']} ... :py:meth:`result.markdown ` ------------------------------------------------------------------------------ Add Markdown into the report. Useful for displaying blocks of formatted text, code, messages, images etc. Downloadable examples that use markdown assertion can be found :ref:`here `. .. code-block:: python result.markdown(""" Testplan is a [Python](http://python.org) package that can start a local live environment, setup mocks, connections to services and run tests against these. """, description="Testplan" ) :py:meth:`result.log_html ` ------------------------------------------------------------------------------ A shortcut of :py:meth:`result.markdown ` but disable escape flag. Downloadable examples that use html assertion can be found :ref:`here `. .. code-block:: python result.html("""
Testplan
""", description="Testplan" ) .. warning:: Embedded HTML does not support