Repository: beetbox/beets

Issue:
nose (1.x) is mostly unmaintained
https://nose.readthedocs.io/en/latest/#note-to-users

> Nose has been in maintenance mode for the past several years and will likely cease without a new person/team to take over maintainership. New projects should consider using Nose2, py.test, or just plain unittest/unittest2.

Are there any benefits to nose2 over py.test?

The community seems to mostly use py.test (if not using unittest) at this point.  I'd like to suggest making it our default test runner.    We can decide on adapting our tests to py.test separately.



Discussion:
Thanks for pointing this out. This confirms our long-standing unwritten policy of not depending on nose! Test _runners_ come and go, but plain `unittest` is eternal. 😇 

I don't have much experience with py.test (and zero with nose2), so I don't have a strong opinion about which to recommend.


Resolving patch:
diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -109,24 +109,33 @@ def build_manpages():
         ['colorama'] if (sys.platform == 'win32') else []
     ),
 
-    tests_require=[
-        'beautifulsoup4',
-        'flask',
-        'mock',
-        'pylast',
-        'rarfile',
-        'responses',
-        'pyxdg',
-        'python-mpd2',
-        'discogs-client',
-        'requests_oauthlib'
-    ] + (
-        # Tests for the thumbnails plugin need pathlib on Python 2 too.
-        ['pathlib'] if (sys.version_info < (3, 4, 0)) else []
-    ),
-
-    # Plugin (optional) dependencies:
     extras_require={
+        'test': [
+            'beautifulsoup4',
+            'coverage',
+            'discogs-client',
+            'flask',
+            'mock',
+            'pylast',
+            'pytest',
+            'python-mpd2',
+            'pyxdg',
+            'rarfile',
+            'responses>=0.3.0',
+            'requests_oauthlib',
+        ] + (
+            # Tests for the thumbnails plugin need pathlib on Python 2 too.
+            ['pathlib'] if (sys.version_info < (3, 4, 0)) else []
+            ),
+        'lint': [
+            'flake8',
+            'flake8-blind-except',
+            'flake8-coding',
+            'flake8-future-import',
+            'pep8-naming',
+        ],
+
+        # Plugin (optional) dependencies:
         'absubmit': ['requests'],
         'fetchart': ['requests', 'Pillow'],
         'embedart': ['Pillow'],


Test patch:
diff --git a/.github/workflows/integration_test.yaml b/.github/workflows/integration_test.yaml
--- a/.github/workflows/integration_test.yaml
+++ b/.github/workflows/integration_test.yaml
@@ -5,28 +5,23 @@ on:
 jobs:
   test integration:
     runs-on: ubuntu-latest
-    strategy:
-      python-version: 3.8
+
     env:
-      NOSE_SHOW_SKIPPED: 1
-      PY_COLOR: 1
-      INTEGRATION_TEST: 1
+      PY_COLORS: 1
+
     steps:
       - uses: actions/checkout@v2
+
       - name: Set up Python 3.8
         uses: actions/setup-python@v2
         with:
           python-version: 3.8
-      - uses: actions/cache@v1
-        with:
-          path: ~/.cache/pip
-          key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
-          restore-keys: |
-            ${{ runner.os }}-pip-
+
       - name: Install base dependencies
         run: |
           python -m pip install --upgrade pip
-          pip install tox
+          python -m pip install tox sphinx
+
       - name: Test with tox
         run: |
-          tox -e py38-test
\ No newline at end of file
+          tox -e int
\ No newline at end of file
diff --git a/test/_common.py b/test/_common.py
--- a/test/_common.py
+++ b/test/_common.py
@@ -44,7 +44,7 @@
 RSRC = util.bytestring_path(os.path.join(os.path.dirname(__file__), 'rsrc'))
 PLUGINPATH = os.path.join(os.path.dirname(__file__), 'rsrc', 'beetsplug')
 
-# Propagate to root logger so nosetest can capture it
+# Propagate to root logger so the test runner can capture it
 log = logging.getLogger('beets')
 log.propagate = True
 log.setLevel(logging.DEBUG)
diff --git a/test/test_parentwork.py b/test/test_parentwork.py
--- a/test/test_parentwork.py
+++ b/test/test_parentwork.py
@@ -17,6 +17,7 @@
 
 from __future__ import division, absolute_import, print_function
 
+import os
 import unittest
 from test.helper import TestHelper
 
@@ -34,6 +35,9 @@ def tearDown(self):
         self.unload_plugins()
         self.teardown_beets()
 
+    @unittest.skipUnless(
+        os.environ.get('INTEGRATION_TEST', '0') == '1',
+        'integration testing not enabled')
     def test_normal_case(self):
         item = Item(path='/file',
                     mb_workid=u'e27bda6e-531e-36d3-9cd7-b8ebc18e8c53')
@@ -45,6 +49,9 @@ def test_normal_case(self):
         self.assertEqual(item['mb_parentworkid'],
                          u'32c8943f-1b27-3a23-8660-4567f4847c94')
 
+    @unittest.skipUnless(
+        os.environ.get('INTEGRATION_TEST', '0') == '1',
+        'integration testing not enabled')
     def test_force(self):
         self.config['parentwork']['force'] = True
         item = Item(path='/file',
@@ -58,6 +65,9 @@ def test_force(self):
         self.assertEqual(item['mb_parentworkid'],
                          u'32c8943f-1b27-3a23-8660-4567f4847c94')
 
+    @unittest.skipUnless(
+        os.environ.get('INTEGRATION_TEST', '0') == '1',
+        'integration testing not enabled')
     def test_no_force(self):
         self.config['parentwork']['force'] = True
         item = Item(path='/file', mb_workid=u'e27bda6e-531e-36d3-9cd7-\
@@ -72,6 +82,9 @@ def test_no_force(self):
     # test different cases, still with Matthew Passion Ouverture or Mozart
     # requiem
 
+    @unittest.skipUnless(
+        os.environ.get('INTEGRATION_TEST', '0') == '1',
+        'integration testing not enabled')
     def test_direct_parent_work(self):
         mb_workid = u'2e4a3668-458d-3b2a-8be2-0b08e0d8243a'
         self.assertEqual(u'f04b42df-7251-4d86-a5ee-67cfa49580d1',