Unser Entwicklungsteam verwendet einen PEP8-Linter, der eine maximale Zeilenlänge von 80 Zeichen erfordert .
Wenn ich Unit-Tests in Python schreibe, möchte ich beschreibende Methodennamen haben, um zu beschreiben, was jeder Test tut. Dies führt jedoch oft dazu, dass ich die Zeichenbeschränkung überschreite.
Hier ist ein Beispiel für eine Funktion, die zu lang ist ...
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()
Meine Optionen:
Sie könnten einfach kürzere Methodennamen schreiben!
Ich weiß, aber ich möchte die Beschreiblichkeit der Testnamen nicht verlieren.
Sie können über jeden Test mehrzeilige Kommentare schreiben, anstatt lange Namen zu verwenden!
Dies ist eine anständige Idee, aber dann kann ich die Testnamen nicht sehen, wenn ich die Tests in meiner IDE (PyCharm) ausführe.
Vielleicht können Sie die Zeilen mit einem Backslash (einem logischen Zeilenfortsetzungszeichen) fortsetzen.
Leider ist dies in Python keine Option, wie in Dans Antwort erwähnt.
Sie könnten aufhören, Ihre Tests zu färben.
Dies ist in gewisser Hinsicht sinnvoll, aber es ist schön, eine gut formatierte Testsuite zu fördern.
Sie können die Zeilenlängenbeschränkung erhöhen.
Unser Team mag das Limit, weil es hilft, den Code auf schmalen Displays lesbar zu halten, daher ist dies nicht die beste Option.
Sie können
test
vom Anfang Ihrer Methoden entfernen .Dies ist keine Option. Der Testläufer von Python benötigt zunächst alle Testmethoden, sonst werden
test
sie nicht erfasst.Bearbeiten: Bei einigen Testläufern können Sie bei der Suche nach Testfunktionen einen regulären Ausdruck angeben, obwohl ich dies lieber nicht tun möchte, da dies ein zusätzliches Setup für alle ist, die an dem Projekt arbeiten.
Sie können den EventListener in eine eigene Klasse aufteilen und separat testen.
Der Event Listener ist in einer eigenen Klasse (und wird getestet). Es ist nur eine Schnittstelle, die durch Ereignisse in ClientConnection ausgelöst wird. Diese Art von Vorschlag scheint eine gute Absicht zu haben, ist jedoch fehlgeleitet und hilft nicht bei der Beantwortung der ursprünglichen Frage.
Sie können ein BDD-Framework wie Behave verwenden . Es ist für Ausdruckstests konzipiert.
Dies ist wahr, und ich hoffe, dass ich in Zukunft mehr davon verwenden kann. Obwohl ich immer noch gerne wissen möchte, wie man Funktionsnamen über Zeilen verteilt.
Letzten Endes...
Gibt es in Python eine Möglichkeit, eine lange Funktionsdeklaration auf mehrere Zeilen aufzuteilen ?
Zum Beispiel...
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()
Oder muss ich die Kugel beißen und selbst kürzen?
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.
In Ihrem Fall würde dies einen kürzeren Funktionsnamen verwenden.Antworten:
Nein das ist nicht möglich.
In den meisten Fällen wäre ein derart langer Name unter dem Gesichtspunkt der Lesbarkeit und Verwendbarkeit der Funktion unerwünscht, obwohl Ihr Anwendungsfall für Testnamen ziemlich vernünftig erscheint.
Die lexikalischen Regeln von Python erlauben nicht, dass ein einzelnes Token (in diesem Fall ein Bezeichner) auf mehrere Zeilen aufgeteilt wird. Das logische Zeilenfortsetzungszeichen (
\
am Ende einer Zeile) kann mehrere physische Zeilen zu einer einzigen logischen Zeile verbinden, jedoch nicht ein einzelnes Token über mehrere Zeilen hinweg.quelle
with
Anweisungen :with expr1 as x, \<newline> expr2 as y ...
. In allen anderen Fällen bitte, wickeln Sie einfach den Ausdruck in Klammern:(a_very_long <newline> + expression)
funktioniert gut, und ist viel besser lesbar und robust danna_very_long \<newline> + expression
... die letzteren Pausen durch nur einen einzigen Raum nach dem umgekehrten Schrägstrich addieren!with
Aussage nicht in Parens einwickeln können .Sie können auch einen Dekorator schreiben, der
.__name__
für die Methode mutiert .def test_name(name): def wrapper(f): f.__name__ = name return f return wrapper
Dann könnten Sie schreiben:
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()
unter Berufung auf die Tatsache, dass Python an Quellen angrenzende String-Literale verkettet.
quelle
wrapper
mit@functools.wraps(f)
.Gemäß der Antwort auf diese Frage: Wie deaktiviere ich einen pep8-Fehler in einer bestimmten Datei? Verwenden Sie den
# nopep8
oder den# noqa
nachfolgenden Kommentar, um PEP-8 für eine lange Zeile zu deaktivieren. Es ist wichtig zu wissen, wann die Regeln gebrochen werden müssen. Natürlich würde das Zen von Python Ihnen sagen, dass "Sonderfälle nicht speziell genug sind, um die Regeln zu brechen."quelle
# nopep8
Kommentar während der Tests nicht gerne sehen würde ;)Wir können den Dekorator anstelle der Methode auf die Klasse anwenden , da wir
unittest
den Methodennamen von erhaltendir(class)
.Der Dekorateur
decorate_method
durchläuft die Klassenmethoden und benennt den Namen der Methode basierend auf demfunc_mapping
Wörterbuch um.Ich dachte daran, nachdem ich gesehen hatte, wie der Dekorateur von @Sean Vieira antwortete, +1 von mir
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()
Der Testlauf mit dem folgenden
unittest
Namen hat den vollständigen Namen der beschreibenden Funktion angezeigt. Er ist der Ansicht, dass er für Ihren Fall möglicherweise funktioniert, obwohl er in der Implementierung möglicherweise nicht so elegant und lesbar klingt>>> unittest.main(verbosity=2) test_that_client_event_listener_receives_connection_refused_error_without_server (__main__.ClientConnectionTest) ... i am client_test ok
quelle
Eine Art kontextspezifischer Ansatz für das Problem. Der von Ihnen vorgestellte Testfall ähnelt tatsächlich einem Format in natürlicher Sprache, in dem die erforderlichen Schritte für einen Testfall beschrieben werden.
Prüfen Sie, ob die Verwendung des
behave
Frameworks für den Entwicklungstyp des Verhaltenstreibers hier sinnvoller ist. Ihre „Feature“ aussehen könnte (wie diegiven
,when
,then
reflektieren , was man hatte):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
Es gibt auch relevante
pyspecs
Paket- und Verwendungsbeispiele aus einer aktuellen Antwort zu einem verwandten Thema:quelle
behave
. Allerdings wollte ich die Leute in meiner Frage nicht zu sehr ablenken. Es sieht aus wie ein wirklich schönes Framework und ich werde es wahrscheinlich in Zukunft verwenden. Ich habe mein Team tatsächlich gefragt, ob ich es in diesem Projekt verwenden könnte, aber sie wollten nicht testen, ob es "komisch" aussieht;) --- Ich habe noch nie Pyspecs gesehen. Danke für den Vorschlag.pyspecs
Übrigens ist die Integration in Ihre Testcodebasis möglicherweise einfacher - eine "Python" -Methode für BDD -, aber diese Feature-Dateien werden nicht benötigt. Vielen Dank!Die Notwendigkeit dieser Art von Namen kann auf andere Gerüche hinweisen.
class ClientConnectionTest(unittest.TestCase): def test_that_client_event_listener_receives_connection_refused_error_without_server(self): ...
ClientConnectionTest
klingt ziemlich breit (und überhaupt nicht wie eine testbare Einheit) und ist wahrscheinlich eine große Klasse mit vielen Tests, die neu ausgerichtet werden könnten. So was:class ClientEventListenerTest(unittest.TestCase): def receives_connection_refused_without_server(self): ...
"Test" ist im Namen nicht nützlich, weil es impliziert ist.
Bei all dem Code, den Sie mir gegeben haben, lautet mein letzter Rat: Überarbeiten Sie Ihren Testcode und wiederholen Sie Ihr Problem (falls er noch vorhanden ist).
quelle
test
oder der Testläufer nimmt sie nicht auf.Die Lösung mit kürzeren Funktionsnamen hat viele Vorteile. Überlegen Sie, was in Ihrem tatsächlichen Funktionsnamen wirklich benötigt wird und was bereits geliefert wird.
Sicher wissen Sie bereits, dass es ein Test ist, wenn Sie ihn ausführen? Müssen Sie wirklich Unterstriche verwenden? Sind solche Wörter wirklich erforderlich, damit der Name verstanden wird? Wäre Kamelkoffer genauso lesbar? Wie wäre es mit dem ersten Beispiel unten als Umschreibung des Obigen (Zeichenanzahl = 79): Das Akzeptieren einer Konvention zur Verwendung von Abkürzungen für eine kleine Sammlung gebräuchlicher Wörter ist noch effektiver, z. B. Verbindung = Verbindung, Fehler = Fehler. Wenn Sie Abkürzungen verwenden, müssen Sie den Kontext berücksichtigen und sie nur verwenden, wenn keine Verwechslungsgefahr besteht - zweites Beispiel unten. Wenn Sie akzeptieren, dass der Client im Methodennamen nicht als Testobjekt angegeben werden muss, da sich diese Informationen im Klassennamen befinden, ist das dritte Beispiel möglicherweise angemessen. (54) Zeichen.
ClientEventListenerReceivesConnectionRefusedErrorWithoutServer (self):
ClientEventListenerReceivesConnRefusedErrWithoutServer (self):
EventListenerReceiveConnRefusedErrWithoutServer (self):
Ich würde auch dem Vorschlag von B Rad C zustimmen: "Verwenden Sie einen beschreibenden Namen als msg kwarg arg in in einem self.assert." Sie sollten nur daran interessiert sein, die Ausgabe von fehlgeschlagenen Tests zu sehen, wenn die Testsuite ausgeführt wird. Die Überprüfung, ob Sie alle erforderlichen Tests geschrieben haben, sollte nicht davon abhängen, ob die Methodennamen so detailliert sind.
PS Ich würde wahrscheinlich auch 'WithoutServer' als überflüssig entfernen. Sollte der Client-Ereignishandler das Ereignis nicht empfangen, falls der Server aus irgendeinem Grund nicht erreichbar ist? (Obwohl ich denke, dass es besser wäre, wenn der Client keine Verbindung zu einem Server herstellen kann, erhält er eine Art "Verbindung nicht verfügbar". Die verweigerte Verbindung deutet darauf hin, dass der Server gefunden werden kann, lehnt jedoch die Verbindung selbst ab.)
quelle
test
sonst nimmt der Testläufer sie nicht auf.test_EventListenerReceiveConnRefusedErrWithoutServer(self):