是否可以在多行中中断长函数名?


81

我们的开发团队使用PEP8棉短绒,其最大行长为80个字符

当我用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中发生的事件触发的接口。这种建议似乎有很好的意图,但方向错误,无助于回答最初的问题。

  • 您可以使用像Behave这样的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()

还是我必须咬一口子并自己缩短子弹?


8
为什么不使用描述性函数文档字符串?然后,您可以用func.__doc__
jakub '16

62
停止整理单元测试。
John Kugelman

55
然后关闭此规则。这是一个小小的精神错乱,您正在努力解决此皮棉规则,而不仅仅是禁用它。
约翰·库格曼

13
重新访问PEP8 python.org/dev/peps/pep-0008,忽略指导的充分理由:When applying the guideline would make the code less readable, even for someone who is used to reading code that follows this PEP.在您的情况下,将使用较短的函数名。
Akavall's

56
在计算机科学中,有两个难题,即缓存失效,命名问题和一次性错误。
Surt's

Answers:


78

不,这是不可能的。

在大多数情况下,从功能的可读性和可用性的角度来看,这么长的名称是不希望的,尽管您使用测试名称的用例似乎很合理。

Python词汇规则不允许将单个标记(在这种情况下为标识符)拆分为多行。逻辑行连续字符(\在行的末尾)可以将多条物理行连接到一条逻辑行中,但不能在多行中连接一个令牌


2
真可惜 我仍然觉得可能会有某种神奇的解决方案。---我应该提到我在帖子中尝试了反斜杠,以防万一有人向我提及。
byxor

6
最好的办法是在self.assert *方法中将描述性名称用作msg kwarg arg。如果测试通过,您将看不到它。但是,如果测试失败,则描述性字符串将在测试结果对象上可用。
B Rad C

11
值得注意的是有只有一个的情况,其中使用了续行符是可以接受的:长with声明:with expr1 as x, \<newline> expr2 as y ...。在所有其他情况下,请将表达式用括号括起来:(a_very_long <newline> + expression)可以正常工作,并且可读性和健壮性都比a_very_long \<newline> + expression... ...后者通过在反斜杠后仅添加一个空格来打破!
巴库里

3
@Bakuriu-哇!我不知道您不能将with语句包装在括号中。
mattmc3

2
@ mattmc3原因很简单:它不是表达式。AFAIK实际上,这唯一不可能在圆括号上使用括号在换行符上继续的情况。
巴库里

52

可能也写了装饰者,发生变异.__name__的方法。

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

然后,您可以编写:

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

依靠Python连接源相邻字符串文字的事实。


3
这是一个非常好的主意。它看起来也很可读。我将立即尝试,看看我的IDE是否显示更长的函数名称。
byxor

2
不幸的是,装饰器在PyCharm中运行测试之前没有得到应用,这意味着我看不到测试运行器的描述性名称。
byxor

2
我想你会想装饰wrapper@functools.wraps(f)

2
这是最好的解决方案,也可以吃。它结合了@BrandonIbbotson寻找的所有功能。糟糕的PyCharm尚未完全解决。
丹·伦斯基

3
更好的是,修改装饰器以从函数的文档字符串生成描述性名称。
尼克·斯威汀

33

每个问题的答案:如何在特定文件中禁用pep8错误?,请使用# nopep8# noqa尾随注释来禁用PEP-8较长的一行。知道什么时候违反规则很重要。当然,Python的Zen会告诉您“特殊情况不足以打破规则。”


5
这实际上是一个很棒的主意,因为它使我可以抹掉其余的测试文件。我刚刚对其进行了测试,并且可以正常工作。我还保留了长方法名的所有好处。---我唯一担心的是,团队不会喜欢# nopep8在测试过程中看到乱七八糟的评论;)
byxor

8

由于可以从获取方法名称,因此我们可以将装饰器应用于类而不是方法。unittestdir(class)

装饰器decorate_method将遍历类方法,并根据func_mapping字典重命名方法的名称。

看到@Sean Vieira的装饰者回答后,就想到了+1

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

unittest如下所示的测试运行确实显示了完整的长描述性函数名称,认为它可能对您的情况适用,尽管听起来可能不太优雅,并且在实现中不易读

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

7

某种特定于上下文的问题解决方法。您提供的测试用例实际上看起来很像自然语言格式,描述了测试用例所必需的步骤。

看看在这里使用behave行为驱动程序开发样式框架是否更有意义。您的“特征”可能看起来像(怎么看givenwhenthen反映你有什么):

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

还有一个相关的pyspecs,有关相关主题的最新答案的示例用法:


我想提一提,我知道有BDD选项,例如behave。但是,我不想让我的问题分散别人的注意力。它看起来像一个非常好的框架,将来我可能会用它。我实际上问过我的团队是否可以在项目中使用它,但是他们不想测试看起来“怪异”;)---我以前从未见过pyspec。谢谢你的建议。
byxor

1
@BrandonIbbotson明白了,我明白您为什么不想提及它-完全有道理。pyspecs顺便说一句,虽然,这可能更容易集成到测试代码库中-一种更“ python”的BDD方法-不需要这些功能文件。谢谢!
alecxe

5

对此类名称的需求可能会暗示其他气味。

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

ClientConnectionTest听起来范围很广(一点也不像一个可测试的单元),并且可能是一个很大的类,内部有大量测试可以重新调整重点。像这样:

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

“测试”在名称中没有用,因为它是隐含的。

使用您提供给我的所有代码,我的最终建议是:重构您的测试代码,然后重新检查您的问题(如果仍然存在)。


事件监听器是一个接口。其中的方法是由ClientConnection发生的事情触发的。单独测试事件监听器已经完成。我个人认为ClientConnection很好地遵循了SRP,但是我可能会有偏见(您看不到它)。--- Python测试名称必须以开头,test否则测试运行程序不会选择它们。
byxor

1
@BrandonIbbotson啊,我明白了,您正在测试客户端连接是否会触发事件监听器。使用“ test_that_connection_without_server_triggers_connection_refused_event”之类的名称会更加明显。“测试”部分的要求很糟糕,因为它迫使您要么使用笨拙的名称,要么使用充满无用胶水的名称。
BM

那是一个更好的方法名称。我可能会按照您的建议将其中一些方法重命名。尽管我可能仍会使用很多方法来处理80个字符以上
byxor

从我看来,您可以在Python中嵌套类。跑步者可以处理吗?也许您可以将ClientConnectionTest的内容拆分为包含相关测试的嵌套类的主题。这样,主题的类将包含您不需要在每个测试中编写的部分名称。
BM

1
是的,这可能是事实。不确定当时还有什么建议。也许无论如何都应该扩大字符限制,我们自己做了,最后意识到这没什么大不了的,每个人都有空间欢迎80多个字符行。祝好运!
BM

4

较短的函数名称解决方案有很多优点。考虑一下实际函数名称中真正需要什么以及已经提供了什么。

test_that_client_event_listener_receives_connection_refused_error_without_server(self):

您确定在运行它时已经知道这是一个测试吗?您是否真的需要使用下划线?确实需要像“ that”这样的词来理解该名称吗?骆驼的情况会一样可读吗?下面的第一个示例如何改写上面的示例(字符数= 79)呢:接受一个约定以在少量常用单词中使用缩写形式更为有效,例如Connection = Conn,Error = Err。使用缩写词时,您必须注意上下文,并且仅在没有混淆可能性时才使用它们-下面的第二个示例。如果您接受实际上没有必要在方法名称中提及客户端作为测试主题,因为该信息在类名称中,则第三个示例可能是合适的。(54)个字符。

ClientEventListenerReceivesConnectionRefusedErrorWithoutServer():

ClientEventListenerReceivesConnRefusedErrWithoutServer(self):

EventListenerReceiveConnRefusedErrWithoutServer():

我也同意B Rad C的建议“使用描述性名称作为self.assert中的msg kwarg arg”。您应该只对运行测试套件时看到失败测试的输出感兴趣。验证是否已编写所有必要的测试,不取决于方法名称是否如此详细。

PS我也可能会删除多余的'WithoutServer'。如果服务器由于某种原因无法联系,客户端事件处理程序是否应该接收事件?(尽管tbh我认为,如果他们的客户端无法连接到服务器会收到某种“连接不可用”会更好,但拒绝连接表明可以找到服务器,但拒绝连接本身。)


1
TL; DR-请比较您的答案与其他答案的长度。
MarianD

3
玛丽安(MarianD):很抱歉,但是给了OP答复,他们可能不愿意花一点时间来阅读它,并提出了一些具有建设性的例子和理由来简化名称的策略。如果您想使用简短的版本...“避免使用不必要的单词和标点符号,并始终缩短常用单词”-这样简短就足够了吗?
Charemer

3
使用python的unittest库,每个测试方法都必须以开头,test否则测试运行器不会将其拾取。
byxor

1
@BrandonIbbotsontest_EventListenerReceiveConnRefusedErrWithoutServer(self):
Hendry

1
我喜欢CamelCase,但我认为它似乎违反了PEP 8:“使用函数命名规则:小写并在必要时使用下划线分隔单词,以提高可读性。”
Scooter
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.