Nasz zespół programistów wykorzystuje linter PEP8, który wymaga maksymalnej długości linii 80 znaków .
Kiedy piszę testy jednostkowe w Pythonie, lubię mieć opisowe nazwy metod, aby opisać, co robi każdy test. Jednak często prowadzi to do przekroczenia limitu znaków.
Oto przykład funkcji, która jest zbyt długa ...
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()
Moje opcje:
Możesz po prostu napisać krótsze nazwy metod!
Wiem, ale nie chcę stracić opisowości nazw testów.
Możesz pisać wieloliniowe komentarze nad każdym testem zamiast używać długich nazw!
To przyzwoity pomysł, ale wtedy nie będę mógł zobaczyć nazw testów podczas uruchamiania testów w moim IDE (PyCharm).
Być może możesz kontynuować wiersze za pomocą odwrotnego ukośnika (znak logicznej kontynuacji wiersza).
Niestety nie jest to opcja w Pythonie, jak wspomniano w odpowiedzi Dana.
Możesz przestać lintować swoje testy.
W pewnym sensie ma to sens, ale warto zachęcić do dobrze sformatowanego zestawu testów.
Możesz zwiększyć limit długości linii.
Nasz zespół lubi mieć ten limit, ponieważ pomaga to zachować czytelność kodu na wąskich ekranach, więc nie jest to najlepsza opcja.
Możesz usunąć
test
z początku swoich metod.To nie jest opcja. Program uruchamiający testy Pythona potrzebuje na początek wszystkich metod testowych, w
test
przeciwnym razie ich nie pobierze.Edycja: Niektóre programy uruchamiające testy umożliwiają określenie wyrażenia regularnego podczas wyszukiwania funkcji testowych, chociaż wolałbym tego nie robić, ponieważ jest to dodatkowa konfiguracja dla wszystkich pracujących nad projektem.
Możesz oddzielić EventListener do własnej klasy i przetestować ją oddzielnie.
Odbiornik zdarzeń znajduje się we własnej klasie (i jest testowany). To tylko interfejs, który jest wyzwalany przez zdarzenia zachodzące w ClientConnection. Ten rodzaj sugestii wydaje się mieć dobre intencje, ale jest źle skierowany i nie pomaga w odpowiedzi na pierwotne pytanie.
Możesz użyć struktury BDD, takiej jak Behave . Jest przeznaczony do ekspresyjnych testów.
To prawda i mam nadzieję, że w przyszłości wykorzystam ich więcej. Chociaż nadal chciałbym wiedzieć, jak podzielić nazwy funkcji na linie.
Ostatecznie...
Czy w Pythonie istnieje sposób na podzielenie długiej deklaracji funkcji na wiele wierszy ?
Na przykład...
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()
A może sam będę musiał ugryźć kulę i skrócić ją?
func.__doc__
When applying the guideline would make the code less readable, even for someone who is used to reading code that follows this PEP.
twoim przypadku byłaby to krótsza nazwa funkcji.Odpowiedzi:
Nie, to niemożliwe.
W większości przypadków tak długa nazwa byłaby niepożądana z punktu widzenia czytelności i użyteczności funkcji, chociaż Twój przypadek użycia nazw testów wydaje się całkiem rozsądny.
W leksykalne reguły Pythonie nie pozwalają pojedynczy znak (w tym przypadku identyfikator), aby być podzielony na kilka wierszy. Znak kontynuacji linii logicznej (
\
na końcu linii) może łączyć wiele linii fizycznych w jedną linię logiczną, ale nie może łączyć pojedynczego tokenu w wielu liniach.źródło
with
wypowiedzi:with expr1 as x, \<newline> expr2 as y ...
. We wszystkich innych przypadkach, po prostu zawiń wyrażenie w nawias:(a_very_long <newline> + expression)
działa dobrze, jest znacznie bardziej czytelne i solidniejsze niża_very_long \<newline> + expression
... to drugie przerywa, dodając pojedynczą spację po odwrotnym ukośniku!with
oświadczenia w parens.Państwo mogli również napisać dekorator że mutuje
.__name__
do tej metody.def test_name(name): def wrapper(f): f.__name__ = name return f return wrapper
Wtedy możesz napisać:
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()
opierając się na fakcie, że Python konkatenuje sąsiadujące ze źródłem literały ciągów.
źródło
wrapper
z@functools.wraps(f)
.Zgodnie z odpowiedzią na to pytanie: Jak wyłączyć błąd pep8 w określonym pliku? , użyj komentarza końcowego
# nopep8
lub,# noqa
aby wyłączyć PEP-8 dla długiej linii. Ważne jest, aby wiedzieć, kiedy łamać zasady. Oczywiście Zen Pythona powiedziałby, że „Przypadki specjalne nie są na tyle wyjątkowe, aby łamać zasady”.źródło
# nopep8
komentarz zaśmieca się podczas testów;)Możemy zastosować dekorator do klasy zamiast metody, ponieważ
unittest
pobierz nazwę metody zdir(class)
.Dekorator
decorate_method
przejdzie przez metody klas i zmieni nazwę metody na podstawiefunc_mapping
słownika.Pomyślałem o tym po zobaczeniu odpowiedzi dekoratora od @Sean Vieira, +1 ode mnie
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 uruchomiony z
unittest
jak poniżej pokazał pełną, opisową nazwę funkcji, uważa, że może działać w twoim przypadku, chociaż może nie brzmieć tak elegancko i czytelnie z implementacji>>> unittest.main(verbosity=2) test_that_client_event_listener_receives_connection_refused_error_without_server (__main__.ClientConnectionTest) ... i am client_test ok
źródło
Coś w rodzaju kontekstowego podejścia do problemu. Prezentowany przypadek testowy w rzeczywistości wygląda bardzo podobnie do formatu języka naturalnego opisującego kroki niezbędne do wykonania przypadku testowego.
Zobacz, czy użycie
behave
struktury stylu programowania Behavior Driver miałoby większy sens w tym miejscu. Twój „cecha” może wyglądać następująco (patrz jakgiven
,when
,then
odzwierciedlają to, co trzeba było):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
Istnieje również odpowiedni
pyspecs
pakiet , przykładowe użycie z niedawnej odpowiedzi na pokrewny temat:źródło
behave
. Jednak nie chciałem zbytnio rozpraszać ludzi swoim pytaniem. Wygląda na naprawdę fajny framework i prawdopodobnie użyję go w przyszłości. Właściwie zapytałem mój zespół, czy mógłbym go użyć w tym projekcie, ale nie chcieli, aby testy wyglądały „dziwnie”;) --- Nigdy wcześniej nie widziałem Pyspecs. Dzieki za sugestie.pyspecs
Nawiasem mówiąc, integracja z testowym kodem może być łatwiejsza - bardziej „pythonowy” sposób robienia BDD - te pliki funkcji nie są potrzebne. Dzięki!Potrzeba takich nazw może wskazywać na inne zapachy.
class ClientConnectionTest(unittest.TestCase): def test_that_client_event_listener_receives_connection_refused_error_without_server(self): ...
ClientConnectionTest
brzmi dość obszernie (i wcale nie jak jednostka testowalna) i prawdopodobnie jest dużą klasą z wieloma testami wewnątrz, które można by zmienić. Lubię to:class ClientEventListenerTest(unittest.TestCase): def receives_connection_refused_without_server(self): ...
„Test” nie jest użyteczny w nazwie, ponieważ jest sugerowany.
Po całym kodzie, który mi dałeś, moja ostatnia rada brzmi: zrefaktoryzuj swój kod testowy, a następnie wróć do swojego problemu (jeśli nadal istnieje).
źródło
test
lub program uruchamiający testy ich nie odbiera.Krótsza nazwa funkcji ma wiele zalet. Pomyśl o tym, co jest naprawdę potrzebne w twojej rzeczywistej nazwie funkcji i co jest już dostarczone.
Na pewno już wiesz, że to test, kiedy go uruchomisz? Czy naprawdę musisz używać podkreśleń? czy słowa takie jak „to” są naprawdę potrzebne do zrozumienia nazwy? czy skrzynka wielbłąda byłaby równie czytelna? co powiesz na pierwszy przykład poniżej jako przepisanie powyższego (liczba znaków = 79): Jeszcze skuteczniejsze jest zaakceptowanie konwencji używania skrótów dla małego zbioru popularnych słów, np. Connection = Conn, Error = Err. Używając skrótów, należy uważać na kontekst i używać ich tylko wtedy, gdy nie ma możliwości pomylenia - drugi przykład poniżej. Jeśli zaakceptujesz, że nie ma potrzeby wspominania klienta jako podmiotu testowego w nazwie metody, ponieważ ta informacja znajduje się w nazwie klasy, trzeci przykład może być odpowiedni. (54) znaków.
ClientEventListenerReceivesConnectionRefusedErrorWithoutServer (self):
ClientEventListenerReceivesConnRefusedErrWithoutServer (self):
EventListenerReceiveConnRefusedErrWithoutServer (self):
Zgodziłbym się również z sugestią B Rad C „użyj nazwy opisowej jako argumentu msg kwarg w self.assert” Powinieneś być zainteresowany wyświetlaniem wyników testów zakończonych niepowodzeniem tylko wtedy, gdy uruchomiony jest zestaw testowy. Weryfikacja, że masz napisane wszystkie niezbędne testy, nie powinna zależeć od posiadania tak szczegółowych nazw metod.
PS Prawdopodobnie również usunąłbym „WithoutServer” jako zbędny. Czy program obsługi zdarzeń klienta nie powinien odbierać zdarzenia w przypadku, gdy z jakiegokolwiek powodu nie można skontaktować się z serwerem? (chociaż myślę, że byłoby lepiej, gdyby klient nie mógł połączyć się z serwerem, otrzymywał pewnego rodzaju `` połączenie niedostępne '', odmowa połączenia sugeruje, że serwer można znaleźć, ale sam odmawia połączenia).
źródło
test
przeciwnym razie program uruchamiający testy jej nie pobierze .test_EventListenerReceiveConnRefusedErrWithoutServer(self):