Is it possible to break a long function name across multiple lines?

Question:

Our development team uses a PEP8 linter which requires a maximum line length of 80 characters.

When I’m writing unit tests in python, I like to have descriptive method names to describe what each test does. However this often leads to me exceeding the character limit.

Here is an example of a function that is too long…

class ClientConnectionTest(unittest.TestCase):

    def test_that_client_event_listener_receives_connection_refused_error_without_server(self):
        self.given_server_is_offline()
        self.given_client_connection()
        self.when_client_connection_starts()
        self.then_client_receives_connection_refused_error()

My Options:

  • You could just write shorter method names!

    I know, but I don’t want to lose the descriptiveness of the test names.

  • You could write multi-line comments above each test instead of using long names!

    This is a decent idea, but then I won’t be able to see the test names when running the tests inside my IDE (PyCharm).

  • Perhaps you can continue the lines with a backslash (a logical line continuation character).

    Unfortunately this isn’t an option in Python, as mentioned in Dan’s answer.

  • You could stop linting your tests.

    This makes sense in some ways, but it’s nice to encourage a well-formatted test suite.

  • You could increase the line length limit.

    Our team likes having the limit because it helps keep the code readable on narrow displays, so this isn’t the best option.

  • You could remove test from the start of your methods.

    This is not an option. Python’s test runner needs all test methods to start with test or it won’t pick them up.

    Edit: Some test runners let you specify a regular expression when searching for test functions, although I’d rather not do this because it’s extra setup for everyone working on the project.

  • You could separate the EventListener into its own class and test it separately.

    The Event Listener is in its own class (and is tested). It’s just an interface that gets triggered by events happening within ClientConnection. This kind of suggestion seems to have good intent, but is misdirected and doesn’t help answer the original question.

  • You could use a BDD Framework like Behave. It’s designed for expressive tests.

    This is true, and I hope to use more of them in the future. Although I’d still like to know how to split function names across lines.

Ultimately…

Is there a way in Python to split a long function declaration across multiple lines?

For example…

def test_that_client_event_listener_receives_
  connection_refused_error_without_server(self):
    self.given_server_is_offline()
    self.given_client_connection()
    self.when_client_connection_starts()
    self.then_client_receives_connection_refused_error()

Or will I have to bite the bullet and shorten it myself?

Asked By: byxor

||

Answers:

No, this is not possible.

In most cases such a long name would be undesirable from the standpoint of readability and usability of the function, though your use case for test names seems pretty reasonable.

The lexical rules of Python do not allow a single token (in this case an identifier) to be split across multiple lines. The logical line continuation character ( at the end of a line) can join multiple physical lines into a single logical line, but cannot join a single token across multiple lines.

Answered By: Dan Lenski

Per the answer to this question:How to disable a pep8 error in a specific file?, use the # nopep8 or # noqa trailing comment to disable PEP-8 for a long line. It’s important to know when to break the rules. Of course, the Zen of Python would tell you that “Special cases aren’t special enough to break the rules.”

Answered By: mattmc3

Sort of a context-specific approach to the problem. The test case you’ve presented actually looks very much like a Natural Language format of describing the necessary steps for a test case to take.

See if using the behave Behavior Driver development style framework would make more sense here. Your “feature” might look like (see how the given, when, then reflect what you had):

Feature: Connect error testing

  Scenario: Client event listener receives connection refused error without server
     Given server is offline
      when client connect starts
      then client receives connection refused error

There is also relevant pyspecs package, sample usage from a recent answer on a related topic:

Answered By: alecxe

We can applying decorator to the class instead of method since unittest get methods name from dir(class).

The decorator decorate_method will go through class methods and rename method’s name based on func_mapping dictionary.

Thought of this after seeing decorator answer from @Sean Vieira , +1 from me

import unittest, inspect

# dictionary map short to long function names
func_mapping = {}
func_mapping['test_client'] = ("test_that_client_event_listener_receives_"
                               "connection_refused_error_without_server")     
# continue added more funtion name mapping to the dict

def decorate_method(func_map, prefix='test_'):
    def decorate_class(cls):
        for (name, m) in inspect.getmembers(cls, inspect.ismethod):
            if name in func_map and name.startswith(prefix):
                setattr(cls, func_map.get(name), m) # set func name with new name from mapping dict
                delattr(cls, name) # delete the original short name class attribute
        return cls
    return decorate_class

@decorate_method(func_mapping)
class ClientConnectionTest(unittest.TestCase):     
    def test_client(self):
        # dummy print for testing
        print('i am test_client')
        # self.given_server_is_offline()
        # self.given_client_connection()
        # self.when_client_connection_starts()
        # self.then_client_receives_connection_refused_error()

test run with unittest as below did show the full long descriptive function name, thinks it might works for your case though it may not sounds so elegant
and readable from the implementation

>>> unittest.main(verbosity=2)
test_that_client_event_listener_receives_connection_refused_error_without_server (__main__.ClientConnectionTest) ... i am client_test
ok
Answered By: Skycc

The shorter function name solution has a lot of merit. Think about what is really needed in your actual function name and what is supplied already.

test_that_client_event_listener_receives_connection_refused_error_without_server(self):

Surely you already know it’s a test when you run it? Do you really need to use underscores? are words like ‘that’ really required for the name to be understood? would camel case be just as readable? how about the first example below as a rewriting of the above (character count = 79):
Accepting a convention to use abbreviations for a small collection of common words is even more effective, e.g. Connection = Conn, Error = Err. When using abbreviations you have to be mindful of the context and only use them when there is no possiblity of confusion – Second example below.
If you accept that there’s no actual need to mention the client as the test subject in the method name as that information is in the class name then the third example may be appropriate. (54) characters.

ClientEventListenerReceivesConnectionRefusedErrorWithoutServer(self):

ClientEventListenerReceivesConnRefusedErrWithoutServer(self):

EventListenerReceiveConnRefusedErrWithoutServer(self):

I’d also agree with the the suggestion from B Rad C “use descriptive name as the msg kwarg arg in in a self.assert” You should only be interested in seeing output from failed tests when the testsuite is run. Verification that you have all the necessary tests written shouldn’t depend on having the method names so detailed.

P.S. I’d probably also remove ‘WithoutServer’ as superfluous as well. Shouldn’t the client event handler receive the event in the case that the server isn’t contactable for any reason? (although tbh I’d think that it would be better that if they client can’t connect to a server it receives some sort of ‘connection unavailable’ , connection refused suggests that the server can be found but refuses the connection itself.)

Answered By: Charemer

You could also write a decorator that mutates .__name__ for the method.

def test_name(name):
    def wrapper(f):
        f.__name__ = name
        return f
    return wrapper

Then you could write:

class ClientConnectionTest(unittest.TestCase):
    @test_name("test_that_client_event_listener_"
    "receives_connection_refused_error_without_server")
    def test_client_offline_behavior(self):
        self.given_server_is_offline()
        self.given_client_connection()
        self.when_client_connection_starts()
        self.then_client_receives_connection_refused_error()

relying on the fact that Python concatenates source-adjacent string literals.

Answered By: Sean Vieira

The need for this kind of names may hint at other smells.

class ClientConnectionTest(unittest.TestCase):
   def test_that_client_event_listener_receives_connection_refused_error_without_server(self):
       ...

ClientConnectionTest sounds pretty broad (and not at all like a testable unit), and is likely a large class with plenty of tests inside that could be refocused. Like this:

class ClientEventListenerTest(unittest.TestCase):
  def receives_connection_refused_without_server(self):
      ...

“Test” is not useful in the name because it’s implied.

With all the code you’ve given me, my final advice is: refactor your test code, then revisit your problem (if it’s still there).

Answered By: CakePlusPlus
Categories: questions Tags:
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.