Data Mastery
Data Mastery

Reputation: 2085

Pytest: Get rid of redudant Code used in testfunctions introduced by decorators

I have got the following very simple functions and tests:

from feedback import error_msg, info_msg, warn_msg
import pytest


def test_message_info(capsys):

    @info_msg
    def message():
        return "testmessage"
    message()

    assert capsys.readouterr().out == "INFO: testmessage\n"



def test_message_error(capsys):

    @error_msg
    def message():
        return "testmessage"
    message()

    assert capsys.readouterr().out == "ERROR: testmessage\n"


def test_message_warning(capsys):

    @warn_msg
    def message():
        return "testmessage"
    message()

    assert capsys.readouterr().out == "WARN: testmessage\n"

I know there are probably ways reduce these tests to a single test with a fixture or pytest.mark.parametrize, but I can not get my head around how to solve this. Does anyone know how to do this with a decorator?

Upvotes: 0

Views: 76

Answers (2)

Kale Kundert
Kale Kundert

Reputation: 1494

I'd use @pytest.mark.parametrize:

import pytest
import functools

# Quickly reimplement the `feedback` decorators, so that this
# example will be runnable.  These lines aren't important if
# you're just interested in the answer to the question at hand.

def log_msg(message):

    def decorate(f):

        @functools.wraps(f)
        def wrapper(*args, **kwargs):
            out = f(*args, **kwargs)
            print(f'{message}: {out}')

        return wrapper

    return decorate

info_msg = log_msg('INFO')
error_msg = log_msg('ERROR')
warn_msg = log_msg('WARN')

# Make a parametrized test:

@pytest.mark.parametrize(
        'decorator, expected', [
            (info_msg, 'INFO: testmessage\n'),
            (error_msg, 'ERROR: testmessage\n'),
            (warn_msg, 'WARN: testmessage\n'),
        ],
)
def test_decorators(decorator, expected, capsys):

    @decorator
    def message():
        return "testmessage"

    message()

    assert capsys.readouterr().out == expected

(Edited to make runnable, and to fix some bugs.)

Upvotes: 1

James
James

Reputation: 3411

Apply the decorator to the function "by hand" rather than using the decorator syntactic sugar. Then you can do all three tests in one loop.

from feedback import error_msg, info_msg, warn_msg
import pytest

MESSAGE_BY_DECORATOR = [
(info_msg, "INFO"),
(error_msg, "ERROR"),
(warn_msg, "WARN")]


def test_message_all(capsys):

    def message():
        return "testmessage"

    for decorator, expected in MESSAGE_BY_DECORATOR:
        decorated_message = decorator(message)
        decorated_message()
        assert capsys.readouterr().out == f"{expected}: testmessage\n"
        # you'll need to reset your capsys object here

Upvotes: 0

Related Questions