Skip to content

Commit 090773b

Browse files
committed
TST, fix for issue pandas-dev#17978.
moved generic thing to pandas.utils._hypothesis.py. not sure of what exactly was required to change but still tried to change the content as per review comments.
1 parent d30d6fd commit 090773b

File tree

3 files changed

+109
-97
lines changed

3 files changed

+109
-97
lines changed

doc/source/contributing.rst

+5-3
Original file line numberDiff line numberDiff line change
@@ -775,13 +775,13 @@ Tests that we have ``parametrized`` are now accessible via the test name, for ex
775775
test_cool_feature.py::test_dtypes[int8] PASSED
776776
test_cool_feature.py::test_series[int8] PASSED
777777
778-
Transitioning to ``hypothesis``
779-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
778+
Using ``hypothesis``
779+
~~~~~~~~~~~~~~~~~~~~
780780
With the transition to pytest, things have become easier for testing by having reduced boilerplate for test cases and also by utilizing pytest's features like parametizing, skipping and marking test cases.
781781
782782
However, one has to still come up with input data examples which can be tested against the functionality. There is always a possibility to skip testing an example which could have failed the test case.
783783
784-
Imagine if some framework could generate random input examples based on the property/specification of the function being tested. That is exactly what hypothesis does by generating the input data based on some set of specifications provided by the user.
784+
Hypothesis is a python package which helps in overcoming this issue by generating the input data based on some set of specifications provided by the user.
785785
e.g suppose we have to test python's sum function for a list of int.
786786
787787
Here is a sample test case using pytest:
@@ -841,6 +841,8 @@ output of test cases:
841841
========================== 1 passed in 0.33 seconds ===========================
842842
843843
The main difference in above example is use of a decorator "@given(st.lists(st.integers()))" which if applied to test case function, generates some random list of int, which is then assigned to parameter of test case.
844+
Above example clearly helps in adding more coverage for our test functions.
845+
844846
For more information about hypothesis or in general about property based testing, check below links:
845847
846848
- https://hypothesis.readthedocs.io/en/latest/quickstart.html

pandas/tests/reshape/test_util.py

+7-94
Original file line numberDiff line numberDiff line change
@@ -4,105 +4,18 @@
44
import pandas.util.testing as tm
55
from pandas.core.reshape.util import cartesian_product
66

7-
from hypothesis import strategies as st
8-
from hypothesis import given, settings, assume
7+
import string
98
from datetime import date
109
from dateutil import relativedelta
11-
import string
1210

11+
from pandas.util._hypothesis import (st,
12+
given,
13+
settings,
14+
get_seq,
15+
assume)
1316

14-
NO_OF_EXAMPLES_PER_TEST_CASE = 20
1517

16-
17-
def get_elements(elem_type):
18-
strategy = st.nothing()
19-
if elem_type == bool:
20-
strategy = st.booleans()
21-
elif elem_type == int:
22-
strategy = st.integers()
23-
elif elem_type == float:
24-
strategy = st.floats()
25-
elif elem_type == str:
26-
strategy = st.text(string.ascii_letters, max_size=10)
27-
return strategy
28-
29-
30-
@st.composite
31-
def get_seq(draw, types, mixed=False, min_size=None, max_size=None,
32-
transform_func=None):
33-
"""
34-
Helper function to generate strategy for creating lists.
35-
What constitute in the generated list is driven by the different
36-
parameters.
37-
38-
Parameters
39-
----------
40-
types: iterable sequence like tuple or list
41-
types which can be in the generated list.
42-
mixed: bool
43-
if True, list will contains elements from all types listed in arg,
44-
otherwise it will have elements only from types[0].
45-
min_size: int
46-
minimum size of the list.
47-
max_size: int
48-
maximum size of the list.
49-
transform_func: callable
50-
a callable which can be applied to whole list after it has been
51-
generated. It can think of as providing functionality of filter
52-
and map function.
53-
54-
Returns
55-
-------
56-
hypothesis lists strategy.
57-
58-
Examples
59-
--------
60-
seq_strategy = get_seq((int, str, bool),
61-
mixed=True, min_size=1, max_size=5)
62-
seq_strategy.example()
63-
Out[12]: ['lkYMSn', -2501, 35, 'J']
64-
seq_strategy.example()
65-
Out[13]: [True]
66-
seq_strategy.example()
67-
Out[14]: ['dRWgQYrBrW', True, False, 'gmsujJVDBM', 'Z']
68-
69-
seq_strategy = get_seq((int, bool),
70-
mixed=False,
71-
min_size=1,
72-
max_size=5,
73-
transform_func=lambda seq: [str(x) for x in seq])
74-
seq_strategy.example()
75-
Out[19]: ['-1892']
76-
seq_strategy.example()
77-
Out[20]: ['22', '66', '14785', '-26312', '32']
78-
seq_strategy.example()
79-
Out[21]: ['22890', '-15537', '96']
80-
"""
81-
strategy = st.nothing()
82-
if min_size is None:
83-
min_size = draw(st.integers(min_value=0, max_value=100))
84-
85-
if max_size is None:
86-
max_size = draw(st.integers(min_value=min_size, max_value=100))
87-
88-
assert min_size <= max_size, \
89-
'max_size must be greater than equal to min_size'
90-
91-
elem_strategies = []
92-
for elem_type in types:
93-
elem_strategies.append(get_elements(elem_type))
94-
if not mixed:
95-
break
96-
97-
if transform_func:
98-
strategy = draw(st.lists(st.one_of(elem_strategies),
99-
min_size=min_size,
100-
max_size=max_size).map(transform_func))
101-
else:
102-
strategy = draw(st.lists(st.one_of(elem_strategies),
103-
min_size=min_size,
104-
max_size=max_size))
105-
return strategy
18+
NO_OF_EXAMPLES_PER_TEST_CASE = 20
10619

10720

10821
class TestCartesianProduct(object):

pandas/util/_hypothesis.py

+97
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import string
2+
from hypothesis import (given,
3+
settings,
4+
assume,
5+
strategies as st,
6+
)
7+
8+
9+
def get_elements(elem_type):
10+
strategy = st.nothing()
11+
if elem_type == bool:
12+
strategy = st.booleans()
13+
elif elem_type == int:
14+
strategy = st.integers()
15+
elif elem_type == float:
16+
strategy = st.floats()
17+
elif elem_type == str:
18+
strategy = st.text(string.ascii_letters, max_size=10)
19+
return strategy
20+
21+
22+
@st.composite
23+
def get_seq(draw, types, mixed=False, min_size=None, max_size=None,
24+
transform_func=None):
25+
"""
26+
Helper function to generate strategy for creating lists.
27+
What constitute in the generated list is driven by the different
28+
parameters.
29+
30+
Parameters
31+
----------
32+
types: iterable sequence like tuple or list
33+
types which can be in the generated list.
34+
mixed: bool
35+
if True, list will contains elements from all types listed in arg,
36+
otherwise it will have elements only from types[0].
37+
min_size: int
38+
minimum size of the list.
39+
max_size: int
40+
maximum size of the list.
41+
transform_func: callable
42+
a callable which can be applied to whole list after it has been
43+
generated. It can think of as providing functionality of filter
44+
and map function.
45+
46+
Returns
47+
-------
48+
hypothesis lists strategy.
49+
50+
Examples
51+
--------
52+
seq_strategy = get_seq((int, str, bool),
53+
mixed=True, min_size=1, max_size=5)
54+
seq_strategy.example()
55+
Out[12]: ['lkYMSn', -2501, 35, 'J']
56+
seq_strategy.example()
57+
Out[13]: [True]
58+
seq_strategy.example()
59+
Out[14]: ['dRWgQYrBrW', True, False, 'gmsujJVDBM', 'Z']
60+
61+
seq_strategy = get_seq((int, bool),
62+
mixed=False,
63+
min_size=1,
64+
max_size=5,
65+
transform_func=lambda seq: [str(x) for x in seq])
66+
seq_strategy.example()
67+
Out[19]: ['-1892']
68+
seq_strategy.example()
69+
Out[20]: ['22', '66', '14785', '-26312', '32']
70+
seq_strategy.example()
71+
Out[21]: ['22890', '-15537', '96']
72+
"""
73+
strategy = st.nothing()
74+
if min_size is None:
75+
min_size = draw(st.integers(min_value=0, max_value=100))
76+
77+
if max_size is None:
78+
max_size = draw(st.integers(min_value=min_size, max_value=100))
79+
80+
assert min_size <= max_size, \
81+
'max_size must be greater than equal to min_size'
82+
83+
elem_strategies = []
84+
for elem_type in types:
85+
elem_strategies.append(get_elements(elem_type))
86+
if not mixed:
87+
break
88+
89+
if transform_func:
90+
strategy = draw(st.lists(st.one_of(elem_strategies),
91+
min_size=min_size,
92+
max_size=max_size).map(transform_func))
93+
else:
94+
strategy = draw(st.lists(st.one_of(elem_strategies),
95+
min_size=min_size,
96+
max_size=max_size))
97+
return strategy

0 commit comments

Comments
 (0)