Repository: scikit-fuzzy/scikit-fuzzy (language: python)

Issue:
Replacement of nose with pytest 
It seems there's no path forward for `nose` beyond Python 3.9.  Presently we could get the test suite running again just by masking the 3.10 and 3.11 branches, but this is not a solution.

My preference would be moving to `pytest`, due to a larger userbase and harmony with the majority of the scientific Python community. The other contender would be `nose2`. If there are strong advocates for `nose2`, please feel free to make your case.  Otherwise, the default path is `pytest`.

Converting the test suite will be a work in progress, and this Issue will remain open until it is complete.


Discussion / hints:
See ongoing test failures despite #299 after update of test framework in #300 which are squarely the fault of Nose.

Fix patch:
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index a4088593..40b2fe39 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -11,8 +11,8 @@ jobs:
     strategy:
       fail-fast: false
       matrix:
-        os: [ubuntu-latest, windows-latest]
-        python: [3.8, 3.9] # Nose limited to 3.9, Matplotlib is not yet Py3.12 compatible
+        os: [ubuntu-latest]  # , windows-latest]  # Windows passes even when pytests fail
+        python: [3.8, 3.9, "3.10", 3.11]  # , 3.12]  # Matplotlib is not yet Py3.12 compatible
 
     steps:
       - uses: actions/checkout@v4
@@ -30,6 +30,7 @@ jobs:
 
       - name: Install dependencies
         run: |
+          pip install --upgrade pip setuptools wheel
           pip install -r DEPENDS.txt -r DEPENDS-tests.txt -r DEPENDS-docs.txt
 
       - name: Build package
@@ -41,7 +42,8 @@ jobs:
 
       - name: Run tests
         run: |
-          nosetests -v --exe --with-coverage --cover-package=skfuzzy skfuzzy
+          pytest
+          # pytest --doctest-modules  # TODO: Fix four failing doctests
           flake8 --exclude=test_* skfuzzy docs/examples
       - name: Build docs
         if: matrix.os == 'ubuntu-latest'
diff --git a/Makefile b/Makefile
index 4405a96b..3d08293a 100644
--- a/Makefile
+++ b/Makefile
@@ -20,7 +20,7 @@ clean-cov:
 clean: clean-build clean-pyc clean-version clean-cov
 
 test:
-	nosetests -s -v skfuzzy
+	pytest
 
 coverage: clean-cov
-	nosetests skfuzzy --with-coverage --cover-package=skfuzzy
+	pytest --cov=skfuzzy --cov-report html
diff --git a/skfuzzy/__init__.py b/skfuzzy/__init__.py
index 42644738..1507413f 100644
--- a/skfuzzy/__init__.py
+++ b/skfuzzy/__init__.py
@@ -70,51 +70,6 @@
 pkg_dir = osp.abspath(osp.dirname(__file__))
 data_dir = osp.join(pkg_dir, 'data')
 
-try:
-    importlib_utils.find_spec('nose')
-except ImportError:
-    def _test(doctest=False, verbose=False):
-        """This would run all unit tests, but nose couldn't be
-        imported so the test suite can not run.
-        """
-        raise ImportError("Could not load nose. Unit tests not available.")
-else:
-    def _test(doctest=False, verbose=False):
-        """Run all unit tests."""
-        import nose
-        import warnings
-        args = ['', pkg_dir, '--exe', '--ignore-files=^_test']
-        if verbose:
-            args.extend(['-v', '-s'])
-        if doctest:
-            args.extend([r"--with-doctest",
-                         r"--ignore-files=^\.",
-                         r"--ignore-files=^setup\.py$$",
-                         r"--ignore-files=test"])
-            # Make sure warnings do not break the doc tests
-            with warnings.catch_warnings():
-                warnings.simplefilter("ignore")
-                success = nose.run('skfuzzy', argv=args)
-        else:
-            success = nose.run('skfuzzy', argv=args)
-        # Return sys.exit code
-        if success:
-            return 0
-        else:
-            return 1
-
-
-# do not use `test` as function name as this leads to a recursion problem with
-# the nose test suite
-test = _test
-test_verbose = functools.partial(test, verbose=True)
-test_verbose.__doc__ = test.__doc__
-doctest = functools.partial(test, doctest=True)
-doctest.__doc__ = doctest.__doc__
-doctest_verbose = functools.partial(test, doctest=True, verbose=True)
-doctest_verbose.__doc__ = doctest.__doc__
-
-
 # Logic for checking for improper install and importing while in the source
 # tree when package has not been installed inplace.
 # Code adapted from scikit-learn's __check_build module.
diff --git a/tox.ini b/tox.ini
index 9722a542..20065b91 100644
--- a/tox.ini
+++ b/tox.ini
@@ -17,8 +17,7 @@ deps =
     -e .
 
     {test,watch}: flake8>=3.8.3
-    {test,watch}: nose>=1.3.7
-    {test,watch}: pytest>=5.4.3
+    {test,watch}: pytest>=7.4.2
 
     watch: pytest-watch>=4.2.0
 commands =


Test patch:
diff --git a/DEPENDS-tests.txt b/DEPENDS-tests.txt
index 999e9c95..482664ea 100644
--- a/DEPENDS-tests.txt
+++ b/DEPENDS-tests.txt
@@ -4,5 +4,5 @@
 -r DEPENDS.txt
 coveralls
 flake8
-nose
+packaging
 pytest
diff --git a/skfuzzy/cluster/tests/test_cmeans.py b/skfuzzy/cluster/tests/test_cmeans.py
index 690451b8..4cd7a72e 100644
--- a/skfuzzy/cluster/tests/test_cmeans.py
+++ b/skfuzzy/cluster/tests/test_cmeans.py
@@ -1,8 +1,9 @@
-import nose
+import pytest
 import numpy as np
 import skfuzzy as fuzz
 
 
+@pytest.fixture
 def setup():
     global features, x_corr, y_corr
 
@@ -24,8 +25,7 @@ def setup():
     features = np.c_[xpts, ypts].T
 
 
-@nose.with_setup(setup)
-def test_fuzzy_cmeans_centers():
+def test_fuzzy_cmeans_centers(setup):
     """
     Test fuzzy c-means ability to find cluster centers.
 
@@ -52,8 +52,7 @@ def test_fuzzy_cmeans_centers():
     np.testing.assert_array_equal(cntr1, cntr2)
 
 
-@nose.with_setup(setup)
-def test_fuzzy_cmeans_fpc():
+def test_fuzzy_cmeans_fpc(setup):
     """
     Test utility of fuzzy partition coefficient.
 
@@ -79,8 +78,7 @@ def test_fuzzy_cmeans_fpc():
     assert test == expected
 
 
-@nose.with_setup(setup)
-def test_fuzzy_cmeans_predict():
+def test_fuzzy_cmeans_predict(setup):
     """
     Test ability to classify new data.
 
@@ -125,8 +123,7 @@ def test_fuzzy_cmeans_predict():
     np.testing.assert_array_equal(cluster, U.argmax(axis=0))
 
 
-@nose.with_setup(setup)
-def test_fuzzy_cmeans_predict_numerically():
+def test_fuzzy_cmeans_predict_numerically(setup):
     """
     Test ability to classify new data in a numerically safe manner.
 
diff --git a/skfuzzy/control/tests/_skipclass.py b/skfuzzy/control/tests/_skipclass.py
deleted file mode 100644
index ac256642..00000000
--- a/skfuzzy/control/tests/_skipclass.py
+++ /dev/null
@@ -1,122 +0,0 @@
-import collections.abc
-import functools
-import types
-try:
-    from unittest import SkipTest, TestCase
-except Exception:
-    from nose import SkipTest
-    from nose.case import Test as TestCase
-
-
-def _id(obj):
-    return obj
-
-
-def skip(reason):
-    """Unconditionally skip a test."""
-    def decorator(test_item):
-        try:
-            types.ClassType
-            testagainst = (type, types.ClassType)
-        except AttributeError:
-            testagainst = type
-        if not isinstance(test_item, testagainst):
-            @functools.wraps(test_item)
-            def skip_wrapper(*args, **kwargs):
-                raise SkipTest(reason)
-            test_item = skip_wrapper
-        elif issubclass(test_item, TestCase):
-            @classmethod
-            @functools.wraps(test_item.setUpClass)
-            def skip_wrapper(*args, **kwargs):
-                raise SkipTest(reason)
-            test_item.setUpClass = skip_wrapper
-        test_item.__unittest_skip__ = True
-        test_item.__unittest_skip_why__ = reason
-        return test_item
-    return decorator
-
-
-def skipclassif(condition, reason):
-    """Skip a test if the condition is true."""
-    if condition:
-        return skip(reason)
-    return _id
-
-
-def skipif(skip_condition, msg=None):
-    """
-    Copied from numpy < 1.25
-
-    Make function raise SkipTest exception if a given condition is true.
-
-    If the condition is a callable, it is used at runtime to dynamically
-    make the decision. This is useful for tests that may require costly
-    imports, to delay the cost until the test suite is actually executed.
-
-    Parameters
-    ----------
-    skip_condition : bool or callable
-        Flag to determine whether to skip the decorated test.
-    msg : str, optional
-        Message to give on raising a SkipTest exception. Default is None.
-
-    Returns
-    -------
-    decorator : function
-        Decorator which, when applied to a function, causes SkipTest
-        to be raised when `skip_condition` is True, and the function
-        to be called normally otherwise.
-
-    Notes
-    -----
-    The decorator itself is decorated with the ``nose.tools.make_decorator``
-    function in order to transmit function name, and various other metadata.
-
-    """
-
-    def skip_decorator(f):
-        # Local import to avoid a hard nose dependency and only incur the
-        # import time overhead at actual test-time.
-        import nose
-
-        # Allow for both boolean or callable skip conditions.
-        if isinstance(skip_condition, collections.abc.Callable):
-            skip_val = lambda: skip_condition()  # noqa: E731
-        else:
-            skip_val = lambda: skip_condition    # noqa: E731
-
-        def get_msg(func, msg=None):
-            """Skip message with information about function being skipped."""
-            if msg is None:
-                out = 'Test skipped due to test condition'
-            else:
-                out = msg
-
-            return f'Skipping test: {func.__name__}: {out}'
-
-        # We need to define *two* skippers because Python doesn't allow both
-        # return with value and yield inside the same function.
-        def skipper_func(*args, **kwargs):
-            """Skipper for normal test functions."""
-            if skip_val():
-                raise SkipTest(get_msg(f, msg))
-            else:
-                return f(*args, **kwargs)
-
-        def skipper_gen(*args, **kwargs):
-            """Skipper for test generators."""
-            if skip_val():
-                raise SkipTest(get_msg(f, msg))
-            else:
-                yield from f(*args, **kwargs)
-
-        # Choose the right skipper to use when building the actual decorator.
-        if nose.util.isgenerator(f):
-            skipper = skipper_gen
-        else:
-            skipper = skipper_func
-
-        return nose.tools.make_decorator(f)(skipper)
-
-    return skip_decorator
diff --git a/skfuzzy/control/tests/test_controlsystem.py b/skfuzzy/control/tests/test_controlsystem.py
index 3048ab12..04a091a3 100644
--- a/skfuzzy/control/tests/test_controlsystem.py
+++ b/skfuzzy/control/tests/test_controlsystem.py
@@ -1,13 +1,10 @@
 import networkx
-import nose
 import numpy as np
 import numpy.testing as tst
 import skfuzzy as fuzz
 import skfuzzy.control as ctrl
-from pytest import approx, raises
-from distutils.version import StrictVersion
-
-from _skipclass import skipif
+import pytest
+from packaging.version import Version
 
 from skfuzzy.control import EmptyMembershipError
 
@@ -61,6 +58,7 @@ def test_tipping_problem():
     tst.assert_allclose(tip_sim.output['tip'], 19.8578, atol=1e-2, rtol=1e-2)
 
 
+@pytest.fixture
 def setup_rule_order():
     global a, b, c, d
     a = ctrl.Antecedent(np.linspace(0, 10, 11), 'a')
@@ -129,9 +127,8 @@ def test_bad_inputs():
                              'raise an IndexError.')
 
 
-@skipif(StrictVersion(networkx.__version__) >= StrictVersion("2.0"))
-@nose.with_setup(setup_rule_order)
-def test_rule_order():
+@pytest.mark.skipif(Version(networkx.__version__) >= Version("2.0"), reason="networkx 2.0+ does not support topological sort")
+def test_rule_order(setup_rule_order):
     # Make sure rules are exposed in the order needed to solve them
     # correctly
     global a, b, c, d
@@ -147,10 +144,8 @@ def test_rule_order():
                                               [r1.label, r2.label, r3.label]))
 
 
-# The assert_raises decorator does not work in Python 2.6
-@skipif(StrictVersion(networkx.__version__) >= StrictVersion("2.0"))
-@nose.with_setup(setup_rule_order)
-def test_unresolvable_rule_order():
+@pytest.mark.skipif(Version(networkx.__version__) >= Version("2.0"), reason="networkx 2.0+ does not support topological sort")
+def test_unresolvable_rule_order(setup_rule_order):
     # Make sure we don't get suck in an infinite loop when the user
     # gives an unresolvable rule order
     global a, b, c, d
@@ -165,8 +160,7 @@ def test_unresolvable_rule_order():
         list(ctrl_sys.rules)
 
 
-@nose.with_setup(setup_rule_order)
-def test_bad_rules():
+def test_bad_rules(setup_rule_order):
     global a
 
     not_rules = ['me', 192238, 42, dict()]
@@ -198,13 +192,13 @@ def test_lenient_simulation():
     assert set(sim.output.keys()) == {"y1", "y2"}
     # print("- sim.output['y1']:", sim.output["y1"])
     # print("- sim.output['y2']:", sim.output["y2"])
-    assert sim.output["y1"] == approx(8.333333)
-    assert sim.output["y2"] == approx(8.333333)
+    assert sim.output["y1"] == pytest.approx(8.333333)
+    assert sim.output["y2"] == pytest.approx(8.333333)
 
     sim = ctrl.ControlSystemSimulation(sys, lenient=False)
     sim.input["x1"] = 10
     sim.input["x2"] = 0
-    with raises(EmptyMembershipError):
+    with pytest.raises(EmptyMembershipError):
         sim.compute()
 
     sim = ctrl.ControlSystemSimulation(sys, lenient=True)
@@ -212,7 +206,7 @@ def test_lenient_simulation():
     sim.input["x2"] = 0
     sim.compute()
     assert set(sim.output.keys()) == {"y2"}
-    assert sim.output["y2"] == approx(8.333333)
+    assert sim.output["y2"] == pytest.approx(8.333333)
 
 
 def test_cached_lenient_simulation():
diff --git a/skfuzzy/control/tests/test_fuzzyvariables.py b/skfuzzy/control/tests/test_fuzzyvariables.py
index bb874a27..b442b08a 100644
--- a/skfuzzy/control/tests/test_fuzzyvariables.py
+++ b/skfuzzy/control/tests/test_fuzzyvariables.py
@@ -1,12 +1,13 @@
 from collections import OrderedDict
 
-import nose
+import pytest
 import numpy as np
 import numpy.testing as tst
 
 from skfuzzy.control import Antecedent, Consequent
 
 
+@pytest.fixture
 def setup():
     global ant
     global con
@@ -83,8 +84,7 @@ def test_instantiate_consequent_different_defuzzify_method():
     assert con.defuzzify_method == defuzzify_method
 
 
-@nose.with_setup(setup)
-def test_automf3():
+def test_automf3(setup):
     global ant  # universe: [0, 1, 2, 3, 4, 5]
     global con  # universe: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
 
@@ -129,8 +129,7 @@ def test_automf3():
     assert list(con.terms.keys()) == alt_label3[::-1]
 
 
-@nose.with_setup(setup)
-def test_automf5():
+def test_automf5(setup):
     global ant  # universe: [0, 1, 2, 3, 4, 5]
     global con  # universe: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
 
@@ -179,8 +178,7 @@ def test_automf5():
     assert list(con.terms.keys()) == alt_label5[::-1]
 
 
-@nose.with_setup(setup)
-def test_automf7():
+def test_automf7(setup):
     global ant  # universe: [0, 1, 2, 3, 4, 5]
     global con  # universe: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
 
@@ -235,8 +233,7 @@ def test_automf7():
     assert list(con.terms.keys()) == alt_label7[::-1]
 
 
-@nose.with_setup(setup)
-def test_automf_bad():
+def test_automf_bad(setup):
     global ant
     global con
 
@@ -246,8 +243,7 @@ def test_automf_bad():
     tst.assert_raises(ValueError, con.automf, np.pi)
 
 
-@nose.with_setup(setup)
-def test_bad_set_active():
+def test_bad_set_active(setup):
     global ant
     global con
 
@@ -286,8 +282,7 @@ def test_add_mf():
     tst.assert_equal(con.terms['high'].mf, mf1)
 
 
-@nose.with_setup(setup)
-def test_add_bad_mf():
+def test_add_bad_mf(setup):
     global ant
     global con
 
diff --git a/skfuzzy/image/tests/_skipclass.py b/skfuzzy/image/tests/_skipclass.py
deleted file mode 100644
index c6238857..00000000
--- a/skfuzzy/image/tests/_skipclass.py
+++ /dev/null
@@ -1,43 +0,0 @@
-import functools
-import types
-try:
-    from unittest import SkipTest, TestCase
-except Exception:
-    from nose import SkipTest
-    from nose.case import Test as TestCase
-
-
-def _id(obj):
-    return obj
-
-
-def skip(reason):
-    """Unconditionally skip a test."""
-    def decorator(test_item):
-        try:
-            types.ClassType
-            testagainst = (type, types.ClassType)
-        except AttributeError:
-            testagainst = type
-        if not isinstance(test_item, testagainst):
-            @functools.wraps(test_item)
-            def skip_wrapper(*args, **kwargs):
-                raise SkipTest(reason)
-            test_item = skip_wrapper
-        elif issubclass(test_item, TestCase):
-            @classmethod
-            @functools.wraps(test_item.setUpClass)
-            def skip_wrapper(*args, **kwargs):
-                raise SkipTest(reason)
-            test_item.setUpClass = skip_wrapper
-        test_item.__unittest_skip__ = True
-        test_item.__unittest_skip_why__ = reason
-        return test_item
-    return decorator
-
-
-def skipclassif(condition, reason):
-    """Skip a test if the condition is true."""
-    if condition:
-        return skip(reason)
-    return _id
diff --git a/skfuzzy/image/tests/test_pad.py b/skfuzzy/image/tests/test_pad.py
index 0bf8391b..9ef91243 100644
--- a/skfuzzy/image/tests/test_pad.py
+++ b/skfuzzy/image/tests/test_pad.py
@@ -1,19 +1,19 @@
 """
 Tests for the array padding functions.
 """
-from distutils.version import LooseVersion
+from packaging.version import Version
 
 import numpy as np
 from numpy.testing import (assert_array_equal, assert_raises, assert_allclose,
                            TestCase)
 
-from _skipclass import skipclassif
+import pytest
 
 from skfuzzy.image import pad
 
 
-@skipclassif(LooseVersion(np.__version__) > LooseVersion("1.8"),
-             "NumPy's inbuilt pad used instead")
+
+@pytest.mark.skipif(Version(np.__version__) > Version("1.8"), reason="NumPy's inbuilt pad used instead")
 class TestConditionalShortcuts(TestCase):
     def test_zero_padding_shortcuts(self):
         test = np.arange(120).reshape(4, 5, 6)
@@ -57,8 +57,7 @@ def test_clip_statistic_range(self):
                                pad(test, pad_amt, mode=mode, stat_length=30))
 
 
-@skipclassif(LooseVersion(np.__version__) > LooseVersion("1.8"),
-             "NumPy's inbuilt pad used instead")
+@pytest.mark.skipif(Version(np.__version__) > Version("1.8"), reason="NumPy's inbuilt pad used instead")
 class TestStatistic(TestCase):
     def test_check_mean_stat_length(self):
         a = np.arange(100).astype('f')
@@ -353,8 +352,7 @@ def test_check_mean_2(self):
         assert_array_equal(a, b)
 
 
-@skipclassif(LooseVersion(np.__version__) > LooseVersion("1.8"),
-             "NumPy's inbuilt pad used instead")
+@pytest.mark.skipif(Version(np.__version__) > Version("1.8"), reason="NumPy's inbuilt pad used instead")
 class TestConstant(TestCase):
     def test_check_constant(self):
         a = np.arange(100)
@@ -487,8 +485,7 @@ def test_check_constant_odd_pad_amount(self):
         assert_allclose(test, expected)
 
 
-@skipclassif(LooseVersion(np.__version__) > LooseVersion("1.8"),
-             "NumPy's inbuilt pad used instead")
+@pytest.mark.skipif(Version(np.__version__) > Version("1.8"), reason="NumPy's inbuilt pad used instead")
 class TestLinearRamp(TestCase):
     def test_check_simple(self):
         a = np.arange(100).astype('f')
@@ -530,8 +527,7 @@ def test_check_2d(self):
         assert_allclose(test, expected)
 
 
-@skipclassif(LooseVersion(np.__version__) > LooseVersion("1.8"),
-             "NumPy's inbuilt pad used instead")
+@pytest.mark.skipif(Version(np.__version__) > Version("1.8"), reason="NumPy's inbuilt pad used instead")
 class TestReflect(TestCase):
     def test_check_simple(self):
         a = np.arange(100)
@@ -642,8 +638,7 @@ def test_check_03(self):
         assert_array_equal(a, b)
 
 
-@skipclassif(LooseVersion(np.__version__) > LooseVersion("1.8"),
-             "NumPy's inbuilt pad used instead")
+@pytest.mark.skipif(Version(np.__version__) > Version("1.8"), reason="NumPy's inbuilt pad used instead")
 class TestSymmetric(TestCase):
     def test_check_simple(self):
         a = np.arange(100)
@@ -778,8 +773,7 @@ def test_check_03(self):
         assert_array_equal(a, b)
 
 
-@skipclassif(LooseVersion(np.__version__) > LooseVersion("1.8"),
-             "NumPy's inbuilt pad used instead")
+@pytest.mark.skipif(Version(np.__version__) > Version("1.8"), reason="NumPy's inbuilt pad used instead")
 class TestWrap(TestCase):
     def test_check_simple(self):
         a = np.arange(100)
@@ -876,8 +870,7 @@ def test_check_02(self):
         assert_array_equal(a, b)
 
 
-@skipclassif(LooseVersion(np.__version__) > LooseVersion("1.8"),
-             "NumPy's inbuilt pad used instead")
+@pytest.mark.skipif(Version(np.__version__) > Version("1.8"), reason="NumPy's inbuilt pad used instead")
 class TestStatLen(TestCase):
     def test_check_simple(self):
         a = np.arange(30)
@@ -901,8 +894,7 @@ def test_check_simple(self):
         assert_array_equal(a, b)
 
 
-@skipclassif(LooseVersion(np.__version__) > LooseVersion("1.8"),
-             "NumPy's inbuilt pad used instead")
+@pytest.mark.skipif(Version(np.__version__) > Version("1.8"), reason="NumPy's inbuilt pad used instead")
 class TestEdge(TestCase):
     def test_check_simple(self):
         a = np.arange(12)
@@ -924,8 +916,7 @@ def test_check_simple(self):
         assert_array_equal(a, b)
 
 
-@skipclassif(LooseVersion(np.__version__) > LooseVersion("1.8"),
-             "NumPy's inbuilt pad used instead")
+@pytest.mark.skipif(Version(np.__version__) > Version("1.8"), reason="NumPy's inbuilt pad used instead")
 class TestZeroPadWidth(TestCase):
     def test_zero_pad_width(self):
         arr = np.arange(30)
@@ -934,8 +925,7 @@ def test_zero_pad_width(self):
             assert_array_equal(arr, pad(arr, pad_width, mode='constant'))
 
 
-@skipclassif(LooseVersion(np.__version__) > LooseVersion("1.8"),
-             "NumPy's inbuilt pad used instead")
+@pytest.mark.skipif(Version(np.__version__) > Version("1.8"), reason="NumPy's inbuilt pad used instead")
 class TestLegacyVectorFunction(TestCase):
     def test_legacy_vector_functionality(self):
         def _padwithtens(vector, pad_width, iaxis, kwargs):
@@ -958,8 +948,7 @@ def _padwithtens(vector, pad_width, iaxis, kwargs):
         assert_array_equal(a, b)
 
 
-@skipclassif(LooseVersion(np.__version__) > LooseVersion("1.8"),
-             "NumPy's inbuilt pad used instead")
+@pytest.mark.skipif(Version(np.__version__) > Version("1.8"), reason="NumPy's inbuilt pad used instead")
 class TestNdarrayPadWidth(TestCase):
     def test_check_simple(self):
         a = np.arange(12)
@@ -981,8 +970,7 @@ def test_check_simple(self):
         assert_array_equal(a, b)
 
 
-@skipclassif(LooseVersion(np.__version__) > LooseVersion("1.8"),
-             "NumPy's inbuilt pad used instead")
+@pytest.mark.skipif(Version(np.__version__) > Version("1.8"), reason="NumPy's inbuilt pad used instead")
 class ValueError1(TestCase):
     def test_check_simple(self):
         arr = np.arange(30)
@@ -1006,8 +994,7 @@ def test_check_negative_pad_width(self):
                       **kwargs)
 
 
-@skipclassif(LooseVersion(np.__version__) > LooseVersion("1.8"),
-             "NumPy's inbuilt pad used instead")
+@pytest.mark.skipif(Version(np.__version__) > Version("1.8"), reason="NumPy's inbuilt pad used instead")
 class ValueError2(TestCase):
     def test_check_negative_pad_amount(self):
         arr = np.arange(30)
@@ -1017,8 +1004,7 @@ def test_check_negative_pad_amount(self):
                       **kwargs)
 
 
-@skipclassif(LooseVersion(np.__version__) > LooseVersion("1.8"),
-             "NumPy's inbuilt pad used instead")
+@pytest.mark.skipif(Version(np.__version__) > Version("1.8"), reason="NumPy's inbuilt pad used instead")
 class ValueError3(TestCase):
     def test_check_kwarg_not_allowed(self):
         arr = np.arange(30).reshape(5, 6)
@@ -1047,8 +1033,7 @@ def test_pad_too_many_axes(self):
                       mode='constant')
 
 
-@skipclassif(LooseVersion(np.__version__) > LooseVersion("1.8"),
-             "NumPy's inbuilt pad used instead")
+@pytest.mark.skipif(Version(np.__version__) > Version("1.8"), reason="NumPy's inbuilt pad used instead")
 class TypeError1(TestCase):
     def test_float(self):
         arr = np.arange(30)
diff --git a/skfuzzy/image/tests/test_view_as_.py b/skfuzzy/image/tests/test_view_as_.py
index 4433cbeb..58a2ee1e 100644
--- a/skfuzzy/image/tests/test_view_as_.py
+++ b/skfuzzy/image/tests/test_view_as_.py
@@ -3,39 +3,39 @@
 
 """
 import numpy as np
-from nose.tools import raises
+import pytest
 from numpy.testing import assert_equal
 from skfuzzy.image.shape import view_as_blocks, view_as_windows
 
 
-@raises(TypeError)
 def test_view_as_blocks_block_not_a_tuple():
     A = np.arange(10)
-    view_as_blocks(A, [5])
+    with pytest.raises(TypeError):
+        view_as_blocks(A, [5])
 
 
-@raises(ValueError)
 def test_view_as_blocks_negative_shape():
     A = np.arange(10)
-    view_as_blocks(A, (-2,))
+    with pytest.raises(ValueError):
+        view_as_blocks(A, (-2,))
 
 
-@raises(ValueError)
 def test_view_as_blocks_block_too_large():
     A = np.arange(10)
-    view_as_blocks(A, (11,))
+    with pytest.raises(ValueError):
+        view_as_blocks(A, (11,))
 
 
-@raises(ValueError)
 def test_view_as_blocks_wrong_block_dimension():
     A = np.arange(10)
-    view_as_blocks(A, (2, 2))
+    with pytest.raises(ValueError):
+        view_as_blocks(A, (2, 2))
 
 
-@raises(ValueError)
 def test_view_as_blocks_1D_array_wrong_block_shape():
     A = np.arange(10)
-    view_as_blocks(A, (3,))
+    with pytest.raises(ValueError):
+        view_as_blocks(A, (3,))
 
 
 def test_view_as_blocks_1D_array():
@@ -50,47 +50,47 @@ def test_view_as_blocks_2D_array():
     B = view_as_blocks(A, (2, 2))
     assert_equal(B[0, 1], np.array([[2, 3],
                                     [6, 7]]))
-    assert_equal(B[1, 0, 1, 1], 13)
+    assert B[1, 0, 1, 1] == 13
 
 
 def test_view_as_blocks_3D_array():
     A = np.arange(4 * 4 * 6).reshape(4, 4, 6)
     B = view_as_blocks(A, (1, 2, 2))
-    assert_equal(B.shape, (4, 2, 3, 1, 2, 2))
+    assert B.shape == (4, 2, 3, 1, 2, 2)
     assert_equal(B[2:, 0, 2], np.array([[[[52, 53],
                                           [58, 59]]],
                                         [[[76, 77],
                                           [82, 83]]]]))
 
 
-@raises(TypeError)
 def test_view_as_windows_input_not_array():
     A = [1, 2, 3, 4, 5]
-    view_as_windows(A, (2,))
+    with pytest.raises(TypeError):
+        view_as_windows(A, (2,))
 
 
-@raises(TypeError)
 def test_view_as_windows_window_not_tuple():
     A = np.arange(10)
-    view_as_windows(A, [2])
+    with pytest.raises(TypeError):
+        view_as_windows(A, [2])
 
 
-@raises(ValueError)
 def test_view_as_windows_wrong_window_dimension():
     A = np.arange(10)
-    view_as_windows(A, (2, 2))
+    with pytest.raises(ValueError):
+        view_as_windows(A, (2, 2))
 
 
-@raises(ValueError)
 def test_view_as_windows_negative_window_length():
     A = np.arange(10)
-    view_as_windows(A, (-1,))
+    with pytest.raises(ValueError):
+        view_as_windows(A, (-1,))
 
 
-@raises(ValueError)
 def test_view_as_windows_window_too_large():
     A = np.arange(10)
-    view_as_windows(A, (11,))
+    with pytest.raises(ValueError):
+        view_as_windows(A, (11,))
 
 
 def test_view_as_windows_1D():
@@ -111,7 +111,7 @@ def test_view_as_windows_2D():
     A = np.arange(5 * 4).reshape(5, 4)
     window_shape = (4, 3)
     B = view_as_windows(A, window_shape)
-    assert_equal(B.shape, (2, 2, 4, 3))
+    assert B.shape == (2, 2, 4, 3)
     assert_equal(B, np.array([[[[0,  1,  2],
                                 [4,  5,  6],
                                 [8,  9, 10],