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


83

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

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


8
설명 함수 독 스트링을 사용하지 않는 이유는 무엇입니까? 그럼 당신은 그것을 인쇄 할 수 있습니다func.__doc__
야쿱

62
단위 테스트를 린팅하지 마십시오.
존 Kugelman

55
그런 다음이 규칙을 해제하십시오. 이 린트 규칙을 비활성화하는 것이 아니라이 규칙을 해결하기 위해 열심히 노력하는 것은 사소한 광기입니다.
존 Kugelman

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
컴퓨터 과학에는 캐시 무효화, 이름 지정 및 일회성 오류라는 두 가지 어려운 문제가 있습니다.
Surt

답변:


79

아니요, 불가능합니다.

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

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


2
부끄러운 일입니다. 그래도 어떻게 든 마법의 해결책이있을 수 있다고 생각합니다. --- 누군가가 나에게 언급 할 경우를 대비하여 내 게시물에서 백 슬래시를 시도했음을 언급해야합니다.
byxor

6
가장 좋은 방법은 self.assert * 메서드에서 설명이 포함 된 이름을 msg kwarg 인수로 사용하는 것입니다. 테스트를 통과하면 볼 수 없습니다. 그러나 테스트가 실패하면 테스트 결과 개체에서 설명 문자열을 사용할 수 있습니다.
B Rad C

11
줄 연속 문자의 사용이 허용되는 상황정확히 한 가지 있다는 점은 주목할 가치가 있습니다. 긴 with문 : with expr1 as x, \<newline> expr2 as y .... 에서 모든 다른 경우, 단지 괄호 안에 발현를 넣어은 : (a_very_long <newline> + expression)잘 작동, 그리고 훨씬 더 읽기이고 강력한a_very_long \<newline> + expression... 그냥 백 슬래시 후 하나의 공간을 추가하여 후자의 휴식!
Bakuriu

3
@Bakuriu-우와! with성명서를 괄호로 묶을 수 없다는 것을 몰랐습니다 .
mattmc3 2016-12-04

2
@ mattmc3 이유는 간단합니다. 표현이 아닙니다. AFAIK 그것은 말 그대로 줄 바꿈에서 괄호를 사용하는 것이 단순히 옵션이 아닌 유일한 경우입니다.
Bakuriu

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

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


3
이것은 아주 좋은 생각입니다. 너무 읽기 쉬워 보입니다. 지금 시도해보고 IDE에 더 긴 함수 이름이 표시되는지 확인합니다.
byxor

2
불행히도 데코레이터는 테스트가 PyCharm에서 실행되기 전에 적용되지 않습니다. 즉, 테스트 실행기에서 설명하는 이름을 볼 수 없습니다.
byxor

2
난 당신이 장식 할 거라고 생각 wrapper과 함께 @functools.wraps(f).

2
이것은 당신의 케이크를 먹고 그것을 너무 먹는 최고의 솔루션입니다. @BrandonIbbotson이 찾고 있던 모든 기능을 결합합니다. 너무 나쁜 PyCharm은 아직 그다지 어렵지 않습니다.
Dan Lenski

3
더 좋은 방법은 데코레이터를 수정하여 함수의 독 스트링에서 설명적인 이름을 생성하는 것입니다.
닉 스위 팅

33

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


5
나머지 테스트 파일을 린트 할 수 있기 때문에 실제로 환상적인 아이디어입니다. 방금 테스트했고 작동합니다. 또한 긴 메서드 이름의 모든 이점을 유지합니다. --- 내 유일한 관심은 그 팀의 의지가 아니라 보는 등 # nopep8시험 전반에 걸쳐 산재 해 의견을)
byxor

8

에서 메소드 이름을 가져 오므 로 메소드 대신 클래스에 데코레이터 를 적용 할 수 있습니다 .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

7

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

여기 에서 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패키지 , 샘플 사용도 있습니다.


나는 같은 BDD 옵션이 있다는 것을 알고 있다고 언급 할 생각이었다 behave. 그러나 나는 내 질문에서 사람들의주의를 너무 분산시키고 싶지 않았습니다. 정말 멋진 프레임 워크처럼 보이며 앞으로도 사용할 것입니다. 나는 실제로 우리 팀에게이 프로젝트에서 그것을 사용할 수 있는지 물었지만 그들은 "이상하게"보이기 위해 테스트하고 싶지 않았습니다.) --- 전에 pyspec을 본 적이 없습니다. 제안 해 주셔서 감사합니다.
byxor

1
@BrandonIbbotson gotcha, 나는 당신이 그것을 언급하고 싶지 않은 이유를 이해합니다-완벽하게 이해됩니다. pyspecs그건 그렇고, 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 dec

내가보기에 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 (self) :

ClientEventListenerReceivesConnRefusedErrWithoutServer (self) :

EventListenerReceiveConnRefusedErrWithoutServer (self) :

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

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


1
TL; DR-답변의 길이를 다른 답변과 비교하십시오.
MarianD

3
MarianD : 미안하지만 잠시 시간을내어 읽어야하는 OP에게 답이 주어졌고 건설적인 예와 근거로 이름을 줄이기위한 몇 가지 전략을 언급했습니다. 짧은 버전을 원하는 경우 ... "불필요한 단어와 구두점을 피하고 일반적인 단어를 지속적으로 줄 이세요"-충분히 짧습니까?
Charemer

3
파이썬의 unittest 라이브러리를 사용하면 각 테스트 방법은 test테스트 실행자가 선택하지 않는 것으로 시작 해야합니다.
byxor

1
@BrandonIbbotsontest_EventListenerReceiveConnRefusedErrWithoutServer(self):
헨 드리

1
저는 CamelCase를 좋아하지만 PEP 8을 위반하는 것 같습니다. "함수 이름 지정 규칙을 사용하십시오. 가독성을 높이기 위해 필요에 따라 밑줄로 구분 된 단어가있는 소문자를 사용하십시오."
Scooter
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.