Skip to content

Commit

Permalink
TST: use pytest, instead of nose
Browse files Browse the repository at this point in the history
for compatibility with Python 3.10.

- REL: add `pytest >= 4.6.11` to the argument
  `tests_require` of the function `setuptools.setup`
  in the script `setup.py`.
  This lower bound has been selected to
  ensure compatibility with both Python 3 and
  Python 2.7. See below for details.

- REL: add `pytest >= 4.6.11` to file `requirements.txt`

- REL: remove `nose` from argument `tests_require` of
  the function `setuptools.setup` in script `setup.py`

- REL: remove `nose` from file `requirements.txt`

- CI: update file `.travis.yml`

- CI: remove the collection of
  coverage measurements on Travis CI,
  because these measurements do not collect
  Cython coverage with the build configuration
  currently used for CI testing.

- DEV: remove `coveralls` from the
  file `requirements.txt`, because the
  package `coveralls` was used only on Travis CI.

- TST: add a configuration file `tests/pytest.ini`
  and include it in `MANIFEST.in`

- GIT: ignore `.pytest_cache/` in `.gitignore`


## Motivation

The change from [`nose == 1.3.7`](
    https://pypi.org/project/nose/1.3.7/#history)
to [`pytest`](
    https://pypi.org/project/pytest)
is motivated by compatibility with Python 3.10.


## `nose` is incompatible with Python 3.10

The package `nose`, which was used to run
the tests of `dd`, is not compatible with Python 3.10
(for details, read the last section below).

Also, `nose` uses the `imp` module from
CPython's standard library.
The module `imp` [is deprecated](
    https://docs.python.org/3.10/library/imp.html),
so it may be removed in some future Python version.


## Summary of transition to `pytest`

In summary, using `pytest` with the
existing tests requires adding
a [configuration file `tests/pytest.ini`](
    https://docs.pytest.org/en/latest/reference/customize.html#configuration-file-formats)
to tell `pytest` which functions, classes, and
methods to collect tests from
(called "discovery" of tests).

The [parameter `--continue-on-collection-errors`](
    https://docs.pytest.org/en/latest/reference/reference.html#command-line-flags)
tells `pytest` to not stop in case any
test module fails to import,
and to continue with running the tests.
The ability to run the tests when some
`dd` C extension modules are not installed is necessary.

After transitioning the tests to `pytest`,
the tests have been confirmed
to run successfully:
- on Python 2.7 with `pytest == 4.6.11`, and
- on Python 3.9 with `pytest == 6.2.4`.


## Considering using `unittest`

For writing `dd` tests, the module [`unittest`](
    https://docs.python.org/3/library/unittest.html)
(part of CPython's standard library) suffices.

For *discovering* the tests, `unittest` seems to
require that tests be methods of subclasses of
the class `unittest.TestCase`.
This is not the case in the tests of `dd` tests.
Using `pytest` allows changing the test runner from `nosetests`
with minimal changes to the tests themselves.

Test discovery using `unittest` could possibly be
implemented by adding a file `tests/__init__.py`, and
defining in that file a function `load_tests`,
following the [documentation of `unittest`](
    https://docs.python.org/3/library/unittest.html#unittest.TestLoader.discover).
In any case, it is simpler to use `pytest`,
which requires only a configuration file.

If `unittest` encounters an `ImportError` during
collection of the tests (i.e., when `unittest` tries to
import test modules), then it stops.
There does not appear to be any way to tell `unittest`
to continue and run the rest of the test modules
(those that *could* be imported).


## Usage of `nose`

The dependence on `nose` was minimal.
Only one function was used from `nose`:
the function `nose.tools.assert_raises`.
The function `assert_raises` is dynamically defined
[in the module `nose.tools.trivial`](
    https://github.com/nose-devs/nose/blob/release_1.3.7/nose/tools/trivial.py#L32-L54)
by instantiating the class `unittest.TestCase`,
and setting `assert_raises` to equal the
[bound method `assertRaises`](
    https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertRaises)
of the instance of `TestCase`.

So the function `assert_raises` from `nose` is
a PEP8-compliant binding for the method
`unittest.TestCase.assertRaises`.

Reading the code of `unittest`:
- <https://github.com/python/cpython/blob/6fdc4d37f3fdbc1bd51f841be6e5e4708a3b8798/Lib/unittest/case.py#L156-L243>
- <https://github.com/python/cpython/blob/6fdc4d37f3fdbc1bd51f841be6e5e4708a3b8798/Lib/unittest/case.py#L704-L735>

it follows that the usage:

```python
with nose.tools.assert_raises(AssertionError):
    foo(1, 2)
```

is equivalent to the following code
(`AssertionError` here is used as an example):

```python
with unittest.TestCase().assertRaises(AssertionError):
    foo(1, 2)
```


## Replacing usage of `nose` with `pytest` in test code

The [context manager `pytest.raises`](
    https://docs.pytest.org/en/latest/reference/reference.html#pytest-raises)
is a [drop-in replacement](
    https://en.wikipedia.org/wiki/Drop-in_replacement)
for the function `nose.tools.assert_raises`:

```python
with pytest.raises(AssertionError):
    foo(1, 2)
```

Also, the tests can still be run with `nosetests`
on Python versions where `nose` is still available.


## Replacing the test runner `nosetests` with `pytest`

- `pytest` correctly recognized the
  test files by default

- `pytest` does not recognize by default methods of
  classes that do not start with "Test" as test methods.

  The configuration file is necessary for
  changing this behavior of `pytest`
  (in particular, the command-line parameter `-k`
   did not seem to work for classes).

  Relevant documentation:
  - <https://docs.pytest.org/en/latest/explanation/goodpractices.html#conventions-for-python-test-discovery>
  - <https://docs.pytest.org/en/latest/example/pythoncollection.html#changing-naming-conventions>
  - <https://docs.pytest.org/en/latest/how-to/nose.html>
  - <https://docs.pytest.org/en/latest/reference/reference.html#confval-python_classes>
  - <https://docs.pytest.org/en/latest/reference/reference.html#confval-python_functions>
  - <https://docs.pytest.org/en/latest/reference/reference.html#confval-python_files>

- The call `pytest tests/foo_test.py` imports
  the package `dd` from `site-packages`
  (assuming that the module `foo_test.py` contains
   the statement `import dd`).
  So the default behavior of `pytest` is
  as intended.

  In contrast, `nosetests tests/foo_test.py` imports
  the package `dd` from the local directory `dd/`,
  even though `dd` *is* installed under `site-packages`.

  In any case, `pytest` is called from within
  the directory `tests/`, as was done for `nosetests`.

  Both:
  - `python -m pytest tests/foo_test.py` and
  - `PYTHONPATH=. pytest tests/foo_test.py`

  result in importing `dd` from the local directory `dd/`.

  Relevant documentation:
  - <https://docs.pytest.org/en/latest/explanation/pythonpath.html#invoking-pytest-versus-python-m-pytest>
  - <https://docs.pytest.org/en/latest/how-to/usage.html#invoke-python>

As remarked above, the parameter
[`--continue-on-collection-errors`](
    https://docs.pytest.org/en/latest/reference/reference.html#command-line-flags)
of `pytest` needs to be used for running the tests
when some of the C extension modules are not installed.
For example:

```
cd tests/
pytest -v --continue-on-collection-errors .
```

To activate [Python's development mode](
    https://docs.python.org/3/library/devmode.html):

```
cd tests/
python -X dev -m pytest -vvv --continue-on-collection-errors .
```


## Further remarks

Observations about `pytest`:
- detects assertions that failed, and marks
  their source lines
- avoids the deprecated `imp` module (of the
  standard library) that `nose` uses
  (and thus the associated `DeprecationWarning`)
   <https://docs.python.org/3.10/library/imp.html>
- running the tests of `dd` with `pytest` revealed
  several `DeprecationWarning`s that were previously
  not shown by `nose` (these warnings were about
   invalid escape sequences due to
   backslashes appearing in non-raw strings).


[`pytest == 6.2.4`](https://pypi.org/project/pytest/6.2.4) is not
compatible with Python 2.7.
[`pytest == 4.6.11`](https://pypi.org/project/pytest/4.6.11/)
is the latest version of `pytest` that is compatible with Python 2.7
(released on June 5, 2020).

`pytest` specifies `python_requires` [PEP 345](
    https://www.python.org/dev/peps/pep-0345/#requires-python),
[PEP 503](
    https://www.python.org/dev/peps/pep-0503/):
- <https://github.com/pytest-dev/pytest/blob/4.6.11/setup.cfg#L48>
- <https://github.com/pytest-dev/pytest/blob/5.0.0/setup.cfg#L43>

So including `pytest>=4.6.11` in the file
`requirements.txt` suffices to install,
on each Python version, the latest version of
`pytest` that is compatible with that Python version.

This simplifies testing on CI, and packaging.
In other words, conditional installations in
the file `.travis.yml` are not needed for `pytest`,
neither conditional definition of `tests_require`
in the script `setup.py`.

This approach leaves implicit the upper bound on
`pytest` in `tests_require`.
This upper bound is specified explicitly by
`pytest` itself, depending on the Python version of
the interpreter.

It appears that `pip == 9.0.0` and
`setuptools == 24.2.0` are required,
to correctly implement `python_requires`:
- <https://pip.pypa.io/en/stable/news/#v9-0-0>
- <https://setuptools.readthedocs.io/en/latest/history.html#v24-2-1>


## How replacing usage of `nose` with `unittest` would have looked like

A way to replace `nose` could have been to
add a module `tests/utils.py` containing:

```python
"""Common functionality for tests."""
import unittest

_test_case = unittest.TestCase()
assert_raises = _test_case.assertRaises
```

which is close to what `nose` does.
The function `assert_raises` could then
be imported from the module `utils` in
test modules, and used.

Using `pytest` avoids the need for
this workaround.


## Details about the incompatibility of `nose` with Python 3.10

[`nose == 1.3.7`](
    https://pypi.org/project/nose/1.3.7/#history)
imports in Python 3.10. Running `nosetests` fails,
due to imports from the module `collections` of
classes that have moved to the module `collections.abc`.

Relevant information about CPython changes
in `collections` (removal of ABCs):
- <python/cpython#23754>
- <https://bugs.python.org/issue37324>
- <https://docs.python.org/3.10/library/collections.abc.html#collections-abstract-base-classes>
- Deprecation since Python 3.3,
  present until Python 3.9:
      <https://docs.python.org/3.9/library/collections.html#module-collections>
- Removed in Python 3.10:
      <https://docs.python.org/3.10/whatsnew/3.10.html#removed>


## About skipping tests

The decorator `unittest.skipIf` is recognized
by `pytest`, and skipped tests are correctly
recorded and reported. In any case, note also
the `pytest` test-skipping facilities:
- <https://docs.pytest.org/en/latest/how-to/skipping.html>
- <https://docs.pytest.org/en/latest/how-to/unittest.html>
- <https://docs.pytest.org/en/latest/example/simple.html#control-skipping-of-tests-according-to-command-line-option>


## About passing `-X dev` to `python` in the `Makefile`

The argument `dev` is available for the `python` option
[`-X` only on Python 3.7 and higher](
    https://docs.python.org/3/library/devmode.html#devmode).

So the `Makefile` rules where `-X dev` appears are
not compatible with ealier Python versions supported by `dd`.
This is not an issue: the development environment is
intended to be Python 3.9 or higher, so there is no issue
with using `-X dev`.


## Avoiding interaction between tests via class attributes

Avoid class attributes in test classes.
Use [data attributes][1] instead.
Initialize the data attributes in setup methods
of the test classes, as is common practice.

This approach avoids interaction (via class
attributes) between test scripts that
import the same modules of common tests.

With `nose`, this kind of interaction
apparently did not occur, as observed by
test failures that were expected to happen.

However, `pytest` apparently runs tests in
a way that changes to imported modules
(e.g., class attributes)
persist between different test scripts.

This `pytest` behavior was observed by
the disappearance of test failures when
running with `pytest` (the test failures
were observable with `pytest` only when
telling `pytest` to run individual test
scripts, instead of collecting tests from
all test scripts.

The cause of the issue with `pytest` was
the modification of class attributes
(not [data attributes][1]) from the
importing module, of classes in the
imported module.

The modifications were done by setting the
class attribute `DD` that defines the BDD or
ZDD manager class. Both the scripts
`cudd_test.py` and `autoref_test.py` made
modifications. The end result was
`autoref` tests being run using the
class `dd.cudd.BDD`.

Using data attributes, instead of class
attributes, and subclassing, avoids this
kind of erroneous testing.
This approach is explicit [PEP 20][4].

[1]: https://docs.python.org/3/tutorial/classes.html#instance-objects
[2]: https://github.com/pytest-dev/pytest-xdist
[3]: https://github.com/pytest-dev/pytest-forked
[4]: https://www.python.org/dev/peps/pep-0020/
  • Loading branch information
johnyf committed Jan 11, 2022
1 parent f93ce11 commit 393e28c
Show file tree
Hide file tree
Showing 17 changed files with 124 additions and 93 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
.coverage
.cache
.pytest_cache/
todo.txt
dd/_version.py
cython_debug/*
Expand Down
5 changes: 1 addition & 4 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,4 @@ install:

script:
- cd tests/
- nosetests --with-coverage --cover-package=dd

after_success:
- coveralls
- pytest -v --continue-on-collection-errors .
2 changes: 2 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

## 0.5.7

- require `pytest >= 4.6.11`, instead of `nose`, for Python 3.10 compatibility

API:

- return memory size in bytes from methods `dd.cudd.BDD.statistics` and
Expand Down
1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ include AUTHORS
include requirements.txt
include download.py
include tests/README.md
include tests/pytest.ini
include tests/inspect_cython_signatures.py
include tests/common.py
include tests/common_bdd.py
Expand Down
23 changes: 14 additions & 9 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
# The script `rtests.py` below is an adaptation of:
# https://github.com/tulip-control/tulip-control/blob/master/run_tests.py

wheel_file := $(wildcard dist/*.whl)

.PHONY: cudd install test
Expand All @@ -12,15 +9,16 @@ build_cudd: clean cudd install test
build_sylvan: clean
-pip uninstall -y dd
python setup.py install --sylvan
rtests.py --rednose
pip install pytest
make test

sdist_test: clean
python setup.py sdist --cudd --buddy
cd dist; \
pip install dd*.tar.gz; \
tar -zxf dd*.tar.gz
pip install nose rednose
rtests.py --rednose
pip install pytest
make -C dist/dd*/ -f ../../Makefile test

sdist_test_cudd: clean
pip install cython ply
Expand All @@ -30,8 +28,8 @@ sdist_test_cudd: clean
tar -zxf dd*.tar.gz; \
cd dd*; \
python setup.py install --fetch --cudd
pip install nose rednose
rtests.py --rednose
pip install pytest
make -C dist/dd*/ -f ../../Makefile test

# use to create source distributions for PyPI
sdist: clean
Expand Down Expand Up @@ -69,7 +67,14 @@ develop:

test:
cd tests/; \
nosetests -v
python -X dev -m pytest -v --continue-on-collection-errors .
# `pytest -Werror` turns all warnings into errors
# https://docs.pytest.org/en/latest/how-to/capture-warnings.html
# including pytest warnings about unraisable exceptions:
# https://docs.pytest.org/en/latest/how-to/failures.html
# #warning-about-unraisable-exceptions-and-unhandled-thread-exceptions
# https://docs.pytest.org/en/latest/reference/reference.html
# #pytest.PytestUnraisableExceptionWarning

test_abc:
python -X dev tests/inspect_cython_signatures.py
Expand Down
8 changes: 3 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
[![Build Status][build_img]][travis]
[![Coverage Status][coverage]][coveralls]


About
Expand Down Expand Up @@ -324,14 +323,15 @@ The modules `dd.cudd` and `dd.cudd_zdd` in the wheel dynamically link to the:
Tests
=====

Require [`nose`](https://pypi.python.org/pypi/nose). Run with:
Use [`pytest`](https://pypi.org/project/pytest). Run with:

```shell
cd tests/
nosetests
pytest -v --continue-on-collection-errors .
```

Tests of Cython modules that were not installed will fail.
The code is covered well by tests.


License
Expand All @@ -341,5 +341,3 @@ License

[build_img]: https://travis-ci.com/tulip-control/dd.svg?branch=master
[travis]: https://travis-ci.com/tulip-control/dd
[coverage]: https://coveralls.io/repos/tulip-control/dd/badge.svg?branch=master
[coveralls]: https://coveralls.io/r/tulip-control/dd?branch=master
3 changes: 1 addition & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ cython==0.29.24


# dev
nose==1.3.7
pytest>=4.6.11
gitpython
grip
coveralls
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
'pydot >= 1.2.2',
'setuptools >= 19.6']
TESTS_REQUIRE = [
'nose >= 1.3.4']
'pytest >= 4.6.11']
CLASSIFIERS = [
'Development Status :: 2 - Pre-Alpha',
'Intended Audience :: Developers',
Expand Down
15 changes: 10 additions & 5 deletions tests/autoref_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,22 @@

from dd import autoref as _bdd
import dd.bdd
from nose.tools import assert_raises

from common import Tests
from common_bdd import Tests as BDDTests
import common
import common_bdd


logging.getLogger('astutils').setLevel('ERROR')


Tests.DD = _bdd.BDD
BDDTests.DD = _bdd.BDD
class Tests(common.Tests):
def setup_method(self):
self.DD = _bdd.BDD


class BDDTests(common_bdd.Tests):
def setup_method(self):
self.DD = _bdd.BDD


def test_str():
Expand Down
48 changes: 24 additions & 24 deletions tests/bdd_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from dd import bdd as _bdd
import networkx as nx
import networkx.algorithms.isomorphism as iso
from nose.tools import assert_raises
import pytest


class BDD(_BDD):
Expand Down Expand Up @@ -60,9 +60,9 @@ def test_add_var():
assert b.vars['y'] == 5, b.vars
assert j == 5, j
# attempt to add var at an existing level
with assert_raises(AssertionError):
with pytest.raises(AssertionError):
b.add_var('z', level=35)
with assert_raises(AssertionError):
with pytest.raises(AssertionError):
b.add_var('z', level=5)
#
# mixing automated and
Expand All @@ -75,14 +75,14 @@ def test_add_var():
assert 'y' in b.vars, b.vars
assert b.vars['x'] == 2, b.vars
assert b.vars['y'] == 1, b.vars
with assert_raises(AssertionError):
with pytest.raises(AssertionError):
b.add_var('z')
b.add_var('z', level=0)


def test_var():
b = BDD()
with assert_raises(AssertionError):
with pytest.raises(AssertionError):
b.var('x')
j = b.add_var('x')
u = b.var('x')
Expand All @@ -99,17 +99,17 @@ def test_assert_consistent():
g = x_or_y()
assert g.assert_consistent()
g._succ[2] = (5, 1, 2)
with assert_raises(AssertionError):
with pytest.raises(AssertionError):
g.assert_consistent()
g = x_or_y()
g.roots.add(2)
g._succ[4] = (0, 10, 1)
with assert_raises(AssertionError):
with pytest.raises(AssertionError):
g.assert_consistent()
g = x_or_y()
g.roots.add(2)
g._succ[1] = (2, None, 1)
with assert_raises(AssertionError):
with pytest.raises(AssertionError):
g.assert_consistent()
g = x_and_y()
assert g.assert_consistent()
Expand All @@ -120,7 +120,7 @@ def test_level_to_variable():
g = BDD(ordering)
assert g.var_at_level(0) == 'x'
assert g.var_at_level(1) == 'y'
with assert_raises(AssertionError):
with pytest.raises(AssertionError):
g.var_at_level(10)


Expand Down Expand Up @@ -209,7 +209,7 @@ def test_count():
assert r == 6, r
r = g.count(-4, 3)
assert r == 2, r
with assert_raises(Exception):
with pytest.raises(Exception):
g.count()
r = g.count(4)
assert r == 3, r
Expand Down Expand Up @@ -472,10 +472,10 @@ def test_find_or_add():
assert refv == refv_, (refv, refv_)
assert refw == refw_, (refw, refw_)
# only non-terminals can be added
with assert_raises(AssertionError):
with pytest.raises(AssertionError):
g.find_or_add(2, -1, 1)
# low and high must already exist
with assert_raises(AssertionError):
with pytest.raises(AssertionError):
g.find_or_add(0, 3, 4)
# canonicity of complemented edges
# v < 0, w > 0
Expand Down Expand Up @@ -527,7 +527,7 @@ def test_next_free_int():
# full
g._succ = {1, 2, 3}
g.max_nodes = 3
with assert_raises(Exception):
with pytest.raises(Exception):
g._next_free_int(start=1)


Expand Down Expand Up @@ -663,7 +663,7 @@ def test_cofactor():
ordering = {'x': 0, 'y': 1, 'z': 2}
g = BDD(ordering)
# u not in g
with assert_raises(AssertionError):
with pytest.raises(AssertionError):
g.let({'x': False, 'y': True, 'z': False}, 5)
# x /\ y
e = g.add_expr(r'x /\ y')
Expand Down Expand Up @@ -799,7 +799,7 @@ def test_request_reordering():
ctx._last_len = 1
ctx.length = 3 # >= 2 = 2 * _last_len
# large growth
with assert_raises(_bdd._NeedsReordering):
with pytest.raises(_bdd._NeedsReordering):
_bdd._request_reordering(ctx)
ctx._last_len = 2
ctx.length = 3 # < 4 = 2 * _last_len
Expand All @@ -817,20 +817,20 @@ def test_reordering_context():
ctx.assert_(False)
# nested context
ctx._reordering_context = True
with assert_raises(_bdd._NeedsReordering):
with pytest.raises(_bdd._NeedsReordering):
with _bdd._ReorderingContext(ctx):
ctx.assert_(True)
raise _bdd._NeedsReordering()
ctx.assert_(True)
# other exception
ctx._reordering_context = False
with assert_raises(AssertionError):
with pytest.raises(AssertionError):
with _bdd._ReorderingContext(ctx):
ctx.assert_(True)
raise AssertionError()
ctx.assert_(False)
ctx._reordering_context = True
with assert_raises(Exception):
with pytest.raises(Exception):
with _bdd._ReorderingContext(ctx):
raise Exception()
ctx.assert_(True)
Expand Down Expand Up @@ -944,7 +944,7 @@ def test_undeclare_vars():
bdd = BDD()
bdd.declare('x', 'y', 'z', 'w')
u = bdd.add_expr(r'y /\ w')
with assert_raises(AssertionError):
with pytest.raises(AssertionError):
bdd.undeclare_vars('z', 'y')


Expand Down Expand Up @@ -1100,7 +1100,7 @@ def test_rename():
assert r == r_, (r, r_)
# u not in bdd
dvars = {'x': 'xp'}
with assert_raises(AssertionError):
with pytest.raises(AssertionError):
g.let(dvars, 1000)
# y essential for u
dvars = {'x': 'y'}
Expand Down Expand Up @@ -1153,16 +1153,16 @@ def test_image_rename_map_checks():
assert r == 1, r
# overlapping keys and values
rename = {0: 1, 1: 2}
with assert_raises(AssertionError):
with pytest.raises(AssertionError):
_bdd.image(1, 1, rename, qvars, bdd)
with assert_raises(AssertionError):
with pytest.raises(AssertionError):
_bdd.preimage(1, 1, rename, qvars, bdd)
# may be in support after quantification ?
trans = bdd.add_expr('x => xp')
source = bdd.add_expr(r'x /\ y')
qvars = {0}
rename = {1: 0, 3: 2}
with assert_raises(AssertionError):
with pytest.raises(AssertionError):
_bdd.image(trans, source, rename, qvars, bdd)
# in support of `target` ?
qvars = set()
Expand Down Expand Up @@ -1237,7 +1237,7 @@ def test_assert_valid_ordering():
ordering = {'x': 0, 'y': 1}
_bdd._assert_valid_ordering(ordering)
incorrect_ordering = {'x': 0, 'y': 2}
with assert_raises(AssertionError):
with pytest.raises(AssertionError):
_bdd._assert_valid_ordering(incorrect_ordering)


Expand Down
Loading

0 comments on commit 393e28c

Please sign in to comment.