Py.test: parametrize test cases from classes

Question:

I’m currently following this py.test example and it works out when I do not use classes, however when I introduce test cases into classes I am failing.

The smallest case I managed to write is the following:

import unittest

import pytest

class FixtureTestCase(unittest.TestCase):

    @pytest.mark.parametrize("test_input,expected", [
    ("3+5", 8),
    ("2+4", 6),
    ("6*9", 42),
    ])
    def test_1(self, a, b):
        self.assertEqual(a, b)

unfortunately when I execute

  py.test  test_suite.py

I get the error message:

  TypeError: test_1() takes exactly 3 arguments (1 given)

How can I do in order to generate a battery of test_1 tests?

Asked By: pafede2

||

Answers:

If you subclass from unittest.TestCase, your test methods cannot have additional arguments. If you simply subclass from object, it will work (though you’ll have to use regular assert statements instead of the TestCase.assertEqual methods.

import unittest

import pytest

class TestCase(object):

    @pytest.mark.parametrize("test_input,expected", [
    ("3+5", 8),
    ("2+4", 6),
    ("6*9", 42),
    ])
    def test_1(self, a, b):
        assert eval(a) == b

At that point though, it kind of begs the question why you’re using classes instead of just defining functions, since the test will essentially be the same, but require less overall boilerplate and code.

Answered By: Brendan Abel

Finally and considering the reply by @Brendan Abel and comments I managed to succeed in what I was intended to do by doing:

class TestCase(object):

    @parameterized.expand([
    ("negative", -1.5, -2.0),
    ("integer", 1, 1.0),
    ("large fraction", 1.6, 1),
    ])
    def test_floor(self, name, input, expected):
        assert_equal(math.floor(input), expected)


    @parameterized.expand([
    ("3+5", 8),
    ("2+4", 6),
    ("6*9", 42),
    ])
    def test_1(self, a, b):
        assert_equal(eval(a), b)

Then I am able to execute the tests by the nosetests command:

  nosetests -v --with-id class.py
Answered By: pafede2

I don’t know if this was the case 5 years ago, but these days you can use parameterized (https://pypi.org/project/parameterized/) with pytest to decorate test methods on a test class, yes including unittest.TestCase, without having to resort to nose. E.g.:

from unittest import TestCase
from parameterized import parameterized

class SomeTestCase(TestCase):

    @parameterized.expand([
        (1, 2),
        ('a', 'b')
    ])
    def test_something(self, param1, param2):
        ...

The only gotcha, but you better remember this, is that the decorator will generate new test methods for each listed input parameter, so you will not be able to run your original test method directly by specifying it on the command line. E.g. pytest some_test.py::SomeTestCase::test_something won’t work anymore (because your test method now expects two parameters). However, you can call the generated methods directly, for which you can get the name from either the pytest error output when you run the whole TestCase or by doing a pytest --collect-only.

Answered By: atleta

For those who still interested, I wrote a drop-in @pytest.mark.parametrize replacement for unittest.TestCase: https://github.com/MrMrRobat/parametrize

import unittest

from parametrize import parametrize

class FixtureTestCase(unittest.TestCase):

    @parametrize(
        "test_input,expected",
        [
            ("3+5", 8),
            ("2+4", 6),
            ("6*9", 42),
        ]
    )
    def test_1(self, test_input, expected):
        self.assertEqual(eval(test_input), expected)

$ pytest test.py::FixtureTestCase::test_1

Test session starts (platform: darwin, Python 3.9.4, pytest 6.2.4, pytest-sugar 0.9.4)
plugins: sugar-0.9.4, cov-2.11.1, mock-3.6.0
collecting ... 
 test.py ✓✓                                                       67% ██████▋   

―――――――――――――――――――――――― FixtureTestCase.test_1[6*9-42] ――――――――――――――――――――――――

self = <test.FixtureTestCase testMethod=test_1[6*9-42]>, test_input = '6*9'
expected = 42

    @parametrize(
        "test_input,expected",
        [
            ("3+5", 8),
            ("2+4", 6),
            ("6*9", 42),
        ]
    )
    def test_1(self, test_input, expected):
>       self.assertEqual(eval(test_input), expected)
E       AssertionError: 54 != 42

test.py:16: AssertionError

 test.py ⨯                                                       100% ██████████
=========================== short test summary info ============================
FAILED test.py::FixtureTestCase::test_1[6*9-42] - AssertionError: 54 != 42

Results (0.09s):
       2 passed
       1 failed
         - test.py:7 FixtureTestCase.test_1[6*9-42]
Answered By: Bobronium