Index: python/tests/__init__.py
===================================================================
--- python/tests/__init__.py	(revision 38214151f08818ad130112ddceaa2cb543f876bb)
+++ 	(revision )
@@ -1,1 +1,0 @@
-
Index: python/tests/_tools.py
===================================================================
--- python/tests/_tools.py	(revision adc6e02ecf2d72a827822e7d09b519434e66376a)
+++ python/tests/_tools.py	(revision adc6e02ecf2d72a827822e7d09b519434e66376a)
@@ -0,0 +1,41 @@
+"""
+This file imports test methods from different testing modules, in this
+order:
+
+    - try importing 'pytest'
+    - if it fails, fallback to 'numpy.testing'
+
+Nose2 support was removed because of lacking assertWarns on py2.7.
+
+"""
+
+import sys
+
+_has_pytest = False
+
+# check if we have pytest
+try:
+    import pytest
+    parametrize = pytest.mark.parametrize
+    assert_raises = pytest.raises
+    assert_warns = pytest.warns
+    skipTest = pytest.skip
+    _has_pytest = True
+    def run_module_suite():
+        import sys, pytest
+        pytest.main(sys.argv)
+except:
+    pass
+
+# otherwise fallback on numpy.testing
+if not _has_pytest:
+    from numpy.testing import dec, assert_raises, assert_warns
+    from numpy.testing import SkipTest
+    parametrize = dec.parametrize
+    def skipTest(msg):
+        raise SkipTest(msg)
+    from numpy.testing import run_module_suite
+
+# always use numpy's assert_equal
+import numpy
+assert_equal = numpy.testing.assert_equal
Index: python/tests/run_all_tests
===================================================================
--- python/tests/run_all_tests	(revision 38214151f08818ad130112ddceaa2cb543f876bb)
+++ 	(revision )
@@ -1,5 +1,0 @@
-#! /usr/bin/env python
-
-if __name__ == '__main__':
-    import nose2.main
-    nose2.discover()
Index: python/tests/test_aubio.py
===================================================================
--- python/tests/test_aubio.py	(revision 38214151f08818ad130112ddceaa2cb543f876bb)
+++ python/tests/test_aubio.py	(revision adc6e02ecf2d72a827822e7d09b519434e66376a)
@@ -1,5 +1,4 @@
 #! /usr/bin/env python
 
-from unittest import main
 from numpy.testing import TestCase
 
@@ -16,4 +15,4 @@
 
 if __name__ == '__main__':
+    from unittest import main
     main()
-
Index: python/tests/test_aubio_cmd.py
===================================================================
--- python/tests/test_aubio_cmd.py	(revision 38214151f08818ad130112ddceaa2cb543f876bb)
+++ python/tests/test_aubio_cmd.py	(revision adc6e02ecf2d72a827822e7d09b519434e66376a)
@@ -1,7 +1,6 @@
 #! /usr/bin/env python
 
+from numpy.testing import TestCase
 import aubio.cmd
-from nose2 import main
-from numpy.testing import TestCase
 
 class aubio_cmd(TestCase):
@@ -32,3 +31,4 @@
 
 if __name__ == '__main__':
+    from unittest import main
     main()
Index: python/tests/test_aubio_cut.py
===================================================================
--- python/tests/test_aubio_cut.py	(revision 38214151f08818ad130112ddceaa2cb543f876bb)
+++ python/tests/test_aubio_cut.py	(revision adc6e02ecf2d72a827822e7d09b519434e66376a)
@@ -2,5 +2,4 @@
 
 import aubio.cut
-from nose2 import main
 from numpy.testing import TestCase
 
@@ -14,3 +13,4 @@
 
 if __name__ == '__main__':
+    from unittest import main
     main()
Index: python/tests/test_cvec.py
===================================================================
--- python/tests/test_cvec.py	(revision 38214151f08818ad130112ddceaa2cb543f876bb)
+++ python/tests/test_cvec.py	(revision adc6e02ecf2d72a827822e7d09b519434e66376a)
@@ -1,5 +1,4 @@
 #! /usr/bin/env python
 
-from unittest import main
 import numpy as np
 from numpy.testing import TestCase, assert_equal
@@ -142,3 +141,4 @@
 
 if __name__ == '__main__':
+    from unittest import main
     main()
Index: python/tests/test_dct.py
===================================================================
--- python/tests/test_dct.py	(revision 38214151f08818ad130112ddceaa2cb543f876bb)
+++ python/tests/test_dct.py	(revision adc6e02ecf2d72a827822e7d09b519434e66376a)
@@ -67,2 +67,6 @@
         except AssertionError:
             self.skipTest('creating aubio.dct with size %d did not fail' % size)
+
+if __name__ == '__main__':
+    from unittest import main
+    main()
Index: python/tests/test_fft.py
===================================================================
--- python/tests/test_fft.py	(revision 38214151f08818ad130112ddceaa2cb543f876bb)
+++ python/tests/test_fft.py	(revision adc6e02ecf2d72a827822e7d09b519434e66376a)
@@ -1,5 +1,4 @@
 #! /usr/bin/env python
 
-from unittest import main
 from numpy.testing import TestCase
 from numpy.testing import assert_equal, assert_almost_equal
@@ -213,3 +212,4 @@
 
 if __name__ == '__main__':
+    from unittest import main
     main()
Index: python/tests/test_filter.py
===================================================================
--- python/tests/test_filter.py	(revision 38214151f08818ad130112ddceaa2cb543f876bb)
+++ python/tests/test_filter.py	(revision adc6e02ecf2d72a827822e7d09b519434e66376a)
@@ -1,8 +1,7 @@
 #! /usr/bin/env python
 
-from unittest import main
 from numpy.testing import TestCase, assert_equal, assert_almost_equal
 from aubio import fvec, digital_filter
-from .utils import array_from_text_file
+from utils import array_from_text_file
 
 class aubio_filter_test_case(TestCase):
@@ -95,3 +94,4 @@
 
 if __name__ == '__main__':
+    from unittest import main
     main()
Index: python/tests/test_filterbank.py
===================================================================
--- python/tests/test_filterbank.py	(revision 38214151f08818ad130112ddceaa2cb543f876bb)
+++ python/tests/test_filterbank.py	(revision adc6e02ecf2d72a827822e7d09b519434e66376a)
@@ -5,5 +5,5 @@
 
 from aubio import cvec, filterbank, float_type
-from .utils import array_from_text_file
+from utils import array_from_text_file
 
 class aubio_filterbank_test_case(TestCase):
@@ -88,4 +88,4 @@
 
 if __name__ == '__main__':
-    import nose2
-    nose2.main()
+    from unittest import main
+    main()
Index: python/tests/test_filterbank_mel.py
===================================================================
--- python/tests/test_filterbank_mel.py	(revision 38214151f08818ad130112ddceaa2cb543f876bb)
+++ python/tests/test_filterbank_mel.py	(revision adc6e02ecf2d72a827822e7d09b519434e66376a)
@@ -3,9 +3,7 @@
 import numpy as np
 from numpy.testing import TestCase, assert_equal, assert_almost_equal
+from _tools import assert_warns
 
 from aubio import fvec, cvec, filterbank, float_type
-
-import warnings
-warnings.filterwarnings('ignore', category=UserWarning, append=True)
 
 class aubio_filterbank_mel_test_case(TestCase):
@@ -76,6 +74,6 @@
         freq_list = [0, samplerate//4, samplerate // 2 + 1]
         f = filterbank(len(freq_list)-2, 1024)
-        # TODO add assert_warns
-        f.set_triangle_bands(fvec(freq_list), samplerate)
+        with assert_warns(UserWarning):
+            f.set_triangle_bands(fvec(freq_list), samplerate)
 
     def test_triangle_freqs_with_not_enough_filters(self):
@@ -84,6 +82,6 @@
         freq_list = [0, 100, 1000, 4000, 8000, 10000]
         f = filterbank(len(freq_list)-3, 1024)
-        # TODO add assert_warns
-        f.set_triangle_bands(fvec(freq_list), samplerate)
+        with assert_warns(UserWarning):
+            f.set_triangle_bands(fvec(freq_list), samplerate)
 
     def test_triangle_freqs_with_too_many_filters(self):
@@ -92,6 +90,6 @@
         freq_list = [0, 100, 1000, 4000, 8000, 10000]
         f = filterbank(len(freq_list)-1, 1024)
-        # TODO add assert_warns
-        f.set_triangle_bands(fvec(freq_list), samplerate)
+        with assert_warns(UserWarning):
+            f.set_triangle_bands(fvec(freq_list), samplerate)
 
     def test_triangle_freqs_with_double_value(self):
@@ -100,6 +98,6 @@
         freq_list = [0, 100, 1000, 4000, 4000, 4000, 10000]
         f = filterbank(len(freq_list)-2, 1024)
-        # TODO add assert_warns
-        f.set_triangle_bands(fvec(freq_list), samplerate)
+        with assert_warns(UserWarning):
+            f.set_triangle_bands(fvec(freq_list), samplerate)
 
     def test_triangle_freqs_with_triple(self):
@@ -108,6 +106,7 @@
         freq_list = [0, 100, 1000, 4000, 4000, 4000, 10000]
         f = filterbank(len(freq_list)-2, 1024)
-        # TODO add assert_warns
-        f.set_triangle_bands(fvec(freq_list), samplerate)
+        with assert_warns(UserWarning):
+            f.set_triangle_bands(fvec(freq_list), samplerate)
+
 
     def test_triangle_freqs_without_norm(self):
@@ -169,4 +168,4 @@
 
 if __name__ == '__main__':
-    import nose2
-    nose2.main()
+    from unittest import main
+    main()
Index: python/tests/test_fvec.py
===================================================================
--- python/tests/test_fvec.py	(revision 38214151f08818ad130112ddceaa2cb543f876bb)
+++ python/tests/test_fvec.py	(revision adc6e02ecf2d72a827822e7d09b519434e66376a)
@@ -1,5 +1,4 @@
 #! /usr/bin/env python
 
-from unittest import main
 import numpy as np
 from numpy.testing import TestCase, assert_equal, assert_almost_equal
@@ -149,3 +148,4 @@
 
 if __name__ == '__main__':
+    from unittest import main
     main()
Index: python/tests/test_fvec_shift.py
===================================================================
--- python/tests/test_fvec_shift.py	(revision 38214151f08818ad130112ddceaa2cb543f876bb)
+++ python/tests/test_fvec_shift.py	(revision adc6e02ecf2d72a827822e7d09b519434e66376a)
@@ -31,5 +31,5 @@
         self.run_shift_ishift(7)
 
-from unittest import main
 if __name__ == '__main__':
+    from unittest import main
     main()
Index: python/tests/test_hztomel.py
===================================================================
--- python/tests/test_hztomel.py	(revision 38214151f08818ad130112ddceaa2cb543f876bb)
+++ python/tests/test_hztomel.py	(revision adc6e02ecf2d72a827822e7d09b519434e66376a)
@@ -4,4 +4,5 @@
 from numpy.testing import TestCase
 from numpy.testing import assert_equal, assert_almost_equal
+from _tools import assert_warns
 import numpy as np
 import aubio
@@ -38,10 +39,10 @@
 
     def test_meltohz_negative(self):
-        # TODO add assert_warns
-        assert_equal(meltohz(-1), 0)
+        with assert_warns(UserWarning):
+            assert_equal(meltohz(-1), 0)
 
     def test_hztomel_negative(self):
-        # TODO add assert_warns
-        assert_equal(hztomel(-1), 0)
+        with assert_warns(UserWarning):
+            assert_equal(hztomel(-1), 0)
 
 
@@ -58,12 +59,14 @@
 
     def test_meltohz_negative(self):
-        # TODO add assert_warns
-        assert_equal(meltohz(-1, htk=True), 0)
+        with assert_warns(UserWarning):
+            assert_equal(meltohz(-1, htk=True), 0)
         assert_almost_equal(meltohz(2000, htk=True), 3428.7, decimal=1)
         assert_almost_equal(meltohz(1000, htk=True), 1000., decimal=1)
 
     def test_hztomel_negative(self):
-        # TODO add assert_warns
-        assert_equal(hztomel(-1, htk=True), 0)
+        with assert_warns(UserWarning):
+            assert_equal(meltohz(-1, htk=True), 0)
+        with assert_warns(UserWarning):
+            assert_equal(hztomel(-1, htk=True), 0)
         assert_almost_equal(hztomel(1000, htk=True), 1000., decimal=1)
 
Index: python/tests/test_mathutils.py
===================================================================
--- python/tests/test_mathutils.py	(revision 38214151f08818ad130112ddceaa2cb543f876bb)
+++ python/tests/test_mathutils.py	(revision adc6e02ecf2d72a827822e7d09b519434e66376a)
@@ -1,5 +1,4 @@
 #! /usr/bin/env python
 
-from unittest import main
 from numpy.testing import TestCase, assert_equal
 from numpy import array, arange, isnan, isinf
@@ -102,3 +101,4 @@
 
 if __name__ == '__main__':
+    from unittest import main
     main()
Index: python/tests/test_mfcc.py
===================================================================
--- python/tests/test_mfcc.py	(revision 38214151f08818ad130112ddceaa2cb543f876bb)
+++ python/tests/test_mfcc.py	(revision adc6e02ecf2d72a827822e7d09b519434e66376a)
@@ -1,6 +1,5 @@
 #! /usr/bin/env python
 
-from nose2 import main
-from nose2.tools import params
+from _tools import parametrize, assert_raises
 from numpy import random, count_nonzero
 from numpy.testing import TestCase
@@ -16,26 +15,19 @@
 new_deflts = [1024, 40, 13, 44100]
 
-class aubio_mfcc(TestCase):
+class Test_aubio_mfcc(object):
 
-    def setUp(self):
-        self.o = mfcc()
+    members_args = 'name'
 
-    def test_default_creation(self):
-        pass
-
-    def test_delete(self):
-        del self.o
-
-    @params(*new_params)
+    @parametrize(members_args, new_params)
     def test_read_only_member(self, name):
-        o = self.o
-        with self.assertRaises((TypeError, AttributeError)):
+        o = mfcc()
+        with assert_raises((TypeError, AttributeError)):
             setattr(o, name, 0)
 
-    @params(*zip(new_params, new_deflts))
+    @parametrize('name, expected', zip(new_params, new_deflts))
     def test_default_param(self, name, expected):
         """ test mfcc.{:s} = {:d} """.format(name, expected)
-        o = self.o
-        self.assertEqual( getattr(o, name), expected)
+        o = mfcc()
+        assert getattr(o, name) == expected
 
 class aubio_mfcc_wrong_params(TestCase):
@@ -83,7 +75,7 @@
 
 
-class aubio_mfcc_all_parameters(TestCase):
+class Test_aubio_mfcc_all_parameters(object):
 
-    @params(
+    run_values = [
             (2048, 40, 13, 44100),
             (1024, 40, 13, 44100),
@@ -101,5 +93,8 @@
             (1024, 40, 40, 44100),
             (1024, 40, 3, 44100),
-            )
+            ]
+    run_args = ['buf_size', 'n_filters', 'n_coeffs', 'samplerate']
+
+    @parametrize(run_args, run_values)
     def test_run_with_params(self, buf_size, n_filters, n_coeffs, samplerate):
         " check mfcc can run with reasonable parameters "
@@ -147,3 +142,4 @@
 
 if __name__ == '__main__':
-    main()
+    from _tools import run_module_suite
+    run_module_suite()
Index: python/tests/test_midi2note.py
===================================================================
--- python/tests/test_midi2note.py	(revision 38214151f08818ad130112ddceaa2cb543f876bb)
+++ python/tests/test_midi2note.py	(revision adc6e02ecf2d72a827822e7d09b519434e66376a)
@@ -3,6 +3,5 @@
 
 from aubio import midi2note
-from nose2.tools import params
-import unittest
+from _tools import parametrize, assert_raises
 
 list_of_known_midis = (
@@ -16,30 +15,30 @@
         )
 
-class midi2note_good_values(unittest.TestCase):
+class Test_midi2note_good_values(object):
 
-    @params(*list_of_known_midis)
+    @parametrize('midi, note', list_of_known_midis)
     def test_midi2note_known_values(self, midi, note):
         " known values are correctly converted "
-        self.assertEqual ( midi2note(midi), note )
+        assert midi2note(midi) == (note)
 
-class midi2note_wrong_values(unittest.TestCase):
+class Test_midi2note_wrong_values(object):
 
     def test_midi2note_negative_value(self):
         " fails when passed a negative value "
-        self.assertRaises(ValueError, midi2note, -2)
+        assert_raises(ValueError, midi2note, -2)
 
     def test_midi2note_large(self):
         " fails when passed a value greater than 127 "
-        self.assertRaises(ValueError, midi2note, 128)
+        assert_raises(ValueError, midi2note, 128)
 
     def test_midi2note_floating_value(self):
         " fails when passed a floating point "
-        self.assertRaises(TypeError, midi2note, 69.2)
+        assert_raises(TypeError, midi2note, 69.2)
 
     def test_midi2note_character_value(self):
         " fails when passed a value that can not be transformed to integer "
-        self.assertRaises(TypeError, midi2note, "a")
+        assert_raises(TypeError, midi2note, "a")
 
 if __name__ == '__main__':
-    import nose2
-    nose2.main()
+    from _tools import run_module_suite
+    run_module_suite()
Index: python/tests/test_musicutils.py
===================================================================
--- python/tests/test_musicutils.py	(revision 38214151f08818ad130112ddceaa2cb543f876bb)
+++ python/tests/test_musicutils.py	(revision adc6e02ecf2d72a827822e7d09b519434e66376a)
@@ -1,8 +1,7 @@
 #! /usr/bin/env python
 
-from unittest import main
 import numpy as np
 from numpy.testing import TestCase
-from numpy.testing.utils import assert_equal, assert_almost_equal
+from numpy.testing import assert_equal, assert_almost_equal
 from aubio import window, level_lin, db_spl, silence_detection, level_detection
 from aubio import fvec, float_type
@@ -86,3 +85,4 @@
 
 if __name__ == '__main__':
+    from unittest import main
     main()
Index: python/tests/test_note2midi.py
===================================================================
--- python/tests/test_note2midi.py	(revision 38214151f08818ad130112ddceaa2cb543f876bb)
+++ python/tests/test_note2midi.py	(revision adc6e02ecf2d72a827822e7d09b519434e66376a)
@@ -5,6 +5,6 @@
 
 from aubio import note2midi, freq2note, note2freq, float_type
-from nose2.tools import params
-import unittest
+from numpy.testing import TestCase
+from _tools import parametrize, assert_raises, skipTest
 
 list_of_known_notes = (
@@ -45,27 +45,30 @@
         )
 
-class note2midi_good_values(unittest.TestCase):
+class Test_note2midi_good_values(object):
 
-    @params(*list_of_known_notes)
+    @parametrize('note, midi', list_of_known_notes)
     def test_note2midi_known_values(self, note, midi):
         " known values are correctly converted "
-        self.assertEqual ( note2midi(note), midi )
+        assert note2midi(note) == midi
 
-    @params(*list_of_known_notes_with_unicode_issues)
+    @parametrize('note, midi', list_of_known_notes_with_unicode_issues)
     def test_note2midi_known_values_with_unicode_issues(self, note, midi):
-        " known values are correctly converted, unless decoding is expected to fail"
+        " difficult values are correctly converted unless expected failure "
         try:
-            self.assertEqual ( note2midi(note), midi )
+            assert note2midi(note) == midi
         except UnicodeEncodeError as e:
+            # platforms with decoding failures include:
+            # - osx: python <= 2.7.10
+            # - win: python <= 2.7.12
             import sys
-            strfmt = "len(u'\\U0001D12A') != 1, excpected decoding failure | {:s} | {:s} {:s}"
-            strres = strfmt.format(e, sys.platform, sys.version)
-            # happens with: darwin 2.7.10, windows 2.7.12
+            strmsg = "len(u'\\U0001D12A') != 1, expected decoding failure"
+            strmsg += " | upgrade to Python 3 to fix"
+            strmsg += " | {:s} | {:s} {:s}"
             if len('\U0001D12A') != 1 and sys.version[0] == '2':
-                self.skipTest(strres + " | upgrade to Python 3 to fix")
+                skipTest(strmsg.format(repr(e), sys.platform, sys.version))
             else:
                 raise
 
-class note2midi_wrong_values(unittest.TestCase):
+class note2midi_wrong_values(TestCase):
 
     def test_note2midi_missing_octave(self):
@@ -105,10 +108,12 @@
         self.assertRaises(ValueError, note2midi, 'CB+-3')
 
-    @params(*list_of_unknown_notes)
+class Test_note2midi_unknown_values(object):
+
+    @parametrize('note', list_of_unknown_notes)
     def test_note2midi_unknown_values(self, note):
         " unknown values throw out an error "
-        self.assertRaises(ValueError, note2midi, note)
+        assert_raises(ValueError, note2midi, note)
 
-class freq2note_simple_test(unittest.TestCase):
+class freq2note_simple_test(TestCase):
 
     def test_freq2note_above(self):
@@ -120,5 +125,5 @@
         self.assertEqual("A4", freq2note(439))
 
-class note2freq_simple_test(unittest.TestCase):
+class note2freq_simple_test(TestCase):
 
     def test_note2freq(self):
@@ -134,4 +139,4 @@
 
 if __name__ == '__main__':
-    import nose2
-    nose2.main()
+    from _tools import run_module_suite
+    run_module_suite()
Index: python/tests/test_notes.py
===================================================================
--- python/tests/test_notes.py	(revision 38214151f08818ad130112ddceaa2cb543f876bb)
+++ python/tests/test_notes.py	(revision adc6e02ecf2d72a827822e7d09b519434e66376a)
@@ -1,7 +1,10 @@
 #! /usr/bin/env python
 
-from unittest import main
 from numpy.testing import TestCase, assert_equal, assert_almost_equal
-from aubio import notes
+from aubio import notes, source
+import numpy as np
+from utils import list_all_sounds
+
+list_of_sounds = list_all_sounds('sounds')
 
 AUBIO_DEFAULT_NOTES_SILENCE = -70.
@@ -53,12 +56,7 @@
             self.o.set_release_drop(val)
 
-from .utils import list_all_sounds
-list_of_sounds = list_all_sounds('sounds')
-
 class aubio_notes_sinewave(TestCase):
 
     def analyze_file(self, filepath, samplerate=0):
-        from aubio import source
-        import numpy as np
         win_s = 512 # fft size
         hop_s = 256 # hop size
@@ -93,3 +91,4 @@
 
 if __name__ == '__main__':
+    from unittest import main
     main()
Index: python/tests/test_onset.py
===================================================================
--- python/tests/test_onset.py	(revision 38214151f08818ad130112ddceaa2cb543f876bb)
+++ python/tests/test_onset.py	(revision adc6e02ecf2d72a827822e7d09b519434e66376a)
@@ -1,5 +1,4 @@
 #! /usr/bin/env python
 
-from unittest import main
 from numpy.testing import TestCase, assert_equal, assert_almost_equal
 from aubio import onset, fvec
@@ -117,3 +116,4 @@
 
 if __name__ == '__main__':
+    from unittest import main
     main()
Index: python/tests/test_phasevoc.py
===================================================================
--- python/tests/test_phasevoc.py	(revision 38214151f08818ad130112ddceaa2cb543f876bb)
+++ python/tests/test_phasevoc.py	(revision adc6e02ecf2d72a827822e7d09b519434e66376a)
@@ -2,7 +2,6 @@
 
 from numpy.testing import TestCase, assert_equal, assert_array_less
+from _tools import parametrize
 from aubio import fvec, cvec, pvoc, float_type
-from nose2 import main
-from nose2.tools import params
 import numpy as np
 
@@ -19,5 +18,5 @@
     return np.random.rand(hop_s).astype(float_type) * 2. - 1.
 
-class aubio_pvoc_test_case(TestCase):
+class Test_aubio_pvoc_test_case(object):
     """ pvoc object test case """
 
@@ -66,5 +65,6 @@
             assert_equal ( t, 0.)
 
-    @params(
+    resynth_noise_args = "hop_s, ratio"
+    resynth_noise_values = [
             ( 256, 8),
             ( 256, 4),
@@ -88,5 +88,7 @@
             (8192, 4),
             (8192, 2),
-            )
+            ]
+
+    @parametrize(resynth_noise_args, resynth_noise_values)
     def test_resynth_steps_noise(self, hop_s, ratio):
         """ check the resynthesis of a random signal is correct """
@@ -94,5 +96,6 @@
         self.reconstruction(sigin, hop_s, ratio)
 
-    @params(
+    resynth_sine_args = "samplerate, hop_s, ratio, freq"
+    resynth_sine_values = [
             (44100,  256, 8,   441),
             (44100,  256, 4,  1203),
@@ -109,5 +112,7 @@
             (96000, 1024, 8, 47000),
             (96000, 1024, 8,    20),
-            )
+            ]
+
+    @parametrize(resynth_sine_args, resynth_sine_values)
     def test_resynth_steps_sine(self, samplerate, hop_s, ratio, freq):
         """ check the resynthesis of a sine is correct """
@@ -200,4 +205,4 @@
 
 if __name__ == '__main__':
+    from unittest import main
     main()
-
Index: python/tests/test_pitch.py
===================================================================
--- python/tests/test_pitch.py	(revision 38214151f08818ad130112ddceaa2cb543f876bb)
+++ python/tests/test_pitch.py	(revision adc6e02ecf2d72a827822e7d09b519434e66376a)
@@ -1,6 +1,5 @@
 #! /usr/bin/env python
 
-from unittest import TestCase, main
-from numpy.testing import assert_equal
+from numpy.testing import TestCase, assert_equal
 from numpy import sin, arange, mean, median, isnan, pi
 from aubio import fvec, pitch, freqtomidi, float_type
@@ -117,9 +116,10 @@
 for algo in pitch_algorithms:
     for mode in signal_modes:
-        test_method = create_test (algo, mode)
-        test_method.__name__ = 'test_pitch_%s_%d_%d_%dHz_sin_%.0f' % ( algo,
+        _test_method = create_test (algo, mode)
+        _test_method.__name__ = 'test_pitch_%s_%d_%d_%dHz_sin_%.0f' % ( algo,
                 mode[0], mode[1], mode[2], mode[3] )
-        setattr (aubio_pitch_Sinusoid, test_method.__name__, test_method)
+        setattr (aubio_pitch_Sinusoid, _test_method.__name__, _test_method)
 
 if __name__ == '__main__':
+    from unittest import main
     main()
Index: python/tests/test_sink.py
===================================================================
--- python/tests/test_sink.py	(revision 38214151f08818ad130112ddceaa2cb543f876bb)
+++ python/tests/test_sink.py	(revision adc6e02ecf2d72a827822e7d09b519434e66376a)
@@ -1,12 +1,8 @@
 #! /usr/bin/env python
 
-from nose2 import main
-from nose2.tools import params
 from numpy.testing import TestCase
 from aubio import fvec, source, sink
-from .utils import list_all_sounds, get_tmp_sink_path, del_tmp_sink_path
-
-import warnings
-warnings.filterwarnings('ignore', category=UserWarning, append=True)
+from utils import list_all_sounds, get_tmp_sink_path, del_tmp_sink_path
+from _tools import parametrize, skipTest, assert_raises
 
 list_of_sounds = list_all_sounds('sounds')
@@ -24,28 +20,24 @@
             all_params.append((hop_size, samplerate, soundfile))
 
-class aubio_sink_test_case(TestCase):
-
-    def setUp(self):
-        if not len(list_of_sounds):
-            self.skipTest('add some sound files in \'python/tests/sounds\'')
+class Test_aubio_sink(object):
 
     def test_wrong_filename(self):
-        with self.assertRaises(RuntimeError):
+        with assert_raises(RuntimeError):
             sink('')
 
     def test_wrong_samplerate(self):
-        with self.assertRaises(RuntimeError):
+        with assert_raises(RuntimeError):
             sink(get_tmp_sink_path(), -1)
 
     def test_wrong_samplerate_too_large(self):
-        with self.assertRaises(RuntimeError):
+        with assert_raises(RuntimeError):
             sink(get_tmp_sink_path(), 1536001, 2)
 
     def test_wrong_channels(self):
-        with self.assertRaises(RuntimeError):
+        with assert_raises(RuntimeError):
             sink(get_tmp_sink_path(), 44100, -1)
 
     def test_wrong_channels_too_large(self):
-        with self.assertRaises(RuntimeError):
+        with assert_raises(RuntimeError):
             sink(get_tmp_sink_path(), 44100, 202020)
 
@@ -67,11 +59,11 @@
         shutil.rmtree(tmpdir)
 
-    @params(*all_params)
+    @parametrize('hop_size, samplerate, path', all_params)
     def test_read_and_write(self, hop_size, samplerate, path):
-
         try:
             f = source(path, samplerate, hop_size)
         except RuntimeError as e:
-            self.skipTest('failed opening with hop_s = {:d}, samplerate = {:d} ({:s})'.format(hop_size, samplerate, str(e)))
+            err_msg = '{:s} (hop_s = {:d}, samplerate = {:d})'
+            skipTest(err_msg.format(str(e), hop_size, samplerate))
         if samplerate == 0: samplerate = f.samplerate
         sink_path = get_tmp_sink_path()
@@ -85,10 +77,11 @@
         del_tmp_sink_path(sink_path)
 
-    @params(*all_params)
+    @parametrize('hop_size, samplerate, path', all_params)
     def test_read_and_write_multi(self, hop_size, samplerate, path):
         try:
             f = source(path, samplerate, hop_size)
         except RuntimeError as e:
-            self.skipTest('failed opening with hop_s = {:d}, samplerate = {:d} ({:s})'.format(hop_size, samplerate, str(e)))
+            err_msg = '{:s} (hop_s = {:d}, samplerate = {:d})'
+            skipTest(err_msg.format(str(e), hop_size, samplerate))
         if samplerate == 0: samplerate = f.samplerate
         sink_path = get_tmp_sink_path()
@@ -126,3 +119,4 @@
 
 if __name__ == '__main__':
-    main()
+    from _tools import run_module_suite
+    run_module_suite()
Index: python/tests/test_slicing.py
===================================================================
--- python/tests/test_slicing.py	(revision 38214151f08818ad130112ddceaa2cb543f876bb)
+++ python/tests/test_slicing.py	(revision adc6e02ecf2d72a827822e7d09b519434e66376a)
@@ -1,9 +1,8 @@
 #! /usr/bin/env python
 
-from unittest import main
 from numpy.testing import TestCase, assert_equal
 from aubio import slice_source_at_stamps
-from .utils import count_files_in_directory, get_default_test_sound
-from .utils import count_samples_in_directory, count_samples_in_file
+from utils import count_files_in_directory, get_default_test_sound
+from utils import count_samples_in_directory, count_samples_in_file
 
 import tempfile
@@ -168,3 +167,4 @@
 
 if __name__ == '__main__':
+    from unittest import main
     main()
Index: python/tests/test_source.py
===================================================================
--- python/tests/test_source.py	(revision 38214151f08818ad130112ddceaa2cb543f876bb)
+++ python/tests/test_source.py	(revision adc6e02ecf2d72a827822e7d09b519434e66376a)
@@ -1,12 +1,10 @@
 #! /usr/bin/env python
 
-from nose2 import main
-from nose2.tools import params
+
 from numpy.testing import TestCase, assert_equal
 from aubio import source
-from .utils import list_all_sounds
-
-import warnings
-warnings.filterwarnings('ignore', category=UserWarning, append=True)
+from utils import list_all_sounds
+import unittest
+from _tools import parametrize, assert_raises, assert_equal, skipTest
 
 list_of_sounds = list_all_sounds('sounds')
@@ -14,5 +12,5 @@
 hop_sizes = [512, 1024, 64]
 
-path = None
+default_test_sound = len(list_of_sounds) and list_of_sounds[0] or None
 
 all_params = []
@@ -22,15 +20,11 @@
             all_params.append((hop_size, samplerate, soundfile))
 
+no_sounds_msg = "no test sounds, add some in 'python/tests/sounds/'!"
 
-class aubio_source_test_case_base(TestCase):
+_debug = False
 
-    def setUp(self):
-        if not len(list_of_sounds):
-            self.skipTest('add some sound files in \'python/tests/sounds\'')
-        self.default_test_sound = list_of_sounds[0]
+class Test_aubio_source_test_case(object):
 
-class aubio_source_test_case(aubio_source_test_case_base):
-
-    @params(*list_of_sounds)
+    @parametrize('filename', list_of_sounds)
     def test_close_file(self, filename):
         samplerate = 0 # use native samplerate
@@ -39,5 +33,5 @@
         f.close()
 
-    @params(*list_of_sounds)
+    @parametrize('filename', list_of_sounds)
     def test_close_file_twice(self, filename):
         samplerate = 0 # use native samplerate
@@ -47,5 +41,5 @@
         f.close()
 
-class aubio_source_read_test_case(aubio_source_test_case_base):
+class Test_aubio_source_read(object):
 
     def read_from_source(self, f):
@@ -57,26 +51,30 @@
                 assert_equal(samples[read:], 0)
                 break
-        #result_str = "read {:.2f}s ({:d} frames in {:d} blocks at {:d}Hz) from {:s}"
-        #result_params = total_frames / float(f.samplerate), total_frames, total_frames//f.hop_size, f.samplerate, f.uri
-        #print (result_str.format(*result_params))
+        if _debug:
+            result_str = "read {:.2f}s ({:d} frames"
+            result_str += " in {:d} blocks at {:d}Hz) from {:s}"
+            result_params = total_frames / float(f.samplerate), total_frames, \
+                    total_frames//f.hop_size, f.samplerate, f.uri
+            print (result_str.format(*result_params))
         return total_frames
 
-    @params(*all_params)
+    @parametrize('hop_size, samplerate, soundfile', all_params)
     def test_samplerate_hopsize(self, hop_size, samplerate, soundfile):
         try:
             f = source(soundfile, samplerate, hop_size)
         except RuntimeError as e:
-            self.skipTest('failed opening with hop_s = {:d}, samplerate = {:d} ({:s})'.format(hop_size, samplerate, str(e)))
+            err_msg = 'failed opening with hop_s={:d}, samplerate={:d} ({:s})'
+            skipTest(err_msg.format(hop_size, samplerate, str(e)))
         assert f.samplerate != 0
         read_frames = self.read_from_source(f)
         if 'f_' in soundfile and samplerate == 0:
             import re
-            f = re.compile('.*_\([0:9]*f\)_.*')
+            f = re.compile(r'.*_\([0:9]*f\)_.*')
             match_f = re.findall('([0-9]*)f_', soundfile)
             if len(match_f) == 1:
                 expected_frames = int(match_f[0])
-                self.assertEqual(expected_frames, read_frames)
+                assert_equal(expected_frames, read_frames)
 
-    @params(*list_of_sounds)
+    @parametrize('p', list_of_sounds)
     def test_samplerate_none(self, p):
         f = source(p)
@@ -84,5 +82,5 @@
         self.read_from_source(f)
 
-    @params(*list_of_sounds)
+    @parametrize('p', list_of_sounds)
     def test_samplerate_0(self, p):
         f = source(p, 0)
@@ -90,5 +88,5 @@
         self.read_from_source(f)
 
-    @params(*list_of_sounds)
+    @parametrize('p', list_of_sounds)
     def test_zero_hop_size(self, p):
         f = source(p, 0, 0)
@@ -97,5 +95,5 @@
         self.read_from_source(f)
 
-    @params(*list_of_sounds)
+    @parametrize('p', list_of_sounds)
     def test_seek_to_half(self, p):
         from random import randint
@@ -109,5 +107,5 @@
         assert a == b + c
 
-    @params(*list_of_sounds)
+    @parametrize('p', list_of_sounds)
     def test_duration(self, p):
         total_frames = 0
@@ -118,41 +116,42 @@
             total_frames += read
             if read < f.hop_size: break
-        self.assertEqual(duration, total_frames)
+        assert_equal (duration, total_frames)
 
 
-class aubio_source_test_wrong_params(TestCase):
+class Test_aubio_source_wrong_params(object):
 
     def test_wrong_file(self):
-        with self.assertRaises(RuntimeError):
+        with assert_raises(RuntimeError):
             source('path_to/unexisting file.mp3')
 
-class aubio_source_test_wrong_params_with_file(aubio_source_test_case_base):
+@unittest.skipIf(default_test_sound is None, no_sounds_msg)
+class Test_aubio_source_wrong_params_with_file(TestCase):
 
     def test_wrong_samplerate(self):
-        with self.assertRaises(ValueError):
-            source(self.default_test_sound, -1)
+        with assert_raises(ValueError):
+            source(default_test_sound, -1)
 
     def test_wrong_hop_size(self):
-        with self.assertRaises(ValueError):
-            source(self.default_test_sound, 0, -1)
+        with assert_raises(ValueError):
+            source(default_test_sound, 0, -1)
 
     def test_wrong_channels(self):
-        with self.assertRaises(ValueError):
-            source(self.default_test_sound, 0, 0, -1)
+        with assert_raises(ValueError):
+            source(default_test_sound, 0, 0, -1)
 
     def test_wrong_seek(self):
-        f = source(self.default_test_sound)
-        with self.assertRaises(ValueError):
+        f = source(default_test_sound)
+        with assert_raises(ValueError):
             f.seek(-1)
 
     def test_wrong_seek_too_large(self):
-        f = source(self.default_test_sound)
+        f = source(default_test_sound)
         try:
-            with self.assertRaises(ValueError):
+            with assert_raises(ValueError):
                 f.seek(f.duration + f.samplerate * 10)
-        except AssertionError:
-            self.skipTest('seeking after end of stream failed raising ValueError')
+        except:
+            skipTest('seeking after end of stream failed raising ValueError')
 
-class aubio_source_readmulti_test_case(aubio_source_read_test_case):
+class Test_aubio_source_readmulti(Test_aubio_source_read):
 
     def read_from_source(self, f):
@@ -164,13 +163,16 @@
                 assert_equal(samples[:,read:], 0)
                 break
-        #result_str = "read {:.2f}s ({:d} frames in {:d} channels and {:d} blocks at {:d}Hz) from {:s}"
-        #result_params = total_frames / float(f.samplerate), total_frames, f.channels, int(total_frames/f.hop_size), f.samplerate, f.uri
-        #print (result_str.format(*result_params))
+        if _debug:
+            result_str = "read {:.2f}s ({:d} frames in {:d} channels"
+            result_str += " and {:d} blocks at {:d}Hz) from {:s}"
+            result_params = total_frames / float(f.samplerate), total_frames, \
+                    f.channels, int(total_frames/f.hop_size), \
+                    f.samplerate, f.uri
+            print (result_str.format(*result_params))
         return total_frames
 
-class aubio_source_with(aubio_source_test_case_base):
+class Test_aubio_source_with(object):
 
-    #@params(*list_of_sounds)
-    @params(*list_of_sounds)
+    @parametrize('filename', list_of_sounds)
     def test_read_from_mono(self, filename):
         total_frames = 0
@@ -186,3 +188,4 @@
 
 if __name__ == '__main__':
-    main()
+    from _tools import run_module_suite
+    run_module_suite()
Index: python/tests/test_source_channels.py
===================================================================
--- python/tests/test_source_channels.py	(revision 38214151f08818ad130112ddceaa2cb543f876bb)
+++ python/tests/test_source_channels.py	(revision adc6e02ecf2d72a827822e7d09b519434e66376a)
@@ -9,5 +9,5 @@
 import numpy as np
 from numpy.testing import assert_equal
-from .utils import get_tmp_sink_path
+from utils import get_tmp_sink_path
 
 class aubio_source_test_case(unittest.TestCase):
Index: python/tests/test_specdesc.py
===================================================================
--- python/tests/test_specdesc.py	(revision 38214151f08818ad130112ddceaa2cb543f876bb)
+++ python/tests/test_specdesc.py	(revision adc6e02ecf2d72a827822e7d09b519434e66376a)
@@ -1,5 +1,4 @@
 #! /usr/bin/env python
 
-from unittest import main
 from numpy.testing import TestCase, assert_equal, assert_almost_equal
 from numpy import random, arange, log, zeros
@@ -230,3 +229,4 @@
 
 if __name__ == '__main__':
+    from unittest import main
     main()
Index: python/tests/test_zero_crossing_rate.py
===================================================================
--- python/tests/test_zero_crossing_rate.py	(revision 38214151f08818ad130112ddceaa2cb543f876bb)
+++ python/tests/test_zero_crossing_rate.py	(revision adc6e02ecf2d72a827822e7d09b519434e66376a)
@@ -1,5 +1,4 @@
 #! /usr/bin/env python
 
-from unittest import main
 from numpy.testing import TestCase
 from aubio import fvec, zero_crossing_rate
@@ -44,3 +43,4 @@
 
 if __name__ == '__main__':
+    from unittest import main
     main()
Index: python/tests/utils.py
===================================================================
--- python/tests/utils.py	(revision 38214151f08818ad130112ddceaa2cb543f876bb)
+++ python/tests/utils.py	(revision adc6e02ecf2d72a827822e7d09b519434e66376a)
@@ -9,9 +9,6 @@
 
 def array_from_text_file(filename, dtype = 'float'):
-    filename = os.path.join(os.path.dirname(__file__), filename)
-    with open(filename) as f:
-        lines = f.readlines()
-    return np.array([line.split() for line in lines],
-            dtype = dtype)
+    realpathname = os.path.join(os.path.dirname(__file__), filename)
+    return np.loadtxt(realpathname, dtype = dtype)
 
 def list_all_sounds(rel_dir):
@@ -39,11 +36,8 @@
         os.unlink(path)
     except WindowsError as e:
-        print("deleting {:s} failed ({:s}), reopening".format(path, repr(e)))
-        with open(path, 'wb') as f:
-            f.close()
-        try:
-            os.unlink(path)
-        except WindowsError as f:
-            print("deleting {:s} failed ({:s}), aborting".format(path, repr(e)))
+        # removing the temporary directory sometimes fails on windows
+        import warnings
+        errmsg = "failed deleting temporary file {:s} ({:s})"
+        warnings.warn(UserWarning(errmsg.format(path, repr(e))))
 
 def array_from_yaml_file(filename):
