diff --git a/.github/issue_template.md b/.github/issue_template.md index 8bc43705..15aac278 100644 --- a/.github/issue_template.md +++ b/.github/issue_template.md @@ -29,6 +29,6 @@ * Version of spyder-unittest plugin: * Installation method for Spyder and the unittest plugin: Anaconda / pip / ... * Python version: -* Testing framework used: nose / py.test / unittest +* Testing framework used: nose2 / pytest / unittest * Testing framework version: * Operating system: diff --git a/README.md b/README.md index 304d76db..d0930b5f 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ Spyder-unittest is a plugin that integrates popular unit test frameworks with Spyder, allowing you to run test suites and view the results in the IDE. The plugin supports the `unittest` module in the Python standard library -as well as the `pytest` and `nose` testing frameworks. +as well as the `pytest` and `nose2` testing frameworks. Support for `pytest` is most complete at the moment. @@ -76,10 +76,10 @@ The plugin has the following dependencies: * [spyder](https://github.com/spyder-ide/spyder) (obviously), at least version 4.0 * [lxml](http://lxml.de/) * the testing framework that you will be using: [pytest](https://pytest.org) - and/or [nose](https://nose.readthedocs.io) + and/or [nose2](https://docs.nose2.io) In order to run the tests distributed with this plugin, you need -[nose](https://nose.readthedocs.io), [pytest](https://pytest.org) +[nose2](https://docs.nose2.io), [pytest](https://pytest.org) and [pytest-qt](https://github.com/pytest-dev/pytest-qt). If you use Python 2, you also need [mock](https://github.com/testing-cabal/mock). diff --git a/requirements/tests.txt b/requirements/tests.txt index 00e208ac..ed20f926 100644 --- a/requirements/tests.txt +++ b/requirements/tests.txt @@ -1,5 +1,5 @@ flaky -nose +nose2 pytest>=5 pytest-cov pytest-qt diff --git a/setup.py b/setup.py index 6fa09873..e4401799 100644 --- a/setup.py +++ b/setup.py @@ -47,7 +47,7 @@ def get_package_data(name, extlist): frameworks. It allows you to run tests and view the results. The plugin supports the `unittest` framework in the Python -standard library and the `pytest` and `nose` testing frameworks. +standard library and the `pytest` and `nose2` testing frameworks. """ setup( diff --git a/spyder_unittest/backend/noserunner.py b/spyder_unittest/backend/nose2runner.py similarity index 90% rename from spyder_unittest/backend/noserunner.py rename to spyder_unittest/backend/nose2runner.py index 22d0a954..5ad8dd82 100644 --- a/spyder_unittest/backend/noserunner.py +++ b/spyder_unittest/backend/nose2runner.py @@ -19,18 +19,18 @@ _ = gettext.gettext -class NoseRunner(RunnerBase): +class Nose2Runner(RunnerBase): """Class for running tests within Nose framework.""" - module = 'nose' - name = 'nose' + module = 'nose2' + name = 'nose2' def create_argument_list(self, config, cov_path): """Create argument list for testing process.""" return [ - '-m', self.module, '--with-xunit', - '--xunit-file={}'.format(self.resultfilename), - ] + '-m', self.module, '--plugin=nose2.plugins.junitxml', + '--junit-xml', '--junit-xml-path={}'.format(self.resultfilename) + ] def finished(self): """Called when the unit test process has finished.""" @@ -81,7 +81,7 @@ def load_data(self): message = type_ if child.text: extras.append(child.text) - elif child.tag in ('system-out', 'system-err'): + elif child.tag in ('system-out', 'system-err') and child.text: if child.tag == 'system-out': heading = _('Captured stdout') else: diff --git a/spyder_unittest/backend/tests/test_nose2runner.py b/spyder_unittest/backend/tests/test_nose2runner.py new file mode 100644 index 00000000..bc1eec57 --- /dev/null +++ b/spyder_unittest/backend/tests/test_nose2runner.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +# +# Copyright © 2013 Spyder Project Contributors +# Licensed under the terms of the MIT License +# (see LICENSE.txt for details) +"""Tests for nose2runner.py""" + +# Local imports +from spyder_unittest.backend.nose2runner import Nose2Runner +from spyder_unittest.backend.runnerbase import Category + + +def test_nose2runner_load_data(tmpdir): + result_file = tmpdir.join('results') + result_txt = """ + + + + + + text + + +""" + result_file.write(result_txt) + runner = Nose2Runner(None, result_file.strpath) + results = runner.load_data() + assert len(results) == 2 + + assert results[0].category == Category.OK + assert results[0].status == 'ok' + assert results[0].name == 'test_foo.test1' + assert results[0].message == '' + assert results[0].time == 0.04 + assert results[0].extra_text == [] + + assert results[1].category == Category.FAIL + assert results[1].status == 'failure' + assert results[1].name == 'test_foo.test2' + assert results[1].message == 'test failure' + assert results[1].time == 0.01 + assert results[1].extra_text == ['text'] diff --git a/spyder_unittest/backend/tests/test_noserunner.py b/spyder_unittest/backend/tests/test_noserunner.py deleted file mode 100644 index af594914..00000000 --- a/spyder_unittest/backend/tests/test_noserunner.py +++ /dev/null @@ -1,76 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright © 2013 Spyder Project Contributors -# Licensed under the terms of the MIT License -# (see LICENSE.txt for details) -"""Tests for noserunner.py""" - -# Local imports -from spyder_unittest.backend.noserunner import NoseRunner -from spyder_unittest.backend.runnerbase import Category - - -def test_noserunner_load_data(tmpdir): - result_file = tmpdir.join('results') - result_txt = """ - - - - text - - - text2 -""" - result_file.write(result_txt) - runner = NoseRunner(None, result_file.strpath) - results = runner.load_data() - assert len(results) == 3 - - assert results[0].category == Category.OK - assert results[0].status == 'ok' - assert results[0].name == 'test_foo.test1' - assert results[0].message == '' - assert results[0].time == 0.04 - assert results[0].extra_text == [] - - assert results[1].category == Category.FAIL - assert results[1].status == 'failure' - assert results[1].name == 'test_foo.test2' - assert results[1].message == 'failure message' - assert results[1].time == 0.01 - assert results[1].extra_text == ['text'] - - assert results[2].category == Category.SKIP - assert results[2].status == 'skipped' - assert results[2].name == 'test_foo.test3' - assert results[2].message == 'skip message' - assert results[2].time == 0.05 - assert results[2].extra_text == ['text2'] - - -def test_noserunner_load_data_failing_test_with_stdout(tmpdir): - result_file = tmpdir.join('results') - result_txt = """ - - -text -stdout text -""" - result_file.write(result_txt) - runner = NoseRunner(None, result_file.strpath) - results = runner.load_data() - assert results[0].extra_text == ['text', '', '----- Captured stdout -----', 'stdout text'] - - -def test_noserunner_load_data_passing_test_with_stdout(tmpdir): - result_file = tmpdir.join('results') - result_txt = """ - - -stdout text -""" - result_file.write(result_txt) - runner = NoseRunner(None, result_file.strpath) - results = runner.load_data() - assert results[0].extra_text == ['----- Captured stdout -----', 'stdout text'] - diff --git a/spyder_unittest/backend/workers/print_versions.py b/spyder_unittest/backend/workers/print_versions.py index 6d049c27..0bc5809c 100644 --- a/spyder_unittest/backend/workers/print_versions.py +++ b/spyder_unittest/backend/workers/print_versions.py @@ -37,23 +37,21 @@ def pytest_cmdline_main(self, config): 'plugins': plugins} -def get_nose_info(): - """Return information about nose.""" - from pkg_resources import iter_entry_points +def get_nose2_info(): + """ + Return information about nose2. + + This only returns the version of nose2. The function does not gather any + information about plugins. + """ try: - import nose + import nose2 except ImportError: return {'available': False} - plugins = {} - for entry_point, _ in (nose.plugins.manager.EntryPointPluginManager - .entry_points): - for ep in iter_entry_points(entry_point): - plugins[ep.dist.project_name] = ep.dist.version - return {'available': True, - 'version': nose.__version__, - 'plugins': plugins} + 'version': nose2.__version__, + 'plugins': {}} def get_unittest_info(): @@ -75,11 +73,11 @@ def get_all_info(): Information is returned as a dictionary like the following: {'pytest': {'available': True, 'version': '7.1.1', 'plugins': {'flaky': '3.7.0', 'pytest-mock': '3.6.1'}}, - 'nose': {'available': False}, + 'nose2': {'available': False}, 'unittest': {'available': True, 'version': '3.10.5', 'plugins': {}}} """ return {'pytest': get_pytest_info(), - 'nose': get_nose_info(), + 'nose2': get_nose2_info(), 'unittest': get_unittest_info()} diff --git a/spyder_unittest/backend/workers/tests/test_print_versions.py b/spyder_unittest/backend/workers/tests/test_print_versions.py index 06977931..3fd4ddb2 100644 --- a/spyder_unittest/backend/workers/tests/test_print_versions.py +++ b/spyder_unittest/backend/workers/tests/test_print_versions.py @@ -6,7 +6,7 @@ """Tests for print_versions.py""" from spyder_unittest.backend.workers.print_versions import ( - get_nose_info, get_pytest_info, get_unittest_info) + get_nose2_info, get_pytest_info, get_unittest_info) def test_get_pytest_info_without_plugins(monkeypatch): @@ -37,30 +37,11 @@ def test_get_pytest_info_with_plugins(monkeypatch): assert get_pytest_info() == expected -def test_get_nose_info_without_plugins(monkeypatch): - import nose - import pkg_resources - monkeypatch.setattr(nose, '__version__', '1.2.3') - monkeypatch.setattr(pkg_resources, 'iter_entry_points', lambda x: ()) +def test_get_nose2_info(monkeypatch): + import nose2 + monkeypatch.setattr(nose2, '__version__', '1.2.3') expected = {'available': True, 'version': '1.2.3', 'plugins': {}} - assert get_nose_info() == expected - - -def test_get_nose_info_with_plugins(monkeypatch): - import nose - import pkg_resources - monkeypatch.setattr(nose, '__version__', '1.2.3') - dist = pkg_resources.Distribution(project_name='myPlugin', - version='4.5.6') - ep = pkg_resources.EntryPoint('name', 'module_name', dist=dist) - monkeypatch.setattr(pkg_resources, - 'iter_entry_points', - lambda ept: (x for x in (ep,) if ept == nose.plugins - .manager.EntryPointPluginManager - .entry_points[0][0])) - expected = {'available': True, 'version': '1.2.3', - 'plugins': {'myPlugin': '4.5.6'}} - assert get_nose_info() == expected + assert get_nose2_info() == expected def test_get_unittest_imfo(monkeypatch): diff --git a/spyder_unittest/widgets/tests/test_unittestgui.py b/spyder_unittest/widgets/tests/test_unittestgui.py index cecf1284..946f21d6 100644 --- a/spyder_unittest/widgets/tests/test_unittestgui.py +++ b/spyder_unittest/widgets/tests/test_unittestgui.py @@ -151,15 +151,15 @@ def test_unittestwidget_process_finished_abnormally_status_label(widget): expected_text = '{}'.format('Test process exited abnormally') assert widget.status_label.text() == expected_text -@pytest.mark.parametrize('framework', ['pytest', 'nose']) +@pytest.mark.parametrize('framework', ['pytest', 'nose2']) def test_run_tests_and_display_results(qtbot, widget, tmpdir, monkeypatch, framework): """Basic integration test.""" os.chdir(tmpdir.strpath) testfilename = tmpdir.join('test_foo.py').strpath with open(testfilename, 'w') as f: - f.write("def test_ok(): assert 1+1 == 2\n" - "def test_fail(): assert 1+1 == 3\n") + f.write("def test_fail(): assert 1+1 == 3\n" + "def test_ok(): assert 1+1 == 2\n") MockQMessageBox = Mock() monkeypatch.setattr('spyder_unittest.widgets.unittestgui.QMessageBox', @@ -173,14 +173,14 @@ def test_run_tests_and_display_results(qtbot, widget, tmpdir, monkeypatch, frame model = widget.testdatamodel assert model.rowCount() == 2 assert model.index(0, 0).data( - Qt.DisplayRole) == 'ok' if framework == 'nose' else 'passed' - assert model.index(0, 1).data(Qt.DisplayRole) == 't.test_ok' - assert model.index(0, 1).data(Qt.ToolTipRole) == 'test_foo.test_ok' - assert model.index(0, 2).data(Qt.DisplayRole) == '' + Qt.DisplayRole) == 'failure' if framework == 'nose2' else 'failed' + assert model.index(0, 1).data(Qt.DisplayRole) == 't.test_fail' + assert model.index(0, 1).data(Qt.ToolTipRole) == 'test_foo.test_fail' assert model.index(1, 0).data( - Qt.DisplayRole) == 'failure' if framework == 'nose' else 'failed' - assert model.index(1, 1).data(Qt.DisplayRole) == 't.test_fail' - assert model.index(1, 1).data(Qt.ToolTipRole) == 'test_foo.test_fail' + Qt.DisplayRole) == 'ok' if framework == 'nose2' else 'passed' + assert model.index(1, 1).data(Qt.DisplayRole) == 't.test_ok' + assert model.index(1, 1).data(Qt.ToolTipRole) == 'test_foo.test_ok' + assert model.index(1, 2).data(Qt.DisplayRole) == '' def test_run_tests_using_unittest_and_display_results( @@ -215,7 +215,7 @@ def test_run_tests_using_unittest_and_display_results( assert model.index(1, 1).data(Qt.ToolTipRole) == 'test_foo.MyTest.test_ok' assert model.index(1, 2).data(Qt.DisplayRole) == '' -@pytest.mark.parametrize('framework', ['unittest', 'pytest', 'nose']) +@pytest.mark.parametrize('framework', ['unittest', 'pytest', 'nose2']) def test_run_with_no_tests_discovered_and_display_results( qtbot, widget, tmpdir, monkeypatch, framework): """Basic integration test.""" diff --git a/spyder_unittest/widgets/unittestgui.py b/spyder_unittest/widgets/unittestgui.py index b81e5f69..3125b8ef 100644 --- a/spyder_unittest/widgets/unittestgui.py +++ b/spyder_unittest/widgets/unittestgui.py @@ -22,7 +22,7 @@ # Local imports from spyder_unittest.backend.frameworkregistry import FrameworkRegistry -from spyder_unittest.backend.noserunner import NoseRunner +from spyder_unittest.backend.nose2runner import Nose2Runner from spyder_unittest.backend.pytestrunner import PyTestRunner from spyder_unittest.backend.runnerbase import Category, TestResult from spyder_unittest.backend.unittestrunner import UnittestRunner @@ -37,7 +37,7 @@ _ = gettext.gettext # Supported testing frameworks -FRAMEWORKS = {NoseRunner, PyTestRunner, UnittestRunner} +FRAMEWORKS = {Nose2Runner, PyTestRunner, UnittestRunner} class UnitTestWidgetActions: