Merge pull request #42 from pradyunsg/master
Add basic support for YAML data files
This commit is contained in:
commit
115a805476
72
ddt.py
72
ddt.py
|
@ -11,6 +11,13 @@ import os
|
|||
import re
|
||||
from functools import wraps
|
||||
|
||||
try:
|
||||
import yaml
|
||||
except ImportError: # pragma: no cover
|
||||
_have_yaml = False
|
||||
else:
|
||||
_have_yaml = True
|
||||
|
||||
__version__ = '1.0.3'
|
||||
|
||||
# These attributes will not conflict with any real python attribute
|
||||
|
@ -152,25 +159,56 @@ def process_file_data(cls, name, func, file_attr):
|
|||
cls_path = os.path.abspath(inspect.getsourcefile(cls))
|
||||
data_file_path = os.path.join(os.path.dirname(cls_path), file_attr)
|
||||
|
||||
def _raise_ve(*args): # pylint: disable-msg=W0613
|
||||
raise ValueError("%s does not exist" % file_attr)
|
||||
def create_error_func(message): # pylint: disable-msg=W0613
|
||||
def func(*args):
|
||||
raise ValueError(message % file_attr)
|
||||
return func
|
||||
|
||||
if os.path.exists(data_file_path) is False:
|
||||
# If file does not exist, provide an error function instead
|
||||
if not os.path.exists(data_file_path):
|
||||
test_name = mk_test_name(name, "error")
|
||||
add_test(cls, test_name, _raise_ve, None)
|
||||
else:
|
||||
data = json.loads(open(data_file_path).read())
|
||||
for i, elem in enumerate(data):
|
||||
if isinstance(data, dict):
|
||||
key, value = elem, data[elem]
|
||||
test_name = mk_test_name(name, key, i)
|
||||
elif isinstance(data, list):
|
||||
value = elem
|
||||
test_name = mk_test_name(name, value, i)
|
||||
if isinstance(value, dict):
|
||||
add_test(cls, test_name, func, **value)
|
||||
else:
|
||||
add_test(cls, test_name, func, value)
|
||||
add_test(cls, test_name, create_error_func("%s does not exist"), None)
|
||||
return
|
||||
|
||||
_is_yaml_file = data_file_path.endswith((".yml", ".yaml"))
|
||||
|
||||
# Don't have YAML but want to use YAML file.
|
||||
if _is_yaml_file and not _have_yaml:
|
||||
test_name = mk_test_name(name, "error")
|
||||
add_test(
|
||||
cls,
|
||||
test_name,
|
||||
create_error_func("%s is a YAML file, please install PyYAML"),
|
||||
None
|
||||
)
|
||||
return
|
||||
|
||||
with open(data_file_path) as f:
|
||||
# Load the data from YAML or JSON
|
||||
if _is_yaml_file:
|
||||
data = yaml.safe_load(f)
|
||||
else:
|
||||
data = json.load(f)
|
||||
|
||||
_add_tests_from_data(cls, name, func, data)
|
||||
|
||||
|
||||
def _add_tests_from_data(cls, name, func, data):
|
||||
"""
|
||||
Add tests from data loaded from the data file into the class
|
||||
|
||||
"""
|
||||
for i, elem in enumerate(data):
|
||||
if isinstance(data, dict):
|
||||
key, value = elem, data[elem]
|
||||
test_name = mk_test_name(name, key, i)
|
||||
elif isinstance(data, list):
|
||||
value = elem
|
||||
test_name = mk_test_name(name, value, i)
|
||||
if isinstance(value, dict):
|
||||
add_test(cls, test_name, func, **value)
|
||||
else:
|
||||
add_test(cls, test_name, func, value)
|
||||
|
||||
|
||||
def ddt(cls):
|
||||
|
|
|
@ -5,7 +5,12 @@ DDT consists of a class decorator ``ddt`` (for your ``TestCase`` subclass)
|
|||
and two method decorators (for your tests that want to be multiplied):
|
||||
|
||||
* ``data``: contains as many arguments as values you want to feed to the test.
|
||||
* ``file_data``: will load test data from a JSON file.
|
||||
* ``file_data``: will load test data from a JSON or YAML file.
|
||||
|
||||
.. note::
|
||||
|
||||
Only files ending with ".yml" and ".yaml" are loaded as YAML files. All
|
||||
other files are loaded as JSON files.
|
||||
|
||||
Normally each value within ``data`` will be passed as a single argument to
|
||||
your test method. If these values are e.g. tuples, you will have to unpack them
|
||||
|
@ -29,6 +34,14 @@ and ``test_data_list.json``:
|
|||
.. literalinclude:: ../test/test_data_list.json
|
||||
:language: javascript
|
||||
|
||||
.. literalinclude:: ../test/test_data_dict.yaml
|
||||
:language: yaml
|
||||
|
||||
and ``test_data_list.yaml``:
|
||||
|
||||
.. literalinclude:: ../test/test_data_list.yaml
|
||||
:language: yaml
|
||||
|
||||
And then run them with your favourite test runner, e.g. if you use nose::
|
||||
|
||||
$ nosetests -v test/test_example.py
|
||||
|
|
|
@ -3,3 +3,5 @@ coverage
|
|||
flake8
|
||||
nose
|
||||
six>=1.4.0
|
||||
PyYAML
|
||||
mock
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
unsorted_list:
|
||||
- 10
|
||||
- 15
|
||||
- 12
|
||||
|
||||
sorted_list: [ 15, 12, 50 ]
|
|
@ -0,0 +1,19 @@
|
|||
positive_integer_range:
|
||||
start: 0
|
||||
end: 2
|
||||
value: 1
|
||||
|
||||
negative_integer_range:
|
||||
start: -2
|
||||
end: 0
|
||||
value: -1
|
||||
|
||||
positive_real_range:
|
||||
start: 0.0
|
||||
end: 1.0
|
||||
value: 0.5
|
||||
|
||||
negative_real_range:
|
||||
start: -1.0
|
||||
end: 0.0
|
||||
value: -0.5
|
|
@ -0,0 +1,2 @@
|
|||
- "Hello"
|
||||
- "Goodbye"
|
|
@ -2,6 +2,19 @@ import unittest
|
|||
from ddt import ddt, data, file_data, unpack
|
||||
from test.mycode import larger_than_two, has_three_elements, is_a_greeting
|
||||
|
||||
try:
|
||||
import yaml
|
||||
except ImportError: # pragma: no cover
|
||||
have_yaml_support = False
|
||||
else:
|
||||
have_yaml_support = True
|
||||
del yaml
|
||||
|
||||
# A good-looking decorator
|
||||
needs_yaml = unittest.skipUnless(
|
||||
have_yaml_support, "Need YAML to run this test"
|
||||
)
|
||||
|
||||
|
||||
class Mylist(list):
|
||||
pass
|
||||
|
@ -32,17 +45,34 @@ class FooTestCase(unittest.TestCase):
|
|||
self.assertGreater(a, b)
|
||||
|
||||
@file_data("test_data_dict_dict.json")
|
||||
def test_file_data_dict_dict(self, start, end, value):
|
||||
def test_file_data_json_dict_dict(self, start, end, value):
|
||||
self.assertLess(start, end)
|
||||
self.assertLess(value, end)
|
||||
self.assertGreater(value, start)
|
||||
|
||||
@file_data('test_data_dict.json')
|
||||
def test_file_data_dict(self, value):
|
||||
def test_file_data_json_dict(self, value):
|
||||
self.assertTrue(has_three_elements(value))
|
||||
|
||||
@file_data('test_data_list.json')
|
||||
def test_file_data_list(self, value):
|
||||
def test_file_data_json_list(self, value):
|
||||
self.assertTrue(is_a_greeting(value))
|
||||
|
||||
@needs_yaml
|
||||
@file_data("test_data_dict_dict.yaml")
|
||||
def test_file_data_yaml_dict_dict(self, start, end, value):
|
||||
self.assertLess(start, end)
|
||||
self.assertLess(value, end)
|
||||
self.assertGreater(value, start)
|
||||
|
||||
@needs_yaml
|
||||
@file_data('test_data_dict.yaml')
|
||||
def test_file_data_yaml_dict(self, value):
|
||||
self.assertTrue(has_three_elements(value))
|
||||
|
||||
@needs_yaml
|
||||
@file_data('test_data_list.yaml')
|
||||
def test_file_data_yaml_list(self, value):
|
||||
self.assertTrue(is_a_greeting(value))
|
||||
|
||||
@data((3, 2), (4, 3), (5, 3))
|
||||
|
|
|
@ -2,9 +2,14 @@ import os
|
|||
import json
|
||||
|
||||
import six
|
||||
import mock
|
||||
|
||||
from ddt import ddt, data, file_data
|
||||
from nose.tools import assert_equal, assert_is_not_none, assert_raises
|
||||
from nose.tools import (
|
||||
assert_true, assert_equal, assert_is_not_none, assert_raises
|
||||
)
|
||||
|
||||
from test.mycode import has_three_elements
|
||||
|
||||
|
||||
@ddt
|
||||
|
@ -42,7 +47,7 @@ class FileDataDummy(object):
|
|||
|
||||
|
||||
@ddt
|
||||
class FileDataMissingDummy(object):
|
||||
class JSONFileDataMissingDummy(object):
|
||||
"""
|
||||
Dummy class to test the file_data decorator on when
|
||||
JSON file is missing
|
||||
|
@ -53,6 +58,18 @@ class FileDataMissingDummy(object):
|
|||
return value
|
||||
|
||||
|
||||
@ddt
|
||||
class YAMLFileDataMissingDummy(object):
|
||||
"""
|
||||
Dummy class to test the file_data decorator on when
|
||||
YAML file is missing
|
||||
"""
|
||||
|
||||
@file_data("test_data_dict_missing.yaml")
|
||||
def test_something_again(self, value):
|
||||
return value
|
||||
|
||||
|
||||
def test_data_decorator():
|
||||
"""
|
||||
Test the ``data`` method decorator
|
||||
|
@ -170,11 +187,23 @@ def test_feed_data_file_data():
|
|||
|
||||
def test_feed_data_file_data_missing_json():
|
||||
"""
|
||||
Test that a ValueError is raised
|
||||
Test that a ValueError is raised when JSON file is missing
|
||||
"""
|
||||
tests = filter(_is_test, FileDataMissingDummy.__dict__)
|
||||
tests = filter(_is_test, JSONFileDataMissingDummy.__dict__)
|
||||
|
||||
obj = FileDataMissingDummy()
|
||||
obj = JSONFileDataMissingDummy()
|
||||
for test in tests:
|
||||
method = getattr(obj, test)
|
||||
assert_raises(ValueError, method)
|
||||
|
||||
|
||||
def test_feed_data_file_data_missing_yaml():
|
||||
"""
|
||||
Test that a ValueError is raised when YAML file is missing
|
||||
"""
|
||||
tests = filter(_is_test, YAMLFileDataMissingDummy.__dict__)
|
||||
|
||||
obj = YAMLFileDataMissingDummy()
|
||||
for test in tests:
|
||||
method = getattr(obj, test)
|
||||
assert_raises(ValueError, method)
|
||||
|
@ -270,3 +299,24 @@ def test_feed_data_with_invalid_identifier():
|
|||
'test_data_with_invalid_identifier_1_32v2_g__Gmw845h_W_b53wi_'
|
||||
)
|
||||
assert_equal(method(), '32v2 g #Gmw845h$W b53wi.')
|
||||
|
||||
|
||||
@mock.patch('ddt._have_yaml', False)
|
||||
def test_load_yaml_without_yaml_support():
|
||||
"""
|
||||
Test that YAML files are not loaded if YAML is not installed.
|
||||
"""
|
||||
|
||||
@ddt
|
||||
class NoYAMLInstalledTest(object):
|
||||
|
||||
@file_data('test_data_dict.yaml')
|
||||
def test_file_data_yaml_dict(self, value):
|
||||
assert_true(has_three_elements(value))
|
||||
|
||||
tests = filter(_is_test, NoYAMLInstalledTest.__dict__)
|
||||
|
||||
obj = NoYAMLInstalledTest()
|
||||
for test in tests:
|
||||
method = getattr(obj, test)
|
||||
assert_raises(ValueError, method)
|
||||
|
|
Loading…
Reference in New Issue