1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
|
# Testing Guidelines for xarray
## Handling Optional Dependencies
xarray has many optional dependencies that may not be available in all testing environments. Always use the standard decorators and patterns when writing tests that require specific dependencies.
### Standard Decorators
**ALWAYS use decorators** like `@requires_dask`, `@requires_cftime`, etc. instead of conditional `if` statements.
All available decorators are defined in `xarray/tests/__init__.py` (look for `requires_*` decorators).
### DO NOT use conditional imports or skipif
❌ **WRONG - Do not do this:**
```python
def test_mean_with_cftime():
if has_dask: # WRONG!
ds = ds.chunk({})
result = ds.mean()
```
❌ **ALSO WRONG - Avoid pytest.mark.skipif in parametrize:**
```python
@pytest.mark.parametrize(
"chunk",
[
pytest.param(
True, marks=pytest.mark.skipif(not has_dask, reason="requires dask")
),
False,
],
)
def test_something(chunk): ...
```
✅ **CORRECT - Do this instead:**
```python
def test_mean_with_cftime():
# Test without dask
result = ds.mean()
@requires_dask
def test_mean_with_cftime_dask():
# Separate test for dask functionality
ds = ds.chunk({})
result = ds.mean()
```
✅ **OR for parametrized tests, split them:**
```python
def test_something_without_dask():
# Test the False case
...
@requires_dask
def test_something_with_dask():
# Test the True case with dask
...
```
### Multiple dependencies
When a test requires multiple optional dependencies:
```python
@requires_dask
@requires_scipy
def test_interpolation_with_dask(): ...
```
### Importing optional dependencies in tests
For imports within test functions, use `pytest.importorskip`:
```python
def test_cftime_functionality():
cftime = pytest.importorskip("cftime")
# Now use cftime
```
### Common patterns
1. **Split tests by dependency** - Don't mix optional dependency code with base functionality:
```python
def test_base_functionality():
# Core test without optional deps
result = ds.mean()
assert result is not None
@requires_dask
def test_dask_functionality():
# Dask-specific test
ds_chunked = ds.chunk({})
result = ds_chunked.mean()
assert result is not None
```
2. **Use fixtures for dependency-specific setup**:
```python
@pytest.fixture
def dask_array():
pytest.importorskip("dask.array")
import dask.array as da
return da.from_array([1, 2, 3], chunks=2)
```
3. **Check available implementations**:
```python
from xarray.core.duck_array_ops import available_implementations
@pytest.mark.parametrize("implementation", available_implementations())
def test_with_available_backends(implementation): ...
```
### Key Points
- CI environments intentionally exclude certain dependencies (e.g., `all-but-dask`, `bare-minimum`)
- A test failing in "all-but-dask" because it uses dask is a test bug, not a CI issue
- Look at similar existing tests for patterns to follow
|