Merge pull request #42 from pradyunsg/master

Add basic support for YAML data files
This commit is contained in:
Carles Barrobés 2016-05-30 10:24:07 +02:00
commit 115a805476
8 changed files with 186 additions and 26 deletions

72
ddt.py
View File

@ -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):

View File

@ -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

View File

@ -3,3 +3,5 @@ coverage
flake8
nose
six>=1.4.0
PyYAML
mock

6
test/test_data_dict.yaml Normal file
View File

@ -0,0 +1,6 @@
unsorted_list:
- 10
- 15
- 12
sorted_list: [ 15, 12, 50 ]

View File

@ -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

2
test/test_data_list.yaml Normal file
View File

@ -0,0 +1,2 @@
- "Hello"
- "Goodbye"

View File

@ -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))

View File

@ -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)