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

우리 개발팀은 최대 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()

unittest아래와 같이 테스트 실행 은 전체 긴 설명 함수 이름을 보여 주며 구현에서 그렇게 우아하고 읽을 수는 없지만 귀하의 경우에 작동한다고 생각합니다.

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


답변

문제에 대한 상황 별 접근 방식입니다. 제시 한 테스트 케이스는 실제로 테스트 케이스가 수행하는 데 필요한 단계를 설명 하는 자연어 형식 과 매우 유사합니다 .

여기 에서 behaveBehavior Driver 개발 스타일 프레임 워크를 사용하는 것이 더 합리적인지 확인하십시오. 귀하의 “기능”합니다 (방법을 볼처럼 보일 수 있습니다 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

관련 주제에 대한 최근 답변의 관련 pyspecs패키지 , 샘플 사용도 있습니다.


답변

이런 종류의 이름에 대한 필요성은 다른 냄새를 암시 할 수 있습니다.

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

“테스트”는 암시 적이므로 이름에 유용하지 않습니다.

제게 주신 모든 코드를 사용하여 마지막 조언은 테스트 코드를 리팩토링 한 다음 문제를 재검토하는 것입니다 (아직 문제가있는 경우).


답변

더 짧은 함수 이름 솔루션은 많은 장점이 있습니다. 실제 함수 이름에 실제로 필요한 것과 이미 제공된 것이 무엇인지 생각해보십시오 .

test_that_client_event_listener_receives_connection_refused_error_without_server(self):

확실히 당신은 그것을 실행할 때 그것이 테스트라는 것을 이미 알고 있습니까? 정말로 밑줄을 사용해야합니까? 이름을 이해하려면 ‘that’과 같은 단어가 정말 필요한가요? 낙타 케이스는 읽을 수있는 것처럼 보입니까? 위의 재 작성으로 아래의 첫 번째 예는 어떻습니까 (문자 수 = 79) : 작은 공통 단어 모음에 대해 약어를 사용하는 규칙을 수락하는 것이 훨씬 더 효과적입니다 (예 : Connection = Conn, Error = Err). 약어를 사용할 때는 문맥을 염두에두고 혼동 가능성이 없을 때만 사용하십시오. 아래 두 번째 예입니다. 정보가 클래스 이름에 있으므로 메서드 이름에서 테스트 대상으로 클라이언트를 실제로 언급 할 필요가 없다는 사실을 수락하면 세 번째 예제가 적절할 수 있습니다. (54) 문자.

ClientEventListenerReceivesConnectionRefusedErrorWithoutServer (self) :

ClientEventListenerReceivesConnRefusedErrWithoutServer (self) :

EventListenerReceiveConnRefusedErrWithoutServer (self) :

또한 B Rad C의 제안에 동의합니다. “self.assert의 msg kwarg arg에 설명적인 이름을 사용하십시오.”테스트 스위트가 실행될 때 실패한 테스트의 출력 만 보는 데 관심이 있어야합니다. 필요한 모든 테스트를 작성했는지 확인하는 것은 메서드 이름이 너무 자세하다는 것에 의존해서는 안됩니다.

추신 : 아마도 불필요한 ‘WithoutServer’도 제거 할 것입니다. 어떤 이유로 든 서버에 연결할 수없는 경우 클라이언트 이벤트 처리기가 이벤트를 수신하지 않아야합니까? (tbh 클라이언트가 서버에 연결할 수 없다면 일종의 ‘연결 불가’를 수신하는 것이 더 나을 것이라고 생각하지만 연결 거부는 서버를 찾을 수 있지만 연결 자체를 거부한다는 것을 나타냅니다.)