Skip to content

[MRG] Update pytest #186

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

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ python:
- "2.7"
- "3.4"
before_install:
- pip install --upgrade pip
- pip install --upgrade pip pytest
- pip install wheel
- pip install codecov
- if [[ $TRAVIS_PYTHON_VERSION == "3.4" ]];
then pip install pytest-cov pytest==3.6;
then pip install pytest-cov;
fi
- pip install numpy scipy scikit-learn
script:
Expand Down
61 changes: 26 additions & 35 deletions test/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
# and to_transform is some additional data that we would want to transform


@pytest.fixture
def build_classification(with_preprocessor=False):
"""Basic array for testing when using a preprocessor"""
X, y = shuffle(*make_blobs(random_state=SEED),
Expand All @@ -42,7 +41,6 @@ def build_classification(with_preprocessor=False):
return Dataset(X[indices], y[indices], None, X[indices])


@pytest.fixture
def build_regression(with_preprocessor=False):
"""Basic array for testing when using a preprocessor"""
X, y = shuffle(*make_regression(n_samples=100, n_features=5,
Expand Down Expand Up @@ -162,15 +160,13 @@ def test_check_input_invalid_type_of_inputs(type_of_inputs):
# ---------------- test check_input with 'tuples' type_of_input' ------------


@pytest.fixture
def tuples_prep():
"""Basic array for testing when using a preprocessor"""
tuples = np.array([[1, 2],
[2, 3]])
return tuples


@pytest.fixture
def tuples_no_prep():
"""Basic array for testing when using no preprocessor"""
tuples = np.array([[[1., 2.3], [2.3, 5.3]],
Expand Down Expand Up @@ -252,15 +248,15 @@ def test_check_tuples_invalid_shape(estimator, context, tuples, found,

@pytest.mark.parametrize('estimator, context',
[(NCA(), " by NCA"), ('NCA', " by NCA"), (None, "")])
def test_check_tuples_invalid_n_features(estimator, context, tuples_no_prep):
def test_check_tuples_invalid_n_features(estimator, context):
"""Checks that the right warning is printed if not enough features
Here we only test if no preprocessor (otherwise we don't ensure this)
"""
msg = ("Found array with 2 feature(s) (shape={}) while"
" a minimum of 3 is required{}.".format(tuples_no_prep.shape,
" a minimum of 3 is required{}.".format(tuples_no_prep().shape,
context))
with pytest.raises(ValueError) as raised_error:
check_input(tuples_no_prep, type_of_inputs='tuples',
check_input(tuples_no_prep(), type_of_inputs='tuples',
preprocessor=None, ensure_min_features=3,
estimator=estimator)
assert str(raised_error.value) == msg
Expand Down Expand Up @@ -317,8 +313,7 @@ def preprocessor(indices): #
assert str(raised_warning[0].message) == msg


def test_check_tuples_invalid_dtype_not_convertible_with_preprocessor(
tuples_prep):
def test_check_tuples_invalid_dtype_not_convertible_with_preprocessor():
"""Checks that a value error is thrown if attempting to convert an
input not convertible to float, when using a preprocessor
"""
Expand All @@ -328,31 +323,30 @@ def preprocessor(indices):
return np.full((indices.shape[0], 3), 'a')

with pytest.raises(ValueError):
check_input(tuples_prep, type_of_inputs='tuples',
check_input(tuples_prep(), type_of_inputs='tuples',
preprocessor=preprocessor, dtype=np.float64)


def test_check_tuples_invalid_dtype_not_convertible_without_preprocessor(
tuples_no_prep):
def test_check_tuples_invalid_dtype_not_convertible_without_preprocessor():
"""Checks that a value error is thrown if attempting to convert an
input not convertible to float, when using no preprocessor
"""
tuples = np.full_like(tuples_no_prep, 'a', dtype=object)
tuples = np.full_like(tuples_no_prep(), 'a', dtype=object)
with pytest.raises(ValueError):
check_input(tuples, type_of_inputs='tuples',
preprocessor=None, dtype=np.float64)


@pytest.mark.parametrize('tuple_size', [2, None])
def test_check_tuples_valid_tuple_size(tuple_size, tuples_prep, tuples_no_prep):
def test_check_tuples_valid_tuple_size(tuple_size):
"""For inputs that have the right matrix dimension (2D or 3D for instance),
checks that checking the number of tuples (pairs, quadruplets, etc) raises
no warning if there is the right number of points in a tuple.
"""
with pytest.warns(None) as record:
check_input(tuples_prep, type_of_inputs='tuples',
check_input(tuples_prep(), type_of_inputs='tuples',
preprocessor=mock_preprocessor, tuple_size=tuple_size)
check_input(tuples_no_prep, type_of_inputs='tuples', preprocessor=None,
check_input(tuples_no_prep(), type_of_inputs='tuples', preprocessor=None,
tuple_size=tuple_size)
assert len(record) == 0

Expand Down Expand Up @@ -400,7 +394,7 @@ def test_check_tuples_valid_without_preprocessor(tuples):
assert len(record) == 0


def test_check_tuples_behaviour_auto_dtype(tuples_no_prep):
def test_check_tuples_behaviour_auto_dtype():
"""Checks that check_tuples allows by default every type if using a
preprocessor, and numeric types if using no preprocessor"""
tuples_prep = [['img1.png', 'img2.png'], ['img3.png', 'img5.png']]
Expand All @@ -410,15 +404,15 @@ def test_check_tuples_behaviour_auto_dtype(tuples_no_prep):
assert len(record) == 0

with pytest.warns(None) as record:
check_input(tuples_no_prep, type_of_inputs='tuples') # numeric type
check_input(tuples_no_prep(), type_of_inputs='tuples') # numeric type
assert len(record) == 0

# not numeric type
tuples_no_prep = np.array([[['img1.png'], ['img2.png']],
[['img3.png'], ['img5.png']]])
tuples_no_prep = tuples_no_prep.astype(object)
tuples_no_prep_bis = np.array([[['img1.png'], ['img2.png']],
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I let tuples_no_prep here, I have an error saying tuples_no_prep is referenced before assignment... I didn't understand why ? Since tuples_no_prep is a function defined above in the module ? But changing into tuples_no_prep_bis it works
(I tried with a simple example and I still don't understand: for instance this script returns UnboundLocalError: local variable 'a' referenced before assignment when executed:

def a():
    return 5

def test():
    c = a()
    a = 3

if __name__ == '__main__':
    test()

Does python automatically detects that the variable "a" is assigned in "test" and that it must be a variable local to "test" ?)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, Python treats local variables specially at the bytecode level. Here's the result of dis.dis(test) using your example code:

  5           0 LOAD_FAST                0 (a)
              3 CALL_FUNCTION            0
              6 STORE_FAST               1 (c)

  6           9 LOAD_CONST               1 (3)
             12 STORE_FAST               0 (a)
             15 LOAD_CONST               0 (None)
             18 RETURN_VALUE

The LOAD_FAST opcode is specifically looking for a local variable named a, which is why you get the UnboundLocalError.

Changing the assignment of a to a different name results in a LOAD_GLOBAL opcode, which works as expected:

In [5]: def test2():
   ...:     c = a()
   ...:     b = 3
   ...:

In [6]: test2()

In [7]: dis.dis(test2)
  2           0 LOAD_GLOBAL              0 (a)
              3 CALL_FUNCTION            0
              6 STORE_FAST               0 (c)

  3           9 LOAD_CONST               1 (3)
             12 STORE_FAST               1 (b)
             15 LOAD_CONST               0 (None)
             18 RETURN_VALUE

You could also mark a global in the function, but this will result in overwriting the module-level function:

In [8]: def test3():
   ...:     global a
   ...:     c = a()
   ...:     a = 3
   ...:

In [9]: test3()

In [10]: test3()
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-10-5dea5f7ba8cb> in <module>()
----> 1 test3()

<ipython-input-8-32a55428a886> in test3()
      1 def test3():
      2     global a
----> 3     c = a()
      4     a = 3
      5

TypeError: 'int' object is not callable

In [11]: dis.dis(test3)
  3           0 LOAD_GLOBAL              0 (a)
              3 CALL_FUNCTION            0
              6 STORE_FAST               0 (c)

  4           9 LOAD_CONST               1 (3)
             12 STORE_GLOBAL             0 (a)
             15 LOAD_CONST               0 (None)
             18 RETURN_VALUE

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very interesting, thanks ! :)

[['img3.png'], ['img5.png']]])
tuples_no_prep_bis = tuples_no_prep_bis.astype(object)
with pytest.raises(ValueError):
check_input(tuples_no_prep, type_of_inputs='tuples')
check_input(tuples_no_prep_bis, type_of_inputs='tuples')


def test_check_tuples_invalid_complex_data():
Expand All @@ -436,14 +430,12 @@ def test_check_tuples_invalid_complex_data():
# ------------- test check_input with 'classic' type_of_inputs ----------------


@pytest.fixture
def points_prep():
"""Basic array for testing when using a preprocessor"""
points = np.array([1, 2])
return points


@pytest.fixture
def points_no_prep():
"""Basic array for testing when using no preprocessor"""
points = np.array([[1., 2.3],
Expand Down Expand Up @@ -484,17 +476,16 @@ def test_check_classic_invalid_shape(estimator, context, points, found,

@pytest.mark.parametrize('estimator, context',
[(NCA(), " by NCA"), ('NCA', " by NCA"), (None, "")])
def test_check_classic_invalid_n_features(estimator, context,
points_no_prep):
def test_check_classic_invalid_n_features(estimator, context):
"""Checks that the right warning is printed if not enough features
Here we only test if no preprocessor (otherwise we don't ensure this)
"""
msg = ("Found array with 2 feature(s) (shape={}) while"
" a minimum of 3 is required{}.".format(points_no_prep.shape,
" a minimum of 3 is required{}.".format(points_no_prep().shape,
context))
with pytest.raises(ValueError) as raised_error:
check_input(points_no_prep, type_of_inputs='classic', preprocessor=None,
ensure_min_features=3,
check_input(points_no_prep(), type_of_inputs='classic',
preprocessor=None, ensure_min_features=3,
estimator=estimator)
assert str(raised_error.value) == msg

Expand Down Expand Up @@ -610,7 +601,7 @@ def test_check_classic_by_default():
check_input([[2, 3], [3, 2]], type_of_inputs='classic')).all()


def test_check_classic_behaviour_auto_dtype(points_no_prep):
def test_check_classic_behaviour_auto_dtype():
"""Checks that check_input (for points) allows by default every type if
using a preprocessor, and numeric types if using no preprocessor"""
points_prep = ['img1.png', 'img2.png', 'img3.png', 'img5.png']
Expand All @@ -620,15 +611,15 @@ def test_check_classic_behaviour_auto_dtype(points_no_prep):
assert len(record) == 0

with pytest.warns(None) as record:
check_input(points_no_prep, type_of_inputs='classic') # numeric type
check_input(points_no_prep(), type_of_inputs='classic') # numeric type
assert len(record) == 0

# not numeric type
points_no_prep = np.array(['img1.png', 'img2.png', 'img3.png',
'img5.png'])
points_no_prep = points_no_prep.astype(object)
points_no_prep_bis = np.array(['img1.png', 'img2.png', 'img3.png',
'img5.png'])
points_no_prep_bis = points_no_prep_bis.astype(object)
with pytest.raises(ValueError):
check_input(points_no_prep, type_of_inputs='classic')
check_input(points_no_prep_bis, type_of_inputs='classic')


def test_check_classic_invalid_complex_data():
Expand Down