長い関数名を複数行に分割することは可能ですか?


83

私たちの開発チームは、最大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内で発生するイベントによってトリガーされる単なるインターフェースです。この種の提案は善意があるように見えますが、誤った方向に向けられており、元の質問に答えるのに役立ちません。

  • 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
記述関数docstringを使用してみませんか?次に、次のように印刷できますfunc.__doc__
jakub 2016

62
ユニットテストのリンティングを停止します。
John Kugelman 2016

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

56
コンピュータサイエンスには、キャッシュの無効化、名前の付け方、オフバイワンエラーの2つの難しい問題があります。
2016

回答:


79

いいえ、これは不可能です。

ほとんどの場合、このような長い名前は、関数の読みやすさと使いやすさの観点から望ましくありませんが、テスト名のユースケースはかなり合理的と思われます。

Python字句規則では、単一のトークン(この場合は識別子)を複数の行に分割することはできません。論理行継続文字(行\の終わり)は、複数の物理行を1つの論理行に結合できますが、複数の行にまたがる単一のトークンを結合することはできません。


2
残念です。でも、どういうわけか魔法の解決策があるのではないかと私はまだ感じています。---誰かが私にそれを言及した場合に備えて、私は私の投稿でバックスラッシュを試したことに言及する必要があります。
byxor 2016

6
最善の方法は、self.assert *メソッドのmsgkwargargとして説明的な名前を使用することです。テストに合格した場合、それは表示されません。ただし、テストが失敗した場合は、説明文字列がテスト結果オブジェクトで使用可能になります。
B Rad C

11
行継続文字の使用が受け入れられる状況1つだけあることに注意してwithくださいwith expr1 as x, \<newline> expr2 as y ...。長いステートメント:。では、すべての他の例、ちょうど括弧内の式をラップしてください:(a_very_long <newline> + expression)罰金を動作し、はるかに読みやすいとされる堅牢その後a_very_long \<newline> + expression...ただバックスラッシュの後に単一のスペースを追加することにより、後者の休憩!
バクリウ2016

3
@バクリウ-おっ!私はあなたがwith親で声明を包むことができないことを知りませんでした。
mattmc3 2016

2
@ mattmc3理由は単純です:それは式ではありません。AFAIKは、文字通り、改行の継続に括弧を使用することが単にオプションではない唯一のケースです。
バクリウ2016

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 2016

2
残念ながら、デコレータはPyCharmでテストを実行する前に適用されません。つまり、テストランナーから説明的な名前を見ることができません。
byxor 2016

2
で飾っwrapperてみたくなると思います@functools.wraps(f)

2
これは、ケーキを食べて食べすぎるのに最適なソリューションです。@BrandonIbbotsonが探していたすべての機能を組み合わせています。あまりにも悪いPyCharmはまだそれを完全に理解していません。
Dan Lenski 2016

3
さらに良いことに、デコレータを変更して、関数のdocstringからわかりやすい名前を生成します。
Nick Sweeting 2016

33

この質問への回答によると:特定のファイルでpep8エラーを無効にする方法は?# nopep8または# noqa末尾のコメントを使用して、長い行のPEP-8を無効にします。ルールを破る時​​期を知ることは重要です。もちろん、Zen of Pythonは、「特別な場合は、ルールを破るほど特別ではない」と言うでしょう。


5
残りのテストファイルを糸くずにすることができるので、これは実際には素晴らしいアイデアです。私はそれをテストしたところ、動作します。また、長いメソッド名のすべての利点を維持することができます。---私の唯一の懸念は、チームが# nopep8テスト中に散らばっているコメントを見
たく

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

問題に対するコンテキスト固有のアプローチの一種。提示したテストケースは、実際には、テストケースが実行するために必要な手順を説明する自然言語形式に非常によく似ています。

ここで、behaveBehaviorDriver開発スタイルフレームワークを使用する方が理にかなっているのかどうかを確認してください。あなたの「機能」(わかりのように見えるかもしれません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。しかし、私は私の質問で人々の気を散らしたくありませんでした。それは本当に素晴らしいフレームワークのように見えます、そして私はおそらくそれを将来使うでしょう。私は実際にこのプロジェクトでそれを使用できるかどうかチームに尋ねましたが、彼らは「奇妙」に見えるようにテストすることを望んでいませんでした;)---私はこれまでpyspecsを見たことがありません。提案をありがとう。
byxor 2016

1
@BrandonIbbotson落とし穴、あなたがそれについて言及したくない理由を理解しています-完全に理にかなっています。pyspecsちなみに、テストコードベースに統合する方が簡単かもしれませんが、BDDを実行するためのより「Python」の方法です。これらの機能ファイルは必要ありません。ありがとう!
alecxe 2016

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 2016

1
@BrandonIbbotsonああ、わかりました。クライアント接続がイベントリスナーで何かトリガーすることをテストしています。これは、「test_that_connection_without_server_triggers_connection_refused_event」のような名前でより明白になります。「テスト」部分の要件は、厄介な名前または役に立たない接着剤でいっぱいの名前を使用する必要があるため、ひどいものです。
BM

それはより良いメソッド名です。私はあなたが提案した方法でそれらの方法のいくつかの名前を変更するかもしれません。私はおそらくまだ80文字を超えるメソッドをたくさん持っているでしょうが
byxor 2016

私が見るところから、Pythonでクラスをネストすることができます。テストランナーはそれを処理しますか?ClientConnectionTestの内部を、関連するテストを含むネストされたクラスであるトピックに分割できるかもしれません。そうすれば、トピックのクラスには、各テストで記述する必要のない名前の部分が含まれます。
BM 2016

1
ええ、そうかもしれないと考えました。その時、他に何を提案すべきかわからない。とにかく文字数制限を拡張することを許可するかもしれませんが、私たちは自分たちでそれを行い、最終的にはそれほど大したことではなく、誰もが80文字以上の行を歓迎する余地があることに気付きました。幸運を!
BM

4

短い関数名ソリューションには多くのメリットがあります。実際の関数名に本当に必要なものと、すでに提供されているものについて考えてください。

test_that_client_event_listener_receives_connection_refused_error_without_server(self):

確かにあなたはそれを実行するときにそれがテストであることをすでに知っていますか?本当にアンダースコアを使用する必要がありますか?「それ」のような言葉は、名前を理解するために本当に必要ですか?キャメルケースも同じように読みやすいでしょうか?上記の書き直しとして、以下の最初の例はどうですか(文字数= 79):一般的な単語の小さなコレクションに略語を使用する規則を受け入れると、さらに効果的です(例:Connection = Conn、Error = Err)。略語を使用するときは、文脈に注意し、混乱の可能性がない場合にのみ使用する必要があります-以下の2番目の例。その情報はクラス名に含まれているため、メソッド名のテスト対象としてクライアントを実際に言及する必要がないことを受け入れる場合は、3番目の例が適切な場合があります。(54)文字。

ClientEventListenerReceivesConnectionRefusedErrorWithoutServer(self):

ClientEventListenerReceivesConnRefusedErrWithoutServer(self):

EventListenerReceiveConnRefusedErrWithoutServer(self):

また、B RadCの提案「self.assertのmsgkwargargとして説明的な名前を使用する」にも同意します。テストスイートの実行時に失敗したテストからの出力のみを確認する必要があります。必要なすべてのテストが記述されていることの確認は、メソッド名がそれほど詳細であることに依存するべきではありません。

PS私はおそらく「WithoutServer」も不要なものとして削除します。何らかの理由でサーバーに接続できない場合、クライアントイベントハンドラーはイベントを受信するべきではありませんか?(クライアントがサーバーに接続できない場合は、何らかの「接続が利用できません」を受信する方がよいと思いますが、接続が拒否された場合は、サーバーを見つけることができますが、接続自体は拒否されます。)


1
TL; DR-あなたの答えの長さを他の答えと比較してください。
marianD 2016

3
MarianD:申し訳ありませんが、OPは、少し時間をかけて読んで、建設的な例と理論的根拠で名前を短くするためのいくつかの戦略に取り組んだ可能性があります。短いバージョンが必要な場合...「不要な単語や句読点を避け、一般的な単語を一貫して短くする」-それで十分ですか?
Charemer 2016

3
Pythonのunittestライブラリでは、各テストメソッドは最初から始める必要がありtestます。そうしないと、テストランナーはそれを取得しません。
byxor 2016

1
@BrandonIbbotsontest_EventListenerReceiveConnRefusedErrWithoutServer(self):
ヘンドリー

1
キャメルケースは好きですが、PEP 8に違反しているようです。「関数の命名規則を使用してください。読みやすさを向上させるために、必要に応じて単語をアンダースコアで区切って小文字にします。」
スクーター2016
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.