2

I need to pass class members as parameters to pytest.mark.parametrize. The following code does not work (I've used simple members strings, but in my case they are complex and constructed):

import pytest
class TestSmth(object):
     def setup_class(cls):
         cls.a = "a"
         cls.b = "b"
         cls.c = "c"

     @pytest.mark.parametrize("test_input,expected", [
     (self.a, "a"),
     (self.b, "b"),
     (self.c, "c")
     ])
     def test_members(test_input, expected):
         assert test_input == expected

Is it possible to achieve such result? Or something similar?

Bartek Banachewicz
  • 38,596
  • 7
  • 91
  • 135
user2207495
  • 325
  • 2
  • 12

2 Answers2

2

This code doesn't work because Python decorators don't work that way. It is unrelated to the actual data in the test parameters. If you were the one writing the decorator, you could solve it by manually passing the instance, but this depends on the decorator itself doing the right thing.

As it stands, pytest decorators simply store the test data to be verified - and so you need to provide the data that's accessible by the time the desugared code is being ran, like so:

o = TestSmth()
o.setup_class()

@pytest.mark.parametrize("test_input,expected", [
(o.a, "a"),
(o.b, "b"),
(o.c, "c")
])
def test_members(test_input, expected):
    assert test_input == expected

I don't believe there are any inherent limitations to the data types being passed to those functions. If you still find that too limiting, pytest has a substantial support for custom parametrization schemes. Without the details of your actual use (rather than a mock example), it's hard to tell what would fit the problem best.

Bartek Banachewicz
  • 38,596
  • 7
  • 91
  • 135
  • I too have to pass some complex parameters for `test_input` and `expected` as in your example. One option is to just writing it out inside the arguments to parameterize.. or to define a Class, which contains the test_data. Is it best practice to define the test data in a class as OP pointed out? – alpha_989 Jul 06 '18 at 18:42
1

Found solution in pytest docs -> "Parametrizing test methods through per-class configuration¶"

#!/usr/bin/env python

import pytest


def pytest_generate_tests(metafunc):
    # called once per each test function
    funcarglist = metafunc.cls.params[metafunc.function.__name__]
    argnames = sorted(funcarglist[0])
    metafunc.parametrize(argnames, [[funcargs[name] for name in argnames]
            for funcargs in funcarglist])

class Complex(object):
    def __init__(self):
        self.a = "a"

class TestBase(object):
    A = Complex()
    params = {
        'test_a' : [dict (test_input=A)]
    }

    def test_a(self, test_input):
        assert test_input.a == "a"

It's ugly but it serves it's purpose.

user2207495
  • 325
  • 2
  • 12