-
-
Notifications
You must be signed in to change notification settings - Fork 18.4k
TST: add hypothesis-based tests #20590
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Addition of "hypothesis usage" in test cases of tests/reshape/test_util.py as kind of POC.
Hello @sushobhit27! Thanks for updating the PR.
Comment last updated on May 22, 2018 at 04:09 Hours UTC |
You'll need to add hypothesis to one (or all) of the |
@TomAugspurger Now all test have passed. Can you please check what else is required at my end. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will have to look
this is adding lots of code in a very specific file
need a more general way to do this
test are failing in this |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Most contributors -- and maintainers :) -- will be unfamiliar with hypothesis. Could you add a section to the contributing documentation, along with links to the hypothesis docs? Mainly I'd be interested in hearing about what tests makes sense for hypothesis, and perhaps a small examples using it.
pandas/tests/reshape/test_util.py
Outdated
|
||
@st.composite | ||
def get_seq(draw, types, mixed=False, min_size=None, max_size=None, transform_func=None): | ||
"""helper function to generate strategy for creating lists. parameters define the nature of to be generated list. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These lines are too long (check the log output or run flake8 locally)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, we use the numpy docstring standard: http://pandas-docs.github.io/pandas-docs-travis/contributing_docstring.html
pandas/tests/reshape/test_util.py
Outdated
def test_datetimeindex(self): | ||
# regression test for GitHub issue #6439 | ||
# make sure that the ordering on datetimeindex is consistent | ||
x = date_range('2000-01-01', periods=2) | ||
d = st.dates(min_value=date(1900, 1, 1), max_value=date(2100, 1, 1)).example() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why generate them here instead of using @given
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no specific reason but copy pasted after testing on console, as example() function can be easily used to check generated value :) and ya it can be moved to "given" decorator.
To be clear, I wasn't suggesting a change. I haven't used hypothesis much.
It'd be good to establish best practices since this will be new to
pandas contributors.
…On Tue, Apr 3, 2018 at 10:51 AM, sushobhit27 ***@***.***> wrote:
***@***.**** commented on this pull request.
------------------------------
In pandas/tests/reshape/test_util.py
<#20590 (comment)>:
> def test_datetimeindex(self):
# regression test for GitHub issue #6439
# make sure that the ordering on datetimeindex is consistent
- x = date_range('2000-01-01', periods=2)
+ d = st.dates(min_value=date(1900, 1, 1), max_value=date(2100, 1, 1)).example()
no specific reason but copy pasted after testing on console, as example()
function can be easily used to check generated value :) and ya it can be
moved to "given" decorator.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#20590 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/ABQHIgBoCofe-vdd4O7iDpVpbeUiDZ4yks5tk5pogaJpZM4TEidT>
.
|
@TomAugspurger,
In my personal experience, I have found it to be most helpful in reducing huge parametized test cases where many examples are overlapping or can be tested by a single hypothesis statement. In very layman terms, take another example of testing python's sum function using hypothesis by just a single statement. @given(st.lists(st.integers())) While, IMO in case of example based testing at least 3-4 example would be required in terms of parametize statement. Below are some links for property bases testing usage: |
Great, could you summarize appropriately for a new subsection in
contributing.rst? :)
…On Tue, Apr 3, 2018 at 11:36 AM, sushobhit27 ***@***.***> wrote:
@TomAugspurger <https://github.com/TomAugspurger>,
Main purpose of using hypothesis or property based testing is to just
write test cases based on the property/specification of the functionality
being tested and let the framework generate all the different and random
test cases for it.
This will initially make the test cases fail but due to which we are able
to find edge cases and then keep on refining boundary for input data of our
test cases. e.g in below example initially test case failed when x was a
list with empty string, thus in a way helped me to figure out that this is
a case for test_empty function (due to which I adeed assume(len(x) != 0)
statement).
def test_simple(self):
x, y = list('ABC'), [1, 22]
result1, result2 = cartesian_product([x, y])
expected1 = np.array(['A', 'A', 'B', 'B', 'C', 'C'])
expected2 = np.array([1, 22, 1, 22, 1, 22])
tm.assert_numpy_array_equal(result1, expected1)
tm.assert_numpy_array_equal(result2, expected2)
@settings(max_examples=NO_OF_EXAMPLES_PER_TEST_CASE)
@given(get_seq((str,), False, 1, 1),
get_seq((int,), False, 1, 2))
def test_simple(self, x, y):
x = list(x[0])
# non-empty test case is handled in test_empty, therefore ignore it here
assume(len(x) != 0)
result1, result2 = cartesian_product([x, y])
expected1 = np.array([item1 for item1 in x for item2 in y])
expected2 = np.array([item2 for item1 in x for item2 in y])
tm.assert_numpy_array_equal(result1, expected1)
tm.assert_numpy_array_equal(result2, expected2)
In my personal experience, I have found it to be most helpful in reducing
huge parametized test cases where many examples are overlapping or can be
tested by a single hypothesis statement.
In very layman terms, take another example of testing python's sum
function using hypothesis by just a single statement.
from hypothesis import strategies as st
from hypothesis import given
@given <https://github.com/given>(st.lists(st.integers()))
def test_sum(seq):
total = 0
for item in seq:
total += item
assert sum(seq) == total
While, IMO in case of example based testing at least 3-4 example would be
required in terms of parametize statement.
Below are some links for property bases testing usage:
https://hypothesis.readthedocs.io/en/latest/quickstart.html
http://blog.jessitron.com/2013/04/property-based-testing-what-is-it.html
https://hypothesis.works/articles/what-is-property-based-testing/
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#20590 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/ABQHIo5e_FqkZQxJzfMGX5crRKRa50xeks5tk6UagaJpZM4TEidT>
.
|
@TomAugspurger sure, I will do the same in a day or two in addition to incorporating other review comments. |
Addition of "hypothesis usage" in test cases of tests/reshape/test_util.py as kind of POC. Incorporate review comments. Resolve flake8 warning.
fail. add hypothesis package requirement.
Codecov Report
@@ Coverage Diff @@
## master #20590 +/- ##
==========================================
- Coverage 91.84% 91.79% -0.05%
==========================================
Files 153 154 +1
Lines 49502 49534 +32
==========================================
+ Hits 45463 45471 +8
- Misses 4039 4063 +24
Continue to review full report at Codecov.
|
Addition of "hypothesis usage" in test cases of tests/reshape/test_util.py as kind of POC. Incorporate review comments. Resolve flake8 warning. Add section for hypothesis in contributing.rst
Addition of "hypothesis usage" in test cases of tests/reshape/test_util.py as kind of POC. Incorporate review comments. Resolve flake8 warning. Add section for hypothesis in contributing.rst
@TomAugspurger Now all test are passing and I have also refactored the code with review comments. Also added section of hypothesis in contributing.rst. |
ci/requirements-2.7_LOCALE.run
Outdated
@@ -10,3 +10,4 @@ matplotlib=1.4.3 | |||
sqlalchemy=0.8.1 | |||
lxml | |||
scipy | |||
hypothesis>=3.46.0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
these are user requirements, not testing requirements. so remove from EACH of these except for environment-dev, rather adding ci/install_travis_travis, install_circle and appveyor.yaml (search for moto and put it next to that)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Exactly in which file I should add the requirement, as earlier, CI tests failed in absence of hypothesis package requirement in *.run
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I put them above
ci/install_travis_travis
, ci/install_circle
and appveyor.yaml
doc/source/contributing.rst
Outdated
@@ -775,6 +775,78 @@ Tests that we have ``parametrized`` are now accessible via the test name, for ex | |||
test_cool_feature.py::test_dtypes[int8] PASSED | |||
test_cool_feature.py::test_series[int8] PASSED | |||
|
|||
Transitioning to ``hypothesis`` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is not transition, its just Using hypthosis
|
||
|
||
output of test cases: | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
make this a bit more succint. hypthosis not going to replace much of our parameterized tests, rather in some cases will simply add more coverage. so downscale this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does it mean changing only content or also changing example?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the context, make this whole section shorter
pandas/tests/reshape/test_util.py
Outdated
@@ -4,31 +4,147 @@ | |||
import pandas.util.testing as tm | |||
from pandas.core.reshape.util import cartesian_product | |||
|
|||
from hypothesis import strategies as st |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
any hypothesis related things that we want to import, pls put in a separate file in pandas.util._hypthoesis.py and import from there. (these are the generic things similar to what we do in conftest.py)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree.
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.
test_empty was failing due to "hypothesis.errors.FailedHealthCheck" error on travis only, therefore decrease the size for lists.
@jreback , @TomAugspurger |
ci/requirements-2.7_LOCALE.run
Outdated
@@ -10,3 +10,4 @@ matplotlib=1.4.3 | |||
sqlalchemy=0.8.1 | |||
lxml | |||
scipy | |||
hypothesis>=3.46.0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I put them above
ci/install_travis_travis
, ci/install_circle
and appveyor.yaml
doc/source/contributing.rst
Outdated
@@ -775,6 +775,80 @@ Tests that we have ``parametrized`` are now accessible via the test name, for ex | |||
test_cool_feature.py::test_dtypes[int8] PASSED | |||
test_cool_feature.py::test_series[int8] PASSED | |||
|
|||
Using ``hypothesis`` | |||
~~~~~~~~~~~~~~~~~~~~ | |||
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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remove this 'transition to pytest
|
||
|
||
output of test cases: | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the context, make this whole section shorter
@@ -0,0 +1,97 @@ | |||
import string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add a doc-string to this module
pandas/util/_hypothesis.py
Outdated
|
||
|
||
def get_elements(elem_type): | ||
strategy = st.nothing() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add doc-strings to each
pandas/tests/reshape/test_util.py
Outdated
from datetime import date | ||
from dateutil import relativedelta | ||
|
||
from pandas.util._hypothesis import (st, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
make this simpler.
from pandas.util import _hypothesis as hp
pandas/tests/reshape/test_util.py
Outdated
assume) | ||
|
||
|
||
NO_OF_EXAMPLES_PER_TEST_CASE = 20 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hard-code this for now in the examples. have to see how this behaves.
pandas/tests/reshape/test_util.py
Outdated
def test_simple(self): | ||
x, y = list('ABC'), [1, 22] | ||
@settings(max_examples=NO_OF_EXAMPLES_PER_TEST_CASE) | ||
@given(get_seq((str,), False, 1, 1), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is there any way to name these things for given
, pretty non-intuitive here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We are already giving them a name in some cases, as arguments in test case function. e.g
@given(st.lists(st.nothing()),
get_seq((int,), False, min_size=1, max_size=10),
get_seq((str,), False, min_size=1, max_size=10))
def test_empty(self, empty_list, list_of_int, list_of_str):
but local function can be added like below:
def get_str_list_with_single_element():
return get_seq((str,), False, 1, 1)
and then used as below
@given(get_str_list_with_single_element(),
get_seq((int,), False, 1, 2))
def test_simple(self, x, y):
but that would be too cumbersome. Instead, for each argument in given decorator, having comments explaining each returned strategy can be more suitable.
pandas/tests/reshape/test_util.py
Outdated
@given(get_seq((str,), False, 1, 1), | ||
get_seq((int,), False, 1, 2)) | ||
def test_simple(self, x, y): | ||
x = list(x[0]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why is this is a list?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
to map test case as closely as possible to original test case, where x = list('ABC').
Although, x = st.lists(st.text(string.ascii_letters, min_size=1, max_size=1), min_size=1), would have achieved the same effect of getting list of single characters, but it was not possible using get_seq function.
pandas/util/_hypothesis.py
Outdated
from hypothesis import (given, | ||
settings, | ||
assume, | ||
strategies as st, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you add some direct documentation / examples in this file
Incorporate review comments.
Incorporate review comments.
@jreback I have incorporated all review comments except "removing hypothesis dependency from most of the files" as I could not really understand your below comment: ci/install_travis_travis, ci/install_circle and appveyor.yaml" |
@jreback, @TomAugspurger |
@sushobhit27 I indicated where you need to update these files in |
Remove hypothesis requirement from *.run files.
@jreback I have tried to do changes as per your comments and now have removed hypothesis dependency from all *.run, except from the 3 files which you mentioned, although I am not sure if I did it correctly. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
does this causes multiple tests to be run (like parameterized)? are these deterministic? these for sure need to be as we don't want non-deterministic ones on the ci (IOW that cannot be reproduced locally). if they are seed based this would be fine.
ci/environment-dev.yaml
Outdated
@@ -13,3 +13,4 @@ dependencies: | |||
- pytz | |||
- setuptools>=3.3 | |||
- sphinx | |||
- hypothesis>=3.46.0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
need a new-line
ci/install_circle.sh
Outdated
@@ -65,6 +65,7 @@ fi | |||
echo "[create env: ${REQ_BUILD}]" | |||
time conda create -n pandas -q --file=${REQ_BUILD} || exit 1 | |||
time conda install -n pandas pytest>=3.1.0 || exit 1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you can just add it on the previous line
ci/install_travis.sh
Outdated
@@ -104,6 +104,7 @@ if [ -e ${REQ} ]; then | |||
fi | |||
|
|||
time conda install -n pandas pytest>=3.1.0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same
ci/requirements-2.7_COMPAT.run
Outdated
@@ -11,4 +11,4 @@ psycopg2 | |||
pymysql=0.6.0 | |||
sqlalchemy=0.7.8 | |||
xlsxwriter=0.5.2 | |||
jinja2=2.8 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
revert these, they shouldn't have any changes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will rebase my branch.
@@ -775,6 +775,46 @@ Tests that we have ``parametrized`` are now accessible via the test name, for ex | |||
test_cool_feature.py::test_dtypes[int8] PASSED | |||
test_cool_feature.py::test_series[int8] PASSED | |||
|
|||
Using ``hypothesis`` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add a ref tag here
doc/source/contributing.rst
Outdated
@@ -775,6 +775,46 @@ Tests that we have ``parametrized`` are now accessible via the test name, for ex | |||
test_cool_feature.py::test_dtypes[int8] PASSED | |||
test_cool_feature.py::test_series[int8] PASSED | |||
|
|||
Using ``hypothesis`` | |||
~~~~~~~~~~~~~~~~~~~~ | |||
With the usage of 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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use double-backticks around pytest
doc/source/contributing.rst
Outdated
|
||
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. | ||
|
||
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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
link to the package
|
||
class TestCartesianProduct(object): | ||
|
||
def test_simple(self): | ||
x, y = list('ABC'), [1, 22] | ||
@hp.settings(max_examples=20) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so this is a lot of boilerplate here, this has to be simpler, we have thousands of tests (sure most cannot use this), but I think you would need to have this reduced to basically a 1-liner to have people use it. can you put some functions in _hypthesis to make this much more readable.
this comment for each of the additions here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jreback I have not seen a case where a test case is passing and then sometimes failing. I have raised pull request quite a few times for this issue and haven't seen non deterministic behavior till now.
Yes, test cases run just like parametrized test cases.
I am not very sure about the seed thing, but a failing test case is always reproducible locally, as hypothesis maintains some kind of cache for failed examples.
Also there is always a seed provided in case of failed example, which can be used to reproduce the same test example again. For more info, check below link:
http://hypothesis.readthedocs.io/en/latest/reproducing.html
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"""so this is a lot of boilerplate here, this has to be simpler, we have thousands of tests (sure most cannot use this), but I think you would need to have this reduced to basically a 1-liner to have people use it. can you put some functions in _hypthesis to make this much more readable."""
If you are talking about boilerplate, like below decorators, I don't think, it can be further reduced as just like, we are bound to have different parametrize for different test cases in pytest, the same issue is with below code. For some function it will be less, for other it can be more.
may be when code evolves, more common code comes out to be refactored.
@hp.settings(max_examples=20)
@hp.given(hp.st.lists(hp.st.text(string.ascii_letters, min_size=1, max_size=1),
min_size=1, max_size=3),
hp.get_seq((int,), False, 1, 2))
def test_simple(self, x, y):
Note: we made some changes to the CI that will cause some merge conflicts,
though hopefully the new setup is simpler.
You can add hypothesis next to `pytest` in the new
`ci/{appveyor,circle,travis}-*.yaml` files.
…On Sun, Apr 22, 2018 at 10:30 AM, Sushobhit ***@***.***> wrote:
***@***.**** commented on this pull request.
------------------------------
In ci/requirements-2.7_COMPAT.run
<#20590 (comment)>:
> @@ -11,4 +11,4 @@ psycopg2
pymysql=0.6.0
sqlalchemy=0.7.8
xlsxwriter=0.5.2
-jinja2=2.8
I will rebase my branch.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#20590 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/ABQHIgniHzQ_CiqdT7szj7EjyMLgwiopks5trKIOgaJpZM4TEidT>
.
|
@TomAugspurger I totally forgot about this PR, now I have rebased branch. You can have a look at it now, as all tests are now passing. |
closing in favor of #22280 |
Addition of "hypothesis usage" in test cases of
tests/reshape/test_util.py as kind of POC.
git diff upstream/master -u -- "*.py" | flake8 --diff