有没有可能在多行之间断开一个很长的函数名?

我们的开发团队使用了一个需要 最大行长为80个字符的 PEP8衬垫。

当我在 Python 中编写单元测试时,我喜欢使用 描述性方法名称来描述每个测试的作用。然而,这经常导致我超过字符限制。

下面是一个函数的例子,它太长了..。

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()

我的选择:

  • 你可以写一些简短的方法名!

    我知道,但我不想失去测试名称的描述性。

  • 您可以在每个测试上面写多行注释,而不是使用长名称!

    这是一个不错的想法,但是当在 IDE (PyCharm)中运行测试时,我将无法看到测试名称。

  • 也许您可以使用反斜杠(逻辑行继续字符)来继续这些行。

    不幸的是,正如 Dan 的回答中提到的,这在 Python 中不是一个选项。

  • 你可以不用在考卷上乱涂乱画了。

    这在某些方面是有意义的,但是鼓励使用格式良好的测试套件也不错。

  • 您可以增加行长度限制。

    我们的团队喜欢有这个限制,因为它有助于保持代码在窄显示器上的可读性,所以这不是 最好的选项。

  • 可以从方法的开始删除 test

    这不是一个选择。Python 的测试运行程序需要所有以 test开始的测试方法,否则它不会接受这些方法。

    编辑: 一些测试运行程序允许您在搜索测试函数时指定正则表达式,尽管我不想这样做,因为这是为项目中的每个工作人员额外设置的。

  • 您可以将 EventListener 分隔为它自己的类并单独测试它。

    事件监听器 在其自己的类中(并且进行了测试)。它只是一个由 ClientConnection 中发生的事件触发的接口。这种建议似乎有好的意图,但是被误导了,无助于回答原来的问题。

  • 你可以使用像 规矩点这样的 BDD 框架,它是为表达式测试而设计的。

    这是事实,我希望将来能更多地使用它们。尽管我仍然想知道如何跨行分割函数名。

最终..。

Python 中有没有到 将长函数声明拆分为多行的方法?

比如说..。

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()

还是我自己咬紧牙关缩短它?

18355 次浏览

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.

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."

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:

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

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.)

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.

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).