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.test(func: Callable[[...], Any]) Callable[[...], Any]

Mark a function or method as a test.

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.metadata(**metadata: Any) Callable[[...], Any]

Provides a way to set metadata in the test report for the decorated function.

Parameters:

metadata – the dictionary of metadata

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 the HTMLTestReport.

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 the doors_testreport and the HTMLTestReport.

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 (or maximum_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.raises(*exceptions: BaseException) Callable[[Callable[[...], Any]], Any]

Decorate a method to catch different exceptions.

Parameters:

*exceptions – a tuple of exceptions that are fetched.

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(reason: str) Callable[[Callable[[...], Any]], Any]

Decorate a method to be skipped in view of the occasion.

Parameters:

reason – the reason to be put into the test report.

Raises:

SkipTest – to skip the test.

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 that condition 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 return True or False. The decorated test is skipped if condition is True or returns True.

  • reason – the reason to be put into the test report.

Raises:

SkipTest – to skip the test if condition is or returns True.

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.stacktrace(func: Callable[[...], Any]) Callable[[...], Any]

The @stacktrace decorator decorates a method so that its call stack is printed to stdout every time it is called. This is useful for debugging.

htf.tags

Attach a tag to tests, fixtures or classes.

htf.tags(*tags: str) Callable[[Callable[[...], Any]], Any]

Tag a test or class.

Parameters:

*tags – the tags to be used

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)
htf.timed(limit: float) Callable[[Callable[[...], Any]], Any]

Decorate a method to ensure that it ends within a given time limit.

Parameters:

limit – the time limit in seconds.

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