Decorators
Decorators can be used to change the behaviour of a method by decorating it.
Decorators that are included in htf are:
htf.test
Decorate function or method to be interpreted as a test.
@htf.test
def function():
pass
htf.after
Run additional commands after the test function.
def run_after(*args, **kwargs):
print("run_after", args, kwargs)
@after(run_after, 1, 2, 3, a="b")
def function():
print("function")
function()
# prints:
# function
# run_after (1, 2, 3) {'a': 'b'}
- htf.after(after_function: Callable[[...], Any], *after_args: Any, **after_kwargs: Any) Callable[[Callable[[...], Any]], Any]
The @after decorator decorates a method so that after_function is called after the decorated function is called itself.
- Parameters:
after_function – the function to be run after the decorated function.
*after_args – positional arguments to be passed to
afterFunction
.**after_kwargs – keyword-arguments to be passed to
afterFunction
.
htf.before
Run additional commands before the test function.
def run_before(*args, **kwargs):
print("runBefore", args, kwargs)
@before(run_before, 1, 2, 3, a="b")
def function():
print("function")
function()
# prints:
# run_before (1, 2, 3) {'a': 'b'}
# function
- htf.before(before_function: Callable[[...], Any], *before_args: Any, **before_kwargs: Any) Callable[[Callable[[...], Any]], Any]
The @before decorator decorates a method so that before_function is called before the decorated function is called itself.
- Parameters:
before_function – the function to be run before the decorated function.
*before_args – positional arguments to be passed to
beforeFunction
.**before_kwargs – keyword-arguments to be passed to
beforeFunction
.
htf.enqueue_exception
Decorate a method to fetch its exception that is enqueued into an exception queue.
@enqueue_exception(queue)
def decorated_method():
raise Exception("This is a test")
- htf.enqueue_exception(queue: Queue) Callable[[Callable[[...], Any]], Any]
Decorate a method to fetch its exception that is enqueued into an exception queue. The enqueued exception can be handled elsewhere. The caught exception can be raised after enqueueing it.
Just decorate you method with the queue to be used and every exception that is raised within the decorated method will be put into the queue before the exception is raised again.
- Parameters:
queue – the queue to enqueue the exception.
htf.metadata
Provides a way to set metadata in the test report for the decorated function.
@htf.metadata(name="Bob", age="20")
def test_with_metadata():
pass
htf.requirements
Used to show that a test checks a certain requirement.
@requirements("REQ_1", "REQ_2")
def test_with_requirements():
pass
- htf.requirements(*requirements: str) Callable[[Callable[[...], Any]], Any]
The
@requirements
decorator reports which requirements are checked by a test in the test report shown in theHTMLTestReport
.- Parameters:
*requirements – a tuple of strings that name the checked requirements, eg. “REQ_1”, “REQ_2”, etc.
htf.implements
Used to annotate a test with implemented tests. These links are used to generate links in test reports and to send test results back to ALM/QMS systems.
@implements("TEST-1", "TEST-2")
def test_implementing_test_specifications():
pass
- htf.implements(*tests: str) Callable[[Callable[[...], Any]], Any]
The
@implements
decorator reports which tests are implemented.- Parameters:
*tests – A tuple of strings that name the implemented tests, eg. “TC-1”, “TC-2”, etc. You can also pass a url, eg. “http://server/path/to/testcase” where “testcase” is the id
htf.meets_doors_requirements
To use in conjunction with the doors_testreport
to show that a test checks a certain requirement.
@meets_doors_requirements("doors://host:port/path-without-id-",
"REQ_1", "REQ_2")
def test_with_a_useful_name():
pass
- htf.meets_doors_requirements(base_url: str, *requirements: str) Callable[[Callable[[...], Any]], Any]
The
@meets_doors_requirements
decorator reports which requirements are checked by a test in the test report shown in thedoors_testreport
and theHTMLTestReport
.- Parameters:
base_url – the DOORS base url the links are created with.
*requirements – a tuple of strings that name the checked requirements, eg. “REQ_1”, “REQ_2”, etc.
htf.periodic
Run a decorated function periodically.
@periodic(period=1.0)
def periodically_called_every_second():
print("call")
@periodic
can also be used within classes
class ClassWithAPeriodicMethod():
@periodic(period=1.0)
def periodically_called_every_second(self):
print("call")
- htf.periodic(period: float, maximum_period: float | None = None, run_condition: Callable[[...], bool] | None = None, raise_exception: bool = True) Callable[[Callable[[...], Any]], Any]
Decorate a method to be run periodically.
- Parameters:
period – the period in seconds.
maximum_period=None – if set to a float >= period the periodic method may take up to maximum_period time without raising an exception. A warning is printed on stdout instead.
run_condition=None – a callable (method or lambda expression) that has to return true while the method is run periodically.
raise_exception=True – if set to
True
an exception is raised if the called method takes longer than the period (ormaximum_period
if set).
Warning
Non-realtime operating systems will not ensure timing accuracy.
htf.raises
The decorated function will catch an exception
@raises(AssertionError)
def test_failure(assertions):
assertions.assert_true(False)
@raises(TimeoutException, UnknownException)
def test_exceptions():
raise TimeoutException("timeout")
raise UnknownException("unknown exception")
htf.skip
Decorate to skip the test method or function.
@skip("This test is skipped in view of the occasion")
def test_always_skipped():
pass
htf.skip_if
Decorate to skip test if condition evaluates to True
at runtime.
Condition is called with the same parameters like the decorated test including all fixtures, etc. and if
it failes it is called without parameters.
def skip_tests():
return True
@htf.skip_if(skip_tests, "Skipped if skip_tests returns True")
def test_skipped_if():
pass
@htf.skip_if(True, "Always skipped")
def test_always_skipped():
pass
@htf.skip_if(False, "Never skipped")
def test_never_skipped():
pass
@htf.skip_if(lambda: True, "Always skipped")
def test_always_skipped_lambda():
pass
def condition():
return True
@htf.skip_if(condition, "skipped")
def test_always_skipped_with_bound_method(self):
pass
class SkipTests(htf.TestCase):
def condition(self, *args, **kwargs):
# can only be evaluated at runtime
return True
@htf.skip_if(condition, "classed skipped")
def test_skipped_by_instance_condition(self, assertions):
pass
def ddt_condition(a):
return a < 100
@htf.data(range(3))
@htf.skip_if(ddt_condition, "skipped ddt")
def test_ddt_skipped(a):
pass
def fixture_condition(assertions):
return True
@htf.skip_if(fixture_condition, "skipped with fixtures")
def test_skipped_with_fixtures(assertions):
pass
def catch_all_condition(*args, **kwargs):
return True
@htf.skip_if(catch_all_condition, "skipped with args and kwargs")
def test_args_kwargs(assertions, delay, step):
pass
- htf.skip_if(condition: bool | Callable[[...], bool], reason: str) Callable[[Callable[[...], Any]], Any]
Decorate a method to be skipped. The
condition
is evaluated at runtime. Note thatcondition
is evaluated twice by the test runner and the test itself. Thus, it should not introduce any side effects.- Parameters:
condition – the condition may be
True
,False
or a callable object that must returnTrue
orFalse
. The decorated test is skipped ifcondition
isTrue
or returnsTrue
.reason – the reason to be put into the test report.
- Raises:
SkipTest – to skip the test if
condition
is or returnsTrue
.
htf.stacktrace
Every time the decorated function is called, the call stack will be printed.
@stacktrace
def function():
pass
function() # will print(the call stack to stdout)
htf.timed
Ensure ending of a method or function within a given time limit.
@timed(limit=0.1)
def decorated_method_that_fails():
time.sleep(0.2)
@timed(limit=1.0)
def decorated_method_that_does_not_fail():
time.sleep(0.2)
User-Defined Decorators
Users can add their own decorators for their own code.
Since htf supports coroutines, functions and methods as tests, user-defined decorators need to support those as well.
Note that some decorators deliver a coroutine regardless of the decorated object.
Therefore, the decorator needs to check whether the decorated object is a coroutine or not and call it accordingly.
import asyncio
from functools import wraps
def user_decorator() -> Callable[[Callable[..., Any]], Any]:
def wrapper(func: Callable[..., Any]) -> Callable[..., Any]:
@wraps(func)
async def _user_decorator_wrapper(*args: Any, **kwargs: Any) -> Any:
# user code
if asyncio.iscoroutinefunction(func):
await func(*args, **kwargs) # call a coroutine
else:
func(*args, **kwargs) # call everything that is not a coroutine
# user code
return _user_decorator_wrapper
return wrapper
@user_decorator()
@htf.requirements("REQ-1")
def test_example():
pass
@user_decorator()
@htf.requirements("REQ-1")
async def test_example_async():
pass