GithubHelp home page GithubHelp logo

pytest-dev / unittest2pytest Goto Github PK

View Code? Open in Web Editor NEW
125.0 12.0 30.0 173 KB

helps rewriting Python `unittest` test-cases into `pytest` test-cases

License: GNU General Public License v3.0

Python 100.00%

unittest2pytest's Introduction

unittest2pytest

Helps converting unittest test-cases to pytest

Author: Hartmut Goebel <[email protected]>
Version: 0.5.dev0
Copyright: 2015-2019 by Hartmut Goebel
Licence:GNU Public Licence v3 or later (GPLv3+)
Homepage:https://github.com/pytest-dev/unittest2pytest
See Build Status on GitHub Actions

unittest2pytest is a tool that helps rewriting Python unittest test-cases into pytest test-cases.

In contrast to other similar tools, this unittest2pytest

  • handles keyword arguments,
  • handles single-line test-cases and several tests on one line,
  • uses context-handlers where appropriate.

This is done by using lib2to3 and Python's mighty inspect module.

Installation

To install unittest2pytest, simply run:

pip install unittest2pytest

Usage

To print a diff of changes that unittest2pytest will make against a particular source file or directory:

unittest2pytest source_folder

To have those changes written to the files:

unittest2pytest -w source_folder

To have those changes written to another directory:

unittest2pytest -w source_folder --output-dir /some/where/else

By default, this will create backup files for each file that will be changed. You can add the -n option to not create the backups. Please do not do this if you are not using a version control system.

For more options about running particular fixers, run unittest2pytest --help or read the lib2to3 documentation. This tool is built on top of that one.

Fixes

A list of the available fixers can be found with the following:

$ unittest2pytest -l
Available transformations for the -f/--fix option:
remove_class
self_assert

Note: if your tests use the context managers with self.assertRaises or with self.assertWarns, they will be transformed to pytest.raises or pytest.warns appropriately, but because the semantics are different, any use of the output value from the context managers (e.g. the x in with pytest.raises(ValueError) as x:) will be wrong and will require manual adjustment after the fact.

unittest2pytest's People

Contributors

adamchainz avatar bdice avatar cclauss avatar gqmelo avatar graingert avatar htgoebel avatar hugovk avatar janosh avatar jck avatar jdufresne avatar joshthoward avatar nicoddemus avatar pombredanne avatar ronnypfannschmidt avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

unittest2pytest's Issues

Installer issue

I am running into the following issue trying to install the package from the master branch.

$ unittest2pytest -l
Available transformations for the -f/--fix option:
Traceback (most recent call last):
  File "/Users/jhoward/homebrew/bin/unittest2pytest", line 11, in <module>
    load_entry_point('unittest2pytest==0.3', 'console_scripts', 'unittest2pytest')()
  File "build/bdist.macosx-10.12-x86_64/egg/unittest2pytest/__main__.py", line 30, in main
  File "/Users/jhoward/homebrew/Cellar/python/2.7.13/Frameworks/Python.framework/Versions/2.7/lib/python2.7/lib2to3/main.py", line 199, in main
    for fixname in refactor.get_all_fix_names(fixer_pkg):
  File "/Users/jhoward/homebrew/Cellar/python/2.7.13/Frameworks/Python.framework/Versions/2.7/lib/python2.7/lib2to3/refactor.py", line 38, in get_all_fix_names
    for name in sorted(os.listdir(fixer_dir)):
OSError: [Errno 20] Not a directory: '/Users/jhoward/homebrew/lib/python2.7/site-packages/unittest2pytest-0.3-py2.7.egg/unittest2pytest/fixes'

doesn't handle self.assertDictContainsSubset

If you have a unittest that contains an assertDictContainsSubset, unittest2pytest will happily ignore/skip over it.

class ExampleTest(unittest.TestCase):
    def test_dict_subset():
        subset = {'a': 1}
        superset = {'a': 1, 'b': 2}
        self.assertDictContainsSubset(subset, superset)

This can be replaced with the following:

def test_dict_subset():
    subset = {'a': 1}
    superset = {'a': 1, 'b': 2}
    assert dict(superset, **subset) == superset

Undefined variable `pattern` when converting self.raisesRegexp

When running the following to convert asserts:

unittest2pytest -f self_assert test/test_foo.py

I consistently get the following output with the below test:

     def test_foo(self):
-        with self.assertRaisesRegexp(ValueError, 'Invalid'):
+        with pytest.raises(ValueError) as excinfo:
             raise ValueError('Invalid')
+        assert re.search(pattern, excinfo.value)

Notice the variable pattern which isn't defined anywhere. This is with unittest2pytest==0.3

Also, it would be nicer if it didn't use re.search and just use the match kwarg to raises. e.g.

     def test_foo(self):
-        with self.assertRaisesRegexp(ValueError, 'Invalid'):
+        with pytest.raises(ValueError, match='Invalid'):
             raise ValueError('Invalid')

Transformations do not account for order of operations

Example input:

class MyTest(unittest.TestCase):
    def test_thing(self):
        self.assertEqual(my_func(), expected_string() or None)

Output:

@@ -1,3 +1,3 @@
 class MyTest(unittest.TestCase):
     def test_thing(self):
-        self.assertEqual(my_func(), expected_value() or None)
+        assert my_func() == expected_value() or None

Due to Python's order of operations, this changes the semantics of the assert. The or operation now happens after the == operation.

Expected output:

@@ -1,3 +1,3 @@
 class MyTest(unittest.TestCase):
     def test_thing(self):
-        self.assertEqual(my_func(), expected_value() or None)
+        assert my_func() == (expected_value() or None)

This came up in a real life project:

https://github.com/python-pillow/Pillow/blob/7bf5246b93cc89cfb9d6cca78c4719a943b10585/Tests/test_file_jpeg.py#L125

Rewrite function names from camelCase to snake_case

When writing unittest.py tests, often the test methods are camel-cased, e.g. def testThing(self): .... With pytest, the usual convention is snake_case (as per pep8), i.e. def test_thing():.

It'd be nice if unittest2pytest could rename the functions accordingly.

Consider linking to `pytestify` from here

i love pytest, and did a few large scale unittest -> pytest conversions at work. hurrah!

while unittest2pytest is awesome, it was missing a few features. i was going to diff those in, but using lib2to3 felt a bit less straightforward than ast / tokenize. So i made https://github.com/dannysepler/pytestify

just curious if you would consider linking pytestify as an alternative to unittest2pytest (since the last release here was a few years ago and pytestify is under active development)

  • alternatively it could be helpful just to look at pytestify and give notes!

(feel free to close if irrelevant, of course)

Missing necessary parentheses and not allowing equality

In my work on certbot/certbot#9585, unittest2pytest rewrote these lines:

self.assertAlmostEqual(mock_sleep.call_args_list[1][0][0], interval - 1, delta=1)
self.assertAlmostEqual(mock_sleep.call_args_list[2][0][0], interval/2 - 1, delta=1)

as

assert abs(mock_sleep.call_args_list[1][0][0]-interval - 1) < 1
assert abs(mock_sleep.call_args_list[2][0][0]-interval/2 - 1) < 1

This is incorrect for two reasons:

  1. Parentheses should be added around the 2nd argument to assertAlmostEqual (or the - 1 changed to + 1).
  2. assertAlmostEqual doesn't error if the difference between the first two arguments equals the delta argument.

(With that said, I just ran unittest2pytest on 28k lines of real test code and this was the only problem I encountered other than the pytest.raises limitation described at the bottom of your README. Nice work!)

assertRaisesRegex should convert the regex pattern to the new assert

Example input:

class MyTest(unittest.TestCase):
    def test_thing(self):
        with self.assertRaisesRegex(Exception, "My exception message"):
            my_func()

Output:

@@ -1,4 +1,7 @@
+import pytest
+import re
 class MyTest(unittest.TestCase):
     def test_thing(self):
-        with self.assertRaisesRegex(Exception, "My exception message"):
+        with pytest.raises(Exception) as excinfo:
             my_func()
+        assert re.search(pattern, excinfo.value)

In the output, pattern should be "My exception message".

This came up in a real life project:

https://github.com/python-pillow/Pillow/blob/7bf5246b93cc89cfb9d6cca78c4719a943b10585/Tests/test_color_lut.py#L40

multi line assertion function replaced with invalid code

Consider the following code:

import unittest


class Tests(unittest.TestCase):
    def test_1(self):
        self.assertIn('multline line assert',
                      'multline line assert')

This is the output:

import unittest


class Tests(unittest.TestCase):
    def test_1(self):
        assert 'multline line assert' in
                      'multline line assert'

This is the correct output:

import unittest


class Tests(unittest.TestCase):
    def test_1(self):
        assert 'multline line assert' in 'multline line assert'

ParseError: bad input: type=22, value='=', context=('', (62, 54))

❯ unittest2pytest git_machete/tests/functional/test_machete.py

RefactoringTool: Can't parse git_machete/tests/functional/test_machete.py: ParseError: bad input: type=22, value='=', context=('', (62, 54))
RefactoringTool: No files need to be modified.
RefactoringTool: There was 1 error:
RefactoringTool: Can't parse git_machete/tests/functional/test_machete.py: ParseError: bad input: type=22, value='=', context=('', (62, 54))

Class converted to invalid code

One of my test files (https://github.com/nedbat/coveragepy/blob/coverage-5.4/tests/test_testing.py) was converted into invalid code. I don't know what it is about that file that caused it, though it is a bit twisty: it's a test of test assert helper methods.

Here is a stripped-down file that shows the same behavior:

import pytest

from coverage.backunittest import TestCase

class TestingTest(TestCase):
    """Tests of helper methods on `backunittest.TestCase`."""

    def test_assert_count_equal(self):
        self.assertCountEqual(set(), set())
        with self.assertRaises(AssertionError):
            self.assertCountEqual({1,2,3}, set())
        with self.assertRaises(AssertionError):
            self.assertCountEqual({1,2,3}, {4,5,6})

Running unittest2pytest -w test_testing.py, the file becomes:

import pytest

from coverage.backunittest import TestCase
"""Tests of helper methods on `backunittest.TestCase`."""
deftest_assert_count_equal(self):
    self.assertCountEqual(set(), set())
    with pytest.raises(AssertionError):
        self.assertCountEqual({1,2,3}, set())
        withpytest.raises(AssertionError):
        self.assertCountEqual({1,2,3}, {4,5,6})

assertRaises used as an expression is converted to a statement, resulting in a syntax error

Example input:

class MyTest(unittest.TestCase):
    def test_thing(self):
        self.my_custom_assert(self.assertRaises(IOError, my_func))

Output

@@ -1,3 +1,5 @@
+import pytest
 class MyTest(unittest.TestCase):
     def test_thing(self):
-        self.my_custom_assert(self.assertRaises(IOError, my_func))
+        self.my_custom_assert(with pytest.raises(IOError):
+            my_func())

There is now a with statement as the first argument passed to my_custom_assert. pytest.raises also supports an expression syntax, so perhaps that should be used here as well.

This came up in a real life project:

https://github.com/python-pillow/Pillow/blob/7bf5246b93cc89cfb9d6cca78c4719a943b10585/Tests/test_file_webp.py#L20

remove_class fix breaks code when there are decorators on methods

test patch
diff --git a/tests/fixtures/remove_class/assertEqual_in.py b/tests/fixtures/remove_class/assertEqual_in.py
index a84867b..2635814 100644
--- a/tests/fixtures/remove_class/assertEqual_in.py
+++ b/tests/fixtures/remove_class/assertEqual_in.py
@@ -7,3 +7,7 @@ class TestAssertNotEqual(TestCase):
 
     def test_you(self):
         self.assertNotEqual(abc, 'xxx')
+
+    @property
+    def test_me(self):
+        pass
diff --git a/tests/fixtures/remove_class/assertEqual_out.py b/tests/fixtures/remove_class/assertEqual_out.py
index 833d980..4da9970 100644
--- a/tests/fixtures/remove_class/assertEqual_out.py
+++ b/tests/fixtures/remove_class/assertEqual_out.py
@@ -5,3 +5,7 @@ def test_you(self):
 
 def test_you(self):
     self.assertNotEqual(abc, 'xxx')
+
+@property
+def test_me(self):
+    pass

Example test failure:

--- expected
+++ refactured result
@@ -6,6 +6,6 @@
 def test_you(self):
     self.assertNotEqual(abc, 'xxx')
 
-@property
-def test_me(self):
+    @property
+deftest_me(self):
     pass

This obliterates the code in the from the point of the decorator until the end of the file.

To avoid this, you can add --nofix=remove_class to your unittest2pytest invocation when running on files which would break.

0.4: pytest warnings

I'm trying to package your module as an rpm package. So I'm using the typical build, install and test cycle used on building packages from non-root account.

  • "setup.py build"
  • "setup.py install --root </install/prefix>"
  • "pytest with PYTHONPATH pointing to sitearch and sitelib inside </install/prefix>

May I ask for help because few units are failing:

+ PYTHONPATH=/home/tkloczko/rpmbuild/BUILDROOT/python-unittest2pytest-0.4-2.fc35.x86_64/usr/lib64/python3.8/site-packages:/home/tkloczko/rpmbuild/BUILDROOT/python-unittest2pytest-0.4-2.fc35.x86_64/usr/lib/python3.8/site-packages
+ /usr/bin/pytest -ra
=========================================================================== test session starts ============================================================================
platform linux -- Python 3.8.12, pytest-6.2.5, py-1.10.0, pluggy-0.13.1
benchmark: 3.4.1 (defaults: timer=time.perf_counter disable_gc=False min_rounds=5 min_time=0.000005 max_time=1.0 calibration_precision=10 warmup=False warmup_iterations=100000)
Using --randomly-seed=2538216899
rootdir: /home/tkloczko/rpmbuild/BUILD/unittest2pytest-0.4
plugins: forked-1.3.0, shutil-1.7.0, virtualenv-1.7.0, expect-1.1.0, flake8-1.0.7, timeout-1.4.2, betamax-0.8.1, freezegun-0.4.2, aspectlib-1.5.2, toolbox-0.5, rerunfailures-9.1.1, requests-mock-1.9.3, cov-2.12.1, flaky-3.7.0, benchmark-3.4.1, xdist-2.3.0, pylama-7.7.1, datadir-1.3.1, regressions-2.2.0, cases-3.6.3, xprocess-0.18.1, black-0.3.12, anyio-3.3.0, asyncio-0.15.1, trio-0.7.0, subtests-0.5.0, isort-2.0.0, hypothesis-6.14.6, mock-3.6.1, profiling-1.7.0, randomly-3.8.0, Faker-8.12.1, nose2pytest-1.0.8, pyfakefs-4.5.1, tornado-0.8.1, twisted-1.13.3
collected 36 items

tests/test_all_fixes.py .................s..................                                                                                                         [100%]

============================================================================= warnings summary =============================================================================
tests/test_all_fixes.py::test_check_fixture[self_assert-assertIsNone]
  /home/tkloczko/rpmbuild/BUILD/unittest2pytest-0.4/tests/fixtures/self_assert/assertIsNone_out.py:14: SyntaxWarning: "is" with a literal. Did you mean "=="?
    assert 'abc' is None

tests/test_all_fixes.py::test_check_fixture[self_assert-assertIs]
  /home/tkloczko/rpmbuild/BUILD/unittest2pytest-0.4/tests/fixtures/self_assert/assertIs_out.py:5: SyntaxWarning: "is" with a literal. Did you mean "=="?
    assert abc is 'xxx'

tests/test_all_fixes.py::test_check_fixture[self_assert-assertIs]
  /home/tkloczko/rpmbuild/BUILD/unittest2pytest-0.4/tests/fixtures/self_assert/assertIs_out.py:8: SyntaxWarning: "is" with a literal. Did you mean "=="?
    assert 123 is xxx+y

tests/test_all_fixes.py::test_check_fixture[self_assert-assertIs]
  /home/tkloczko/rpmbuild/BUILD/unittest2pytest-0.4/tests/fixtures/self_assert/assertIs_out.py:9: SyntaxWarning: "is" with a literal. Did you mean "=="?
    assert 456 is (aaa and bbb)

tests/test_all_fixes.py::test_check_fixture[self_assert-assertIs]
  /home/tkloczko/rpmbuild/BUILD/unittest2pytest-0.4/tests/fixtures/self_assert/assertIs_out.py:10: SyntaxWarning: "is" with a literal. Did you mean "=="?
    assert 789 is (ccc or ddd)

tests/test_all_fixes.py::test_check_fixture[self_assert-assertIs]
  /home/tkloczko/rpmbuild/BUILD/unittest2pytest-0.4/tests/fixtures/self_assert/assertIs_out.py:11: SyntaxWarning: "is" with a literal. Did you mean "=="?
    assert 123 is (True if You else False)

tests/test_all_fixes.py::test_check_fixture[self_assert-assertIs]
  /home/tkloczko/rpmbuild/BUILD/unittest2pytest-0.4/tests/fixtures/self_assert/assertIs_out.py:14: SyntaxWarning: "is" with a literal. Did you mean "=="?
    assert 'abc' is 'def'

tests/test_all_fixes.py::test_check_fixture[self_assert-assertIs]
  /home/tkloczko/rpmbuild/BUILD/unittest2pytest-0.4/tests/fixtures/self_assert/assertIs_out.py:18: SyntaxWarning: "is" with a literal. Did you mean "=="?
    assert 123 is xxx+z, 'This is wrong!'

tests/test_all_fixes.py::test_check_fixture[self_assert-assertIsNot]
  /home/tkloczko/rpmbuild/BUILD/unittest2pytest-0.4/tests/fixtures/self_assert/assertIsNot_out.py:5: SyntaxWarning: "is not" with a literal. Did you mean "!="?
    assert abc is not 'xxx'

tests/test_all_fixes.py::test_check_fixture[self_assert-assertIsNot]
  /home/tkloczko/rpmbuild/BUILD/unittest2pytest-0.4/tests/fixtures/self_assert/assertIsNot_out.py:8: SyntaxWarning: "is not" with a literal. Did you mean "!="?
    assert 123 is not xxx+y

tests/test_all_fixes.py::test_check_fixture[self_assert-assertIsNot]
  /home/tkloczko/rpmbuild/BUILD/unittest2pytest-0.4/tests/fixtures/self_assert/assertIsNot_out.py:9: SyntaxWarning: "is not" with a literal. Did you mean "!="?
    assert 456 is not (aaa and bbb)

tests/test_all_fixes.py::test_check_fixture[self_assert-assertIsNot]
  /home/tkloczko/rpmbuild/BUILD/unittest2pytest-0.4/tests/fixtures/self_assert/assertIsNot_out.py:10: SyntaxWarning: "is not" with a literal. Did you mean "!="?
    assert 789 is not (ccc or ddd)

tests/test_all_fixes.py::test_check_fixture[self_assert-assertIsNot]
  /home/tkloczko/rpmbuild/BUILD/unittest2pytest-0.4/tests/fixtures/self_assert/assertIsNot_out.py:11: SyntaxWarning: "is not" with a literal. Did you mean "!="?
    assert 123 is not (True if You else False)

tests/test_all_fixes.py::test_check_fixture[self_assert-assertIsNot]
  /home/tkloczko/rpmbuild/BUILD/unittest2pytest-0.4/tests/fixtures/self_assert/assertIsNot_out.py:14: SyntaxWarning: "is not" with a literal. Did you mean "!="?
    assert 'abc' is not 'def'

tests/test_all_fixes.py::test_check_fixture[self_assert-assertIsNot]
  /home/tkloczko/rpmbuild/BUILD/unittest2pytest-0.4/tests/fixtures/self_assert/assertIsNot_out.py:18: SyntaxWarning: "is not" with a literal. Did you mean "!="?
    assert 123 is not xxx+z, error_message

tests/test_all_fixes.py::test_check_fixture[self_assert-assertIsNotNone]
  /home/tkloczko/rpmbuild/BUILD/unittest2pytest-0.4/tests/fixtures/self_assert/assertIsNotNone_out.py:14: SyntaxWarning: "is not" with a literal. Did you mean "!="?
    assert 'def' is not None

-- Docs: https://docs.pytest.org/en/stable/warnings.html
========================================================================= short test summary info ==========================================================================
SKIPPED [1] tests/test_all_fixes.py:87: unittest does not have TestCase.assertItemsEqual
================================================================ 35 passed, 1 skipped, 16 warnings in 1.21s ================================================================
pytest-xprocess reminder::Be sure to terminate the started process by running 'pytest --xkill' if you have not explicitly done so in your fixture with 'xprocess.getinfo(<process_name>).terminate()'.

Incorrect transformation due to line wrapping (syntax error)

Consider following testcase:

import unittest

class TestClass(unittest.TestCase):
    def test_wrap(self):
        self.assertEqual(True, True, msg='some %s message' %
                'cool')

The transformation given by unittest2pytest 0.2 is:

--- ./test_example.py   (original)
+++ ./test_example.py   (refactored)
@@ -3,5 +3,5 @@

 class TestClass(unittest.TestCase):
      def test_wrap(self):
      -        self.assertEqual(True, True, msg='some %s message' %
      -                'cool')
      +        assert True == True, 'some %s message' %
      +                'cool'

Which results in:

――――――――――――――――――――――――― ERROR collecting test_example.py ―――――――――――――――――――――――――
../venv/lib/python2.7/site-packages/pytest-2.8.7-py2.7.egg/_pytest/python.py:610:
in _importtestmodule
    mod = self.fspath.pyimport(ensuresyspath=importmode)
    ../venv/lib/python2.7/site-packages/py-1.4.31-py2.7.egg/py/_path/local.py:650:
    in pyimport
        __import__(modname)
        E     File
        "/home/user/unittest2pytest-tmp/result/test_example.py",
        line 6
        E       assert True == True, 'some %s message' %
        E                                              ^
        E   SyntaxError: invalid syntax

Another example of line wrapping causing problems is this:

-        self.assertEqual(NotificationModel()
-                         .get_unread_cnt_for_user(self.u1), 0)
+        assert NotificationModel()
+                         .get_unread_cnt_for_user(self.u1) == 0

Output of invalid indents when comments appear inside implicit string continuations

A comment inside a string breaks the tools as it unnecessarily appends line continuations to strings

i.e. this chokes

foo('a'
    # foo
    'b')

A real example

-            self.assertEqual('\n' + STR_PROJECT_WIDE + '\n\n'
-                             '**** t [Section: ] ****' + '\n\n'
-                             '!    ! [Severity: NORMAL]\n'
-                             '!    ! msg\n'
-                             # Second results file isn't there, no context is
-                             # printed, only a warning log message which we
-                             # don't catch
-                             '\n**** t [Section: ] ****' + '\n\n'
-                             '!    ! [Severity: NORMAL]\n'
+            assert '\n' + STR_PROJECT_WIDE + '\n\n' \
+                             '**** t [Section: ] ****' + '\n\n' \
+                             '!    ! [Severity: NORMAL]\n' \
+                             '!    ! msg\n' \
+                             # Second results file isn't there, no context is \
+                             # printed, only a warning log message which we \
+                             # don't catch \
+                             '\n**** t [Section: ] ****' + '\n\n' \
+                             '!    ! [Severity: NORMAL]\n' \
                              '!    ! {0}\n'.format(
                                  highlight_text(self.no_color, 'msg',
-                                                style=BackgroundMessageStyle)),
-                             stdout.getvalue())
+                                                style=BackgroundMessageStyle)) == \
+                             stdout.getvalue()

From https://github.com/coala/coala/blob/8a25983/tests/output/ConsoleInteractionTest.py#L623

Result: https://travis-ci.org/jayvdb/coala/jobs/246949584#L9052

(Another bug coming about the other problem in that build log.)

Consider moving to pytest-dev

What do you say to move this project to the pytest-dev organization? 😄

The only missing requirement would be to add a tox.ini for running the tests in different python versions, but that would be easy to do.

Would be nice: fix assertEqual with singletons

Currently, unittest2pytest converts this:

self.assertEqual(thing, None)

to:

assert thing == None

which pylint then complains about (as it should).

Arguably, the original line was wrong, but it would be a nice touch if unittest2pytest noticed the value and converted it to:

assert thing is None

Remove_class Fixer Doesn't Properly Modify the Test

Thanks for the great tool, the assert fixer has been a huge time saver for a couple of largest projects I have upgraded to Pytest. ❤️

Here is one of the simpler tests I am working with:

import unittest
from gaphor.UML.collection import collectionlist


class CollectionlistTestCase(unittest.TestCase):
    def test_listing(self):
        c = collectionlist()
        c.append("a")
        c.append("b")
        c.append("c")
        assert str(c) == "['a', 'b', 'c']"
$ unittest2pytest -f remove_class -w gaphor/UML/tests/test_collection.py 
RefactoringTool: No files need to be modified.

It looks like in the remove_class PATTERN, it is only looking for TestCase. So that is the first issue.
If I modify the imports like so:

from unittest import TestCase
class CollectionlistTestCase(TestCase):

I get the following output after rerunning unittest2pytest:

from unittest import TestCase
from gaphor.UML.collection import collectionlist
def test_listing(self):
    c = collectionlist()
    c.append("a")
    c.append("b")
    c.append("c")
    assert str(c) == "['a', 'b', 'c']"

It didn't actually get rid of self in the function definition or remove the TestCase import. Things unfortunately don't get better with a more complex test case.

from unittest import TestCase

from gaphor import UML
from gaphor.application import Application
from gaphor.core import inject
from gaphor.ui.event import DiagramShow
from gaphor.ui.abc import UIComponent


class MainWindowTestCase(TestCase):
    def setUp(self):
        Application.init(
            services=["element_factory", "properties", "main_window", "action_manager"]
        )

    component_registry = inject("component_registry")
    event_manager = inject("event_manager")

    def tearDown(self):
        Application.shutdown()

    def get_current_diagram(self):
        return self.component_registry.get(
            UIComponent, "diagrams"
        ).get_current_diagram()

    def test_creation(self):
        # MainWindow should be created as resource
        main_w = Application.get_service("main_window")
        main_w.open()
        self.assertEqual(self.get_current_diagram(), None)

    def test_show_diagram(self):
        main_w = Application.get_service("main_window")
        element_factory = Application.get_service("element_factory")
        diagram = element_factory.create(UML.Diagram)
        main_w.open()
        self.event_manager.handle(DiagramShow(diagram))
        self.assertEqual(self.get_current_diagram(), diagram)

After running unittest2pytest I get:

from unittest import TestCase

from gaphor import UML
from gaphor.application import Application
from gaphor.core import inject
from gaphor.ui.event import DiagramShow
from gaphor.ui.abc import UIComponent
def setUp(self):
    Application.init(    services=["element_factory", "properties", "main_window", "action_manager"]    )

    component_registry = inject("component_registry")
event_manager=inject("event_manager")
deftearDown(self):
    Application.shutdown()

    def get_current_diagram(self):
    return self.component_registry.get(    UIComponent, "diagrams"    ).get_current_diagram()

    def test_creation(self):
        # MainWindow should be created as resource
    main_w = Application.get_service("main_window")
    main_w.open()
    self.assertEqual(self.get_current_diagram(), None)

    def test_show_diagram(self):
    main_w = Application.get_service("main_window")
    element_factory = Application.get_service("element_factory")
    diagram = element_factory.create(UML.Diagram)
    main_w.open()
    self.event_manager.handle(DiagramShow(diagram))
    self.assertEqual(self.get_current_diagram(), diagram)

The code is no longer valid Python, and there would be no way to automatically reformat this even with a tool like Black.

I would be glad to help try to fix this. Is this the correct expectation, that unittest2pytest should be able to parse these test cases and produce valid pytests including fixtures?

Fatal error when assertTrue is checked with a generator

The test here is quite nonsensical (because a generator is of course always True) and I am actually happy that unittest2pytest pointed that out so I could fix the test. However, I do believe that it should fail a little more gracefully:

$ cat test_gen.py
import unittest
class TestGen(unittest.TestCase):
    def test_gen(self):
        self.assertTrue(x for x in range(1))

$ unittest2pytest test_gen.py
Traceback (most recent call last):
  File "$HOME/anaconda3/bin/unittest2pytest", line 11, in <module>
    sys.exit(main())
  File "$HOME/anaconda3/lib/python3.5/site-packages/unittest2pytest/__main__.py", line 30, in main
    raise SystemExit(lib2to3.main.main(fixes.__name__))
  File "$HOME/anaconda3/lib/python3.5/lib2to3/main.py", line 259, in main
    options.processes)
  File "$HOME/anaconda3/lib/python3.5/lib2to3/refactor.py", line 706, in refactor
    items, write, doctests_only)
  File "$HOME/anaconda3/lib/python3.5/lib2to3/refactor.py", line 301, in refactor
    self.refactor_file(dir_or_file, write, doctests_only)
  File "$HOME/anaconda3/lib/python3.5/lib2to3/refactor.py", line 747, in refactor_file
    *args, **kwargs)
  File "$HOME/anaconda3/lib/python3.5/lib2to3/refactor.py", line 354, in refactor_file
    tree = self.refactor_string(input, filename)
  File "$HOME/anaconda3/lib/python3.5/lib2to3/refactor.py", line 386, in refactor_string
    self.refactor_tree(tree, name)
  File "$HOME/anaconda3/lib/python3.5/lib2to3/refactor.py", line 426, in refactor_tree
    self.traverse_by(self.bmi_post_order_heads, tree.post_order())
  File "$HOME/anaconda3/lib/python3.5/lib2to3/refactor.py", line 502, in traverse_by
    new = fixer.transform(node, results)
  File "$HOME/anaconda3/lib/python3.5/site-packages/unittest2pytest/fixes/fix_self_assert.py", line 404, in transform
    process_arg(results['arglist'])
  File "$HOME/anaconda3/lib/python3.5/site-packages/unittest2pytest/fixes/fix_self_assert.py", line 378, in process_arg
    name, equal, value = arg.children
ValueError: not enough values to unpack (expected 3, got 2)

Expected outcome:

$ unittest2pytest test_gen.py 
RefactoringTool: Refactored test_gen.py
--- test_gen.py (original)
+++ test_gen.py (refactored)
@@ -1,5 +1,5 @@
 import unittest
 class TestGen(unittest.TestCase):
     def test_gen(self):
-        self.assertTrue(x for x in range(1))
+        assert (x for x in range(1))

RefactoringTool: Files that need to be modified:
RefactoringTool: test_gen.py

(And possibly a warning that this test is nonsensical.)

tox failures

tox fails for me locally with:

$ tox
GLOB sdist-make: /Users/adamj/Documents/Projects/unittest2pytest/setup.py
py27 inst-nodeps: /Users/adamj/Documents/Projects/unittest2pytest/.tox/dist/unittest2pytest-0.3.dev0.zip
ERROR: invocation failed (exit code 1), logfile: /Users/adamj/Documents/Projects/unittest2pytest/.tox/py27/log/py27-3.log
ERROR: actionid: py27
msg: installpkg
cmdargs: [local('/Users/adamj/Documents/Projects/unittest2pytest/.tox/py27/bin/pip'), 'install', '-U', '--no-deps', '/Users/adamj/Documents/Projects/unittest2pytest/.tox/dist/unittest2pytest-0.3.dev0.zip']
env: {'PATH': '/Users/adamj/Documents/Projects/unittest2pytest/.tox/py27/bin:/Users/adamj/.rbenv/shims:/Users/adamj/script:/Users/adamj/bin:/usr/local/share/npm/bin:/usr/local/mysql/bin:/Applications/Inkscape.app/Contents/Resources/bin:/usr/local/heroku/bin:/usr/local/Cellar/ruby/2.0.0-p0/bin:/Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home/bin:/usr/X11/bin:/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin:/opt/local/libexec/gnubin:/opt/local/bin:/opt/local/sbin:/usr/local/opt/go/libexec/bin:/Users/adamj/Documents/goworkspace/bin/', 'ITERM_PROFILE': 'Default', 'TERM': 'xterm-256color', '_': '/usr/local/bin/tox', 'LESS_TERMCAP_so': '\x1b[00;47;30m', 'LANG': 'en_GB.UTF-8', 'BROWSER': 'open', 'LOGENTRIES_ACCOUNT_KEY': '8442089d-9b32-4203-a9d3-0e65e90ff216', 'Apple_PubSub_Socket_Render': '/tmp/launch-bU3NpQ/Render', 'GREP_COLORS': 'mt=37;45', 'SSH_AUTH_SOCK': '/tmp/launch-9qjA1S/Listeners', 'LESSOPEN': '| /usr/bin/env lesspipe.sh %s 2>&-', 'LSCOLORS': 'exfxcxdxbxGxDxabagacad', 'HISTFILESIZE': '1000000', '__CF_USER_TEXT_ENCODING': '0x1F5:0:0', '__CHECKFIX1436934': '1', 'PYTHONHASHSEED': '3088900154', 'DISPLAY': '/tmp/launch-518jkq/org.macosforge.xquartz:0', 'MYSQL_PS1': '\\u@\\h [\\c]> ', 'OLDPWD': '/Users/adamj/.virtualenvs', 'LESS_TERMCAP_mb': '\x1b[01;31m', 'TMPDIR': '/var/folders/_z/wpddpg7d5hd6fd2zjn_fct7m0000gn/T/', 'LESS_TERMCAP_us': '\x1b[01;32m', 'LESS_TERMCAP_se': '\x1b[0m', 'SHLVL': '1', 'WORDCHARS': '*?_[]~&;\\!#%^(){}<>', 'LOGNAME': 'adamj', 'COMMAND_MODE': 'unix2003', 'HOME': '/Users/adamj', 'LS_COLORS': 'di=34:ln=35:so=32:pi=33:ex=31:bd=36;01:cd=33;01:su=31;40;07:sg=36;40;07:tw=32;40;07:ow=33;40;07:', 'VIRTUALENVWRAPPER_SCRIPT': '/usr/local/bin/virtualenvwrapper.sh', 'SECURITYSESSIONID': '186a4', 'LESS_TERMCAP_me': '\x1b[0m', 'HISTSIZE': '40000', 'LESS': '-F -g -i -M -R -S -w -X -z-4', 'LESS_TERMCAP_ue': '\x1b[0m', 'USER': 'adamj', 'GREP_COLOR': '37;45', 'SHELL': '/bin/zsh', 'EDITOR': 'st', 'VIRTUALENVWRAPPER_PROJECT_FILENAME': '.project', 'ITERM_SESSION_ID': 'w0t6p0', 'LESS_TERMCAP_md': '\x1b[01;31m', 'HISTIGNORE': '&:ls:[bf]g:exit', 'AUTOJUMP_ERROR_PATH': '/Users/adamj/Library/autojump/errors.log', 'VISUAL': 'nano', 'ANSIBLE_NOCOWS': '1', 'PYENV_SHELL': 'zsh', 'PAGER': 'less', 'GOPATH': '/Users/adamj/Documents/goworkspace', 'VIRTUALENVWRAPPER_HOOK_DIR': '/Users/adamj/.virtualenvs', 'VIRTUALENVWRAPPER_WORKON_CD': '1', 'PYTHONSTARTUP': '/Users/adamj/.python_startup', 'TERM_PROGRAM': 'iTerm.app', 'AUTOJUMP_SOURCED': '1', 'PWD': '/Users/adamj/Documents/Projects/unittest2pytest', 'WORKON_HOME': '/Users/adamj/.virtualenvs', 'VIRTUAL_ENV': '/Users/adamj/Documents/Projects/unittest2pytest/.tox/py27'}

Processing ./.tox/dist/unittest2pytest-0.3.dev0.zip
    Complete output from command python setup.py egg_info:
    Traceback (most recent call last):
      File "<string>", line 20, in <module>
      File "/var/folders/_z/wpddpg7d5hd6fd2zjn_fct7m0000gn/T/pip-RMZRmL-build/setup.py", line 33, in <module>
        read('CHANGES.rst')])
      File "/var/folders/_z/wpddpg7d5hd6fd2zjn_fct7m0000gn/T/pip-RMZRmL-build/setup.py", line 29, in read
        return unicode(codecs.open(filename, encoding='utf-8').read())
      File "/Users/adamj/Documents/Projects/unittest2pytest/.tox/py27/lib/python2.7/codecs.py", line 896, in open
        file = __builtin__.open(filename, mode, buffering)
    IOError: [Errno 2] No such file or directory: 'CHANGES.rst'

    ----------------------------------------
Command "python setup.py egg_info" failed with error code 1 in /var/folders/_z/wpddpg7d5hd6fd2zjn_fct7m0000gn/T/pip-RMZRmL-build
You are using pip version 7.1.2, however version 8.1.2 is available.
You should consider upgrading via the 'pip install --upgrade pip' command.

And similarly for every other environment. It looks like your packaging setup has a few things missing like a MANIFEST.in to ensure that CHANGES.rst is kept with the package. Maybe this breakage has been introduced with newer pip/setuptools/tox versions.

Missing trailing : on emitted with statement

The tool chokes on https://github.com/coala/coala/blob/8a25983/tests/parsing/CliParsingTest.py#L58

Somehow it writes with statements without a trailing :.

         sections = parse_cli(arg_list=['--relpath'])
-        with self.assertRaisesRegex(SystemExit, '2') as cm:
+        with pytest.raises(SystemExit) as excinfo
+        assert re.search(pattern, excinfo.value) as cm:
             check_conflicts(sections)
-            self.assertEqual(cm.exception.code, 2)
+            assert cm.exception.code == 2
 
         sections = parse_cli(arg_list=['--output', 'iraiseValueError'])
-        with self.assertRaisesRegex(SystemExit, '2') as cm:
+        with pytest.raises(SystemExit) as excinfo
+        assert re.search(pattern, excinfo.value) as cm:
             check_conflicts(sections)
-            self.assertEqual(cm.exception.code, 2)
+            assert cm.exception.code == 2

The result is naturally a syntax error: https://travis-ci.org/jayvdb/coala/jobs/246949584#L9068

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.