是否可以跨多行打破长函数名称?

byx*_*xor 77 python

我们的开发团队使用PEP8 linter,最大行长为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()
Run Code Online (Sandbox Code Playgroud)

我的选择:

  • 你可以写更短的方法名称!

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

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

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

  • 也许你可以用反斜杠(逻辑行继续符)继续行.

    不幸的是,这不是Python中的一个选项,正如Dan的回答中提到的那样.

  • 你可以停止linting你的测试.

    这在某些方面是有道理的,但鼓励格式良好的测试套件是很好的.

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

    我们的团队喜欢有限制,因为它有助于在窄显示屏上保持代码可读,因此这不是最佳选择.

  • 您可以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()
Run Code Online (Sandbox Code Playgroud)

或者我必须自己咬紧牙关并缩短它?

Dan*_*ski 75

不,这是不可能的.

在大多数情况下,从功能的可读性和可用性的角度来看,这样一个长名称是不可取的,尽管您的测试名称用例似乎很合理.

Python词法规则不允许将单个标记(在本例中为标识符)分割为多行.逻辑行延续字符(\在行的末尾)可以将多个物理行连接到单个逻辑行,但不能跨多行连接单个标记.

  • 值得注意的是,只有一种情况**使用行继续符是可接受的:long`with`语句:`expr1为x,\ <newline> expr2为y ...`.在**所有**其他情况下,请将表达式包装在括号中:`(a_very_long <newline> + expression)`工作正常,并且更具可读性和*健壮性*然后`a_very_long\<newline> + expression` ...后者只是在反斜杠之后添加一个空格就可以了! (11认同)
  • 最好的方法是在self.assert*方法中使用描述性名称作为msg kwarg arg.如果测试通过,你将看不到它.但是如果测试失败,您的描述性字符串将在测试结果对象上可用. (6认同)
  • @Bakuriu - 哇!我不知道你不能在parens中包含一个`with`语句. (3认同)
  • 真是太遗憾了.我仍然觉得可能会有某种神奇的解决方案.---我应该提一下,我已经在我的帖子中尝试过反斜杠,以防有人提到我. (2认同)
  • @ mattmc3原因很简单:它不是表达.AFAIK它*字面*是唯一一种在换行符上使用括号来表示延续的情况根本不是一种选择. (2认同)
  • @krillgar我部分不同意.该测试代表了我的代码的完整功能,因为我遵循了TDD.很高兴我可以用一个简短的句子列出每个功能. (2认同)
  • @krillgar在旁注中,我发现当我将`test_that`改为`test`时,我的大脑需要更长的时间来理解方法名称.我不确定为什么会出现这种现象,但我认为这是因为我的大脑将`test_that`与动词,测试相关联,而不是让`test`的模糊性成为名词_或_动词. (2认同)

Sea*_*ira 50

可能也写的是一个变异装饰.__name__的方法.

def test_name(name):
    def wrapper(f):
        f.__name__ = name
        return f
    return wrapper
Run Code Online (Sandbox Code Playgroud)

然后你可以写:

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()
Run Code Online (Sandbox Code Playgroud)

依赖于Python连接源相邻字符串文字的事实.

  • 这是一个很好的主意.它看起来也很可读.我现在就试试看看我的IDE是否显示了更长的函数名称. (3认同)
  • 更好的是,修改装饰器以从函数的docstring生成描述性名称. (3认同)
  • 不幸的是,装饰器在PyCharm中运行测试之前没有应用,这意味着我无法从我的测试运行器中看到描述性名称. (2认同)
  • 我想你会想用`@ functools.wraps(f)`来装饰`wrapper`. (2认同)
  • 这是最好的吃蛋糕和吃的解决方案; 它结合了@BrandonIbbotson正在寻找的所有功能.太糟糕了,PyCharm还没有完全理解它. (2认同)

mat*_*mc3 33

根据这个问题的答案:如何禁用特定文件中的pep8错误?,使用# nopep8# noqa尾随注释禁用长线PEP-8.知道什么时候打破规则很重要.当然,Python的Zen会告诉你"特殊情况不足以打破规则."

  • 这实际上是一个很棒的主意,因为它让我可以抓住其余的测试文件.我只是测试它,它的工作原理.我还可以保留长方法名称的所有好处.---我唯一关心的是团队不希望在整个测试中看到"#nopep8"评论散落;) (5认同)

Sky*_*ycc 8

我们可以将decorator应用于类而不是方法,因为unittestget方法的名称来自dir(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()
Run Code Online (Sandbox Code Playgroud)

测试运行unittest如下所示确实显示了完整的长描述性函数名称,认为它可能适用于您的情况虽然它可能听起来不那么优雅和可读的实现

>>> unittest.main(verbosity=2)
test_that_client_event_listener_receives_connection_refused_error_without_server (__main__.ClientConnectionTest) ... i am client_test
ok
Run Code Online (Sandbox Code Playgroud)


ale*_*cxe 7

针对问题的特定于上下文的方法.您提供的测试用例实际上看起来非常像自然语言格式,用于描述测试用例的必要步骤.

看看使用behave行为驱动程序开发风格框架是否会更有意义.您的"特征"可能看起来像(怎么看given,when,then反映你有什么):

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
Run Code Online (Sandbox Code Playgroud)

还有相关主题的相关pyspecs,样本用法来自最近的答案:


Cha*_*mer 5

较短的功能名称解决方案有很多优点.想想实际功能名称中真正需要的是什么以及已经提供了什么.

test_that_client_event_listener_receives_connection_refused_error_without_server(self):
Run Code Online (Sandbox Code Playgroud)

当你运行它时,你肯定知道这是一个测试吗?你真的需要使用下划线吗?这个名字真的需要用'那个'来理解?骆驼的情况会如同可读吗?下面的第一个例子如何重写上面的内容(字符数= 79):接受使用缩写来处理一小部分常用字的约定更有效,例如Connection = Conn,Error = Err.使用缩写时,您必须注意上下文,只有在不存在混淆时才使用它们 - 下面的第二个例子.如果您接受实际上不需要在方法名称中提及客户端作为测试主题,因为该信息在类名中,那么第三个示例可能是合适的.(54)字符.

ClientEventListenerReceivesConnectionRefusedErrorWithoutServer(个体经营):

ClientEventListenerReceivesConnRefusedErrWithoutServer(个体经营):

EventListenerReceiveConnRefusedErrWithoutServer(个体经营):

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

PS我也可能删除'WithoutServer'也是多余的.如果服务器因任何原因无法联系,客户端事件处理程序是否应该接收该事件?(虽然tbh我认为如果客户端无法连接到服务器它会收到某种"连接不可用"会更好,但拒绝连接表明可以找到服务器但拒绝连接本身.)

  • MarianD:很抱歉,但答案是针对OP的,他们可能会花一点时间阅读它,并用建设性的例子和理由解决了缩短名称的几个策略.如果你想要短版本......"避免使用不必要的单词和标点符号并一致地缩短常用词" - 这是否足够简短? (3认同)
  • 使用python的unittest库,每个测试方法必须以`test`开头,否则测试运行器不会接收它. (3认同)

B.M*_*.M. 5

对这种名称的需求可能会暗示其他气味.

class ClientConnectionTest(unittest.TestCase):
   def test_that_client_event_listener_receives_connection_refused_error_without_server(self):
       ...
Run Code Online (Sandbox Code Playgroud)

ClientConnectionTest听起来相当广泛(并且根本不像一个可测试的单位),并且可能是一个大型的课程,内部有大量的测试可以重新聚焦.像这样:

class ClientEventListenerTest(unittest.TestCase):
  def receives_connection_refused_without_server(self):
      ...
Run Code Online (Sandbox Code Playgroud)

"测试"在名称中没用,因为它是隐含的.

有了你给我的所有代码,我的最终建议是:重构你的测试代码,然后重新审视你的问题(如果它还在那里).