programing tip

긴 함수 이름을 여러 줄로 나눌 수 있습니까?

itbloger 2020. 10. 16. 07:13
반응형

긴 함수 이름을 여러 줄로 나눌 수 있습니까?


우리 개발팀은 최대 80 자 길이의 PEP8 린터를 사용합니다 .

파이썬으로 단위 테스트를 작성할 때 각 테스트가 수행하는 작업을 설명하는 설명적인 메서드 이름갖고 싶습니다 . 그러나 이것은 종종 글자 수 제한을 초과하게 만듭니다.

다음은 너무 긴 함수의 예입니다.

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 프레임 워크를 사용할 수 있습니다 . 표현 테스트를 위해 설계되었습니다.

    이것은 사실이며 앞으로 더 많이 사용하고 싶습니다. 함수 이름을 여러 줄로 나누는 방법을 여전히 알고 싶습니다.

궁극적으로 ...

파이썬에서 긴 함수 선언을 여러 줄로 분할 하는 방법이 있습니까?

예를 들면 ...

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

아니면 총알을 깨물고 직접 줄여야할까요?


아니요, 불가능합니다.

대부분의 경우 이러한 긴 이름은 함수의 가독성과 유용성의 관점에서 바람직하지 않지만 테스트 이름에 대한 사용 사례는 상당히 합리적으로 보입니다.

파이썬어휘 규칙은 단일 토큰 (이 경우 식별자)이 여러 줄로 분할되는 것을 허용하지 않습니다. 논리 줄 연속 문자 (줄 \끝)는 여러 물리적 줄을 단일 논리 줄로 결합 할 수 있지만 단일 토큰 을 여러 줄에 결합 할 수는 없습니다 .


당신은 또한 변이 실내 장식 쓰기 .__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()

파이썬이 소스에 인접한 문자열 리터럴을 연결한다는 사실에 의존합니다.


이 질문에 대한 답변 : 특정 파일에서 pep8 오류를 비활성화하는 방법은 무엇입니까? , # nopep8또는 # noqa후행 주석을 사용하여 긴 줄에 대해 PEP-8을 비활성화합니다. 규칙을 어길 때 아는 것이 중요합니다. 물론, Zen of Python은 "특수한 경우는 규칙을 위반할만큼 특별하지 않다"고 말할 것입니다.


에서 메서드 이름을 가져 오므 로 메서드 대신 클래스에 데코레이터적용 할 수 있습니다 .unittestdir(class)

데코레이터 decorate_method는 클래스 메소드를 거치고 func_mapping사전에 따라 메소드의 이름을 바꿉니다 .

@Sean Vieira의 데코레이터 답변을 본 후 생각했습니다.

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

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:


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


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

참고URL : https://stackoverflow.com/questions/40955302/is-it-possible-to-break-a-long-function-name-across-multiple-lines

반응형