Skip to content

Commit 6456df4

Browse files
Zac-HDfmaussion
authored andcommitted
Starter property-based test suite (#1972)
1 parent a1fa397 commit 6456df4

File tree

5 files changed

+102
-0
lines changed

5 files changed

+102
-0
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
*.py[cod]
22
__pycache__
33

4+
# example caches from Hypothesis
5+
.hypothesis/
6+
47
# temp files from docs build
58
doc/auto_gallery
69
doc/example.nc

.travis.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ matrix:
4747
env: CONDA_ENV=py36-zarr-dev
4848
- python: 3.5
4949
env: CONDA_ENV=docs
50+
- python: 3.6
51+
env: CONDA_ENV=py36-hypothesis
5052
allow_failures:
5153
- python: 3.6
5254
env:
@@ -104,6 +106,8 @@ script:
104106
- if [[ "$CONDA_ENV" == "docs" ]]; then
105107
conda install -c conda-forge sphinx sphinx_rtd_theme sphinx-gallery numpydoc;
106108
sphinx-build -n -j auto -b html -d _build/doctrees doc _build/html;
109+
elif [[ "$CONDA_ENV" == "py36-hypothesis" ]]; then
110+
pytest properties ;
107111
else
108112
py.test xarray --cov=xarray --cov-config ci/.coveragerc --cov-report term-missing --verbose $EXTRA_FLAGS;
109113
fi
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
name: test_env
2+
channels:
3+
- conda-forge
4+
dependencies:
5+
- python=3.6
6+
- dask
7+
- distributed
8+
- h5py
9+
- h5netcdf
10+
- matplotlib
11+
- netcdf4
12+
- pytest
13+
- flake8
14+
- numpy
15+
- pandas
16+
- scipy
17+
- seaborn
18+
- toolz
19+
- rasterio
20+
- bottleneck
21+
- zarr
22+
- pip:
23+
- coveralls
24+
- pytest-cov
25+
- pydap
26+
- lxml
27+
- hypothesis

properties/README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Property-based tests using Hypothesis
2+
3+
This directory contains property-based tests using a library
4+
called [Hypothesis](https://github.com/HypothesisWorks/hypothesis-python).
5+
6+
The property tests for Xarray are a work in progress - more are always welcome.
7+
They are stored in a separate directory because they tend to run more examples
8+
and thus take longer, and so that local development can run a test suite
9+
without needing to `pip install hypothesis`.
10+
11+
## Hang on, "property-based" tests?
12+
13+
Instead of making assertions about operations on a particular piece of
14+
data, you use Hypothesis to describe a *kind* of data, then make assertions
15+
that should hold for *any* example of this kind.
16+
17+
For example: "given a 2d ndarray of dtype uint8 `arr`,
18+
`xr.DataArray(arr).plot.imshow()` never raises an exception".
19+
20+
Hypothesis will then try many random examples, and report a minimised
21+
failing input for each error it finds.
22+
[See the docs for more info.](https://hypothesis.readthedocs.io/en/master/)

properties/test_encode_decode.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
"""
2+
Property-based tests for encoding/decoding methods.
3+
4+
These ones pass, just as you'd hope!
5+
6+
"""
7+
from __future__ import absolute_import, division, print_function
8+
9+
from hypothesis import given, settings
10+
import hypothesis.strategies as st
11+
import hypothesis.extra.numpy as npst
12+
13+
import xarray as xr
14+
15+
# Run for a while - arrays are a bigger search space than usual
16+
settings.deadline = None
17+
18+
19+
an_array = npst.arrays(
20+
dtype=st.one_of(
21+
npst.unsigned_integer_dtypes(),
22+
npst.integer_dtypes(),
23+
npst.floating_dtypes(),
24+
),
25+
shape=npst.array_shapes(max_side=3), # max_side specified for performance
26+
)
27+
28+
29+
@given(st.data(), an_array)
30+
def test_CFMask_coder_roundtrip(data, arr):
31+
names = data.draw(st.lists(st.text(), min_size=arr.ndim,
32+
max_size=arr.ndim, unique=True).map(tuple))
33+
original = xr.Variable(names, arr)
34+
coder = xr.coding.variables.CFMaskCoder()
35+
roundtripped = coder.decode(coder.encode(original))
36+
xr.testing.assert_identical(original, roundtripped)
37+
38+
39+
@given(st.data(), an_array)
40+
def test_CFScaleOffset_coder_roundtrip(data, arr):
41+
names = data.draw(st.lists(st.text(), min_size=arr.ndim,
42+
max_size=arr.ndim, unique=True).map(tuple))
43+
original = xr.Variable(names, arr)
44+
coder = xr.coding.variables.CFScaleOffsetCoder()
45+
roundtripped = coder.decode(coder.encode(original))
46+
xr.testing.assert_identical(original, roundtripped)

0 commit comments

Comments
 (0)