Py.test: parametrize test cases from classes
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]
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
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
.
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.