Merge branch 'pull-50'
diff --git a/.gitignore b/.gitignore
index e2beb9c..d0531b9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -20,3 +20,5 @@
.tox
build/
.cache/
+
+\.idea/
diff --git a/CHANGELOG.txt b/CHANGELOG.txt
index 5140586..c4628f1 100644
--- a/CHANGELOG.txt
+++ b/CHANGELOG.txt
@@ -1,3 +1,7 @@
+0.7.0 (2018-12-30)
+ * Added parameterized_class feature, for parameterizing entire test
+ classes.
+
0.6.2 (2018-03-11)
* Make sure that `setUp` and `tearDown` methods work correctly (#40)
* Raise a ValueError when input is empty (thanks @danielbradburn;
diff --git a/README.rst b/README.rst
index 42f295e..a77d3c4 100644
--- a/README.rst
+++ b/README.rst
@@ -11,66 +11,99 @@
.. code:: python
- # test_math.py
- from nose.tools import assert_equal
- from parameterized import parameterized
+ # test_math.py
+ from nose.tools import assert_equal
+ from parameterized import parameterized, parameterized_class
- import unittest
- import math
+ import unittest
+ import math
- @parameterized([
- (2, 2, 4),
- (2, 3, 8),
- (1, 9, 1),
- (0, 9, 0),
- ])
- def test_pow(base, exponent, expected):
- assert_equal(math.pow(base, exponent), expected)
+ @parameterized([
+ (2, 2, 4),
+ (2, 3, 8),
+ (1, 9, 1),
+ (0, 9, 0),
+ ])
+ def test_pow(base, exponent, expected):
+ assert_equal(math.pow(base, exponent), expected)
- class TestMathUnitTest(unittest.TestCase):
- @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)
+ class TestMathUnitTest(unittest.TestCase):
+ @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_class(('a', 'b', 'expected_sum', 'expected_product'), [
+ (1, 2, 3, 2),
+ (5, 5, 10, 25),
+ ])
+ class TestMathClass(unittest.TestCase):
+ def test_add(self):
+ assert_equal(self.a + self.b, self.expected_sum)
+
+ def test_multiply(self):
+ assert_equal(self.a * self.b, self.expected_product)
+
+ @parameterized_class([
+ { "a": 3, "expected": 2 },
+ { "b": 5, "expected": -4 },
+ ])
+ class TestMathClassDict(unittest.TestCase):
+ a = 1
+ b = 1
+
+ def test_subtract(self):
+ assert_equal(self.a - self.b, self.expected)
+
With nose (and nose2)::
$ nosetests -v test_math.py
- test_math.test_pow(2, 2, 4) ... ok
- test_math.test_pow(2, 3, 8) ... ok
- test_math.test_pow(1, 9, 1) ... ok
- test_math.test_pow(0, 9, 0) ... ok
test_floor_0_negative (test_math.TestMathUnitTest) ... ok
test_floor_1_integer (test_math.TestMathUnitTest) ... ok
test_floor_2_large_fraction (test_math.TestMathUnitTest) ... ok
+ test_math.test_pow(2, 2, 4, {}) ... ok
+ test_math.test_pow(2, 3, 8, {}) ... ok
+ test_math.test_pow(1, 9, 1, {}) ... ok
+ test_math.test_pow(0, 9, 0, {}) ... ok
+ test_add (test_math.TestMathClass_0) ... ok
+ test_multiply (test_math.TestMathClass_0) ... ok
+ test_add (test_math.TestMathClass_1) ... ok
+ test_multiply (test_math.TestMathClass_1) ... ok
+ test_subtract (test_math.TestMathClassDict_0) ... ok
----------------------------------------------------------------------
- Ran 7 tests in 0.002s
+ Ran 12 tests in 0.015s
OK
As the package name suggests, nose is best supported and will be used for all
further examples.
+
With py.test (version 2.0 and above)::
$ py.test -v test_math.py
- ============================== test session starts ==============================
- platform darwin -- Python 2.7.2 -- py-1.4.30 -- pytest-2.7.1
- collected 7 items
+ ============================= test session starts ==============================
+ platform darwin -- Python 3.6.1, pytest-3.1.3, py-1.4.34, pluggy-0.4.0
+ collecting ... collected 13 items
test_math.py::test_pow::[0] PASSED
test_math.py::test_pow::[1] PASSED
test_math.py::test_pow::[2] PASSED
test_math.py::test_pow::[3] PASSED
- test_math.py::TestMathUnitTest::test_floor_0_negative
- test_math.py::TestMathUnitTest::test_floor_1_integer
- test_math.py::TestMathUnitTest::test_floor_2_large_fraction
-
- =========================== 7 passed in 0.10 seconds ============================
+ test_math.py::TestMathUnitTest::test_floor_0_negative PASSED
+ test_math.py::TestMathUnitTest::test_floor_1_integer PASSED
+ test_math.py::TestMathUnitTest::test_floor_2_large_fraction PASSED
+ test_math.py::TestMathClass_0::test_add PASSED
+ test_math.py::TestMathClass_0::test_multiply PASSED
+ test_math.py::TestMathClass_1::test_add PASSED
+ test_math.py::TestMathClass_1::test_multiply PASSED
+ test_math.py::TestMathClassDict_0::test_subtract PASSED
+ ==================== 12 passed, 4 warnings in 0.16 seconds =====================
With unittest (and unittest2)::
@@ -78,15 +111,51 @@
test_floor_0_negative (test_math.TestMathUnitTest) ... ok
test_floor_1_integer (test_math.TestMathUnitTest) ... ok
test_floor_2_large_fraction (test_math.TestMathUnitTest) ... ok
+ test_add (test_math.TestMathClass_0) ... ok
+ test_multiply (test_math.TestMathClass_0) ... ok
+ test_add (test_math.TestMathClass_1) ... ok
+ test_multiply (test_math.TestMathClass_1) ... ok
+ test_subtract (test_math.TestMathClassDict_0) ... ok
----------------------------------------------------------------------
- Ran 3 tests in 0.000s
+ Ran 8 tests in 0.001s
OK
(note: because unittest does not support test decorators, only tests created
with ``@parameterized.expand`` will be executed)
+With green::
+
+ $ green test_math.py -vvv
+ test_math
+ TestMathClass_1
+ . test_method_a
+ . test_method_b
+ TestMathClass_2
+ . test_method_a
+ . test_method_b
+ TestMathClass_3
+ . test_method_a
+ . test_method_b
+ TestMathUnitTest
+ . test_floor_0_negative
+ . test_floor_1_integer
+ . test_floor_2_large_fraction
+ TestMathClass_0
+ . test_add
+ . test_multiply
+ TestMathClass_1
+ . test_add
+ . test_multiply
+ TestMathClassDict_0
+ . test_subtract
+
+ Ran 12 tests in 0.121s
+
+ OK (passes=9)
+
+
Installation
------------
@@ -109,8 +178,10 @@
* -
- Py2.6
- Py2.7
- - Py3.3
- Py3.4
+ - Py3.5
+ - Py3.6
+ - Py3.7
- PyPy
* - nose
- yes
@@ -118,18 +189,24 @@
- yes
- yes
- yes
+ - yes
+ - yes
* - nose2
- yes
- yes
- yes
- yes
- yes
+ - yes
+ - yes
* - py.test
- yes
- yes
- yes
- yes
- yes
+ - yes
+ - yes
* - | unittest
| (``@parameterized.expand``)
- yes
@@ -137,6 +214,8 @@
- yes
- yes
- yes
+ - yes
+ - yes
* - | unittest2
| (``@parameterized.expand``)
- yes
@@ -144,6 +223,8 @@
- yes
- yes
- yes
+ - yes
+ - yes
Dependencies
------------
@@ -350,6 +431,48 @@
OK
+Finally ``@parameterized.expand_class`` parameterizes an entire class, using
+either a list of attributes, or a list of dicts that will be applied to the
+class:
+
+.. code:: python
+
+ from yourapp.models import User
+ from parameterized import parameterized_class
+
+ @parameterized_class(("username", "access_level", "expected_status_code"), [
+ ("user_1", 1, 200),
+ ("user_2", 2, 404)
+ ])
+ class TestUserAccessLevel(TestCase):
+ def setUp(self):
+ self.client.force_login(User.objects.get(username=self.username)[0])
+
+ def test_url_a(self):
+ response = self.client.get("/url")
+ self.assertEqual(response.status_code, self.expected_status_code)
+
+ def tearDown(self):
+ self.client.logout()
+
+
+ @parameterized_class([
+ { "username": "user_1", "access_level": 1 },
+ { "username": "user_2", "access_level": 2, "expected_status_code": 404 },
+ ])
+ class TestUserAccessLevel(TestCase):
+ expected_status_code = 200
+
+ def setUp(self):
+ self.client.force_login(User.objects.get(username=self.username)[0])
+
+ def test_url_a(self):
+ response = self.client.get('/url')
+ self.assertEqual(response.status_code, self.expected_status_code)
+
+ def tearDown(self):
+ self.client.logout()
+
Migrating from ``nose-parameterized`` to ``parameterized``
----------------------------------------------------------
diff --git a/parameterized/parameterized.py b/parameterized/parameterized.py
index 2b6aa78..fae361b 100644
--- a/parameterized/parameterized.py
+++ b/parameterized/parameterized.py
@@ -291,6 +291,7 @@
_test_runner_guess = None
return _test_runner_guess
+
class parameterized(object):
""" Parameterize a test case::
@@ -519,3 +520,61 @@
@classmethod
def to_safe_name(cls, s):
return str(re.sub("[^a-zA-Z0-9_]+", "_", s))
+
+
+def parameterized_class(attrs, input_values=None):
+ """ Parameterizes a test class by setting attributes on the class.
+
+ Can be used in two ways:
+
+ 1) With a list of dictionaries containing attributes to override::
+
+ @parameterized_class([
+ { "username": "foo" },
+ { "username": "bar", "access_level": 2 },
+ ])
+ class TestUserAccessLevel(TestCase):
+ ...
+
+ 2) With a tuple of attributes, then a list of tuples of values:
+
+ @parameterized_class(("username", "access_level"), [
+ ("foo", 1),
+ ("bar", 2)
+ ])
+ class TestUserAccessLevel(TestCase):
+ ...
+
+ """
+
+ if isinstance(attrs, string_types):
+ attrs = [attrs]
+
+ input_dicts = (
+ attrs if input_values is None else
+ [dict(zip(attrs, vals)) for vals in input_values]
+ )
+
+ def decorator(base_class):
+ test_class_module = sys.modules[base_class.__module__].__dict__
+ for idx, input_dict in enumerate(input_dicts):
+ test_class_dict = dict(base_class.__dict__)
+ test_class_dict.update(input_dict)
+
+ name_suffix = input_values and input_values[idx]
+ if isinstance(name_suffix, (list, tuple)) and len(input_values) > 0:
+ name_suffix = name_suffix[0]
+ name_suffix = (
+ "_%s" %(name_suffix, ) if isinstance(name_suffix, string_types) else
+ ""
+ )
+
+ name = "%s_%s%s" %(
+ base_class.__name__,
+ idx,
+ name_suffix,
+ )
+
+ test_class_module[name] = type(name, (base_class, ), test_class_dict)
+
+ return decorator
diff --git a/parameterized/test.py b/parameterized/test.py
index 4193e6e..7bc6b49 100644
--- a/parameterized/test.py
+++ b/parameterized/test.py
@@ -7,7 +7,7 @@
from .parameterized import (
PY3, PY2, parameterized, param, parameterized_argument_value_pairs,
- short_repr, detect_runner, SkipTest
+ short_repr, detect_runner, parameterized_class, SkipTest,
)
def assert_contains(haystack, needle):
@@ -401,3 +401,57 @@
@parameterized(cases_over_10)
def test_cases_over_10(input, expected):
assert_equal(input, expected-1)
+
+
+@parameterized_class(("a", "b", "c"), [
+ ("foo", 1, 2),
+ ("bar", 3, 0),
+ (0, 1, 2),
+])
+class TestParameterizedClass(TestCase):
+ expect([
+ "TestParameterizedClass_0_foo:test_method_a('foo', 1, 2)",
+ "TestParameterizedClass_0_foo:test_method_b('foo', 1, 2)",
+ "TestParameterizedClass_1_bar:test_method_a('bar', 3, 0)",
+ "TestParameterizedClass_1_bar:test_method_b('bar', 3, 0)",
+ "TestParameterizedClass_2:test_method_a(0, 1, 2)",
+ "TestParameterizedClass_2:test_method_b(0, 1, 2)",
+ ])
+
+ def _assertions(self, test_name):
+ assert hasattr(self, "a")
+ assert_equal(self.b + self.c, 3)
+ missing_tests.remove("%s:%s(%r, %r, %r)" %(
+ self.__class__.__name__,
+ test_name,
+ self.a,
+ self.b,
+ self.c,
+ ))
+
+ def test_method_a(self):
+ self._assertions("test_method_a")
+
+ def test_method_b(self):
+ self._assertions("test_method_b")
+
+
+@parameterized_class([
+ {"foo": 1},
+ {"bar": 1},
+])
+class TestParameterizedClassDict(TestCase):
+ expect([
+ "TestParameterizedClassDict_0:test_method(1, 0)",
+ "TestParameterizedClassDict_1:test_method(0, 1)",
+ ])
+
+ foo = 0
+ bar = 0
+
+ def test_method(self):
+ missing_tests.remove("%s:test_method(%r, %r)" %(
+ self.__class__.__name__,
+ self.foo,
+ self.bar,
+ ))
diff --git a/setup.py b/setup.py
index 4eac2e3..396ff94 100644
--- a/setup.py
+++ b/setup.py
@@ -14,7 +14,7 @@
setup(
name="parameterized",
- version="0.6.1",
+ version="0.6.3",
url="https://github.com/wolever/parameterized",
license="FreeBSD",
author="David Wolever",
diff --git a/tox.ini b/tox.ini
index c58219f..8bceac2 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,6 +1,5 @@
[tox]
-envlist=py{27,35,36,py}-{nose,nose2,pytest,unit,unit2}
-
+envlist=py{27,35,36,37,py}-{nose,nose2,pytest,unit,unit2}
[testenv]
deps=
nose