Wohin gehen die Python-Unit-Tests?

490

Wenn Sie eine Bibliothek oder eine App schreiben, wohin gehen die Unit-Test-Dateien?

Es ist schön, die Testdateien vom Haupt-App-Code zu trennen, aber es ist umständlich, sie in einem Unterverzeichnis "tests" im App-Stammverzeichnis abzulegen, da es schwieriger ist, die zu testenden Module zu importieren.

Gibt es hier eine bewährte Methode?

Schreibgeschützt
quelle
4
Es gibt viele akzeptable Antworten auf diese Frage, und das macht sie nicht zu einer Frage für eine Q / A-Site. Die Leute müssen verstehen, dass nicht alle wichtigen hilfreichen Fragen zu SO gestellt werden können. Die Former entscheiden sich dafür und wir müssen ihre Regeln befolgen.
Wouter J
121
Die Leute müssen auch verstehen, dass es vielleicht besser ist, einfach mit dem Fluss zu gehen, als sich an die offizielle SO-Doktrin zu halten, wenn SO-Seiten als Top-Hit für eine Google-Suche enden.
Christopher Barber
6
@ChristopherBarber-Leute müssen auch verstehen, dass Entscheidungen und aggressives Schließen zu einem so schrecklichen Ruf als Website geführt haben, dass ich lieber keine Fragen stellen möchte, es sei denn, ich habe wirklich, wirklich keine bessere Option.
Stefano Borini

Antworten:

200

Für eine Datei module.pysollte der test_module.pyKomponententest normalerweise gemäß den Pythonic-Namenskonventionen aufgerufen werden.

Es gibt mehrere allgemein akzeptierte Orte test_module.py:

  1. Im selben Verzeichnis wie module.py.
  2. In ../tests/test_module.py(auf derselben Ebene wie das Codeverzeichnis).
  3. In tests/test_module.py(eine Ebene unter dem Codeverzeichnis).

Ich bevorzuge # 1 wegen seiner Einfachheit, die Tests zu finden und zu importieren. Welches Build-System Sie auch verwenden, es kann einfach so konfiguriert werden, dass Dateien beginnend mit ausgeführt werden test_. Tatsächlich ist das Standardmuster unittestfür die Testerkennungtest*.py .

user6868
quelle
12
Das Protokoll load_tests sucht standardmäßig nach Dateien mit dem Namen test * .py. Auch dieses Top-Google-Ergebnis und diese unittest Dokumentation verwenden beide das Format test_module.py.
dfarrell07
6
Wenn Sie foo_test.py verwenden, können Sie bei Verwendung der Tab-Vervollständigung ein oder zwei Tastenanschläge speichern, da Sie nicht über eine Reihe von Dateien verfügen, die mit 'test_' beginnen.
Wacholder
11
@juniper, wenn Sie Ihren Gedanken folgen, wird module_test beim Codieren automatisch vervollständigt. Das kann verwirrend oder nervig sein.
Medeiros
11
Bei der Bereitstellung von Code möchten wir die Tests nicht an unseren Produktionsstandorten bereitstellen. Es ist also einfach, sie in einem Verzeichnis './tests/test_blah.py' zu haben, wenn wir Bereitstellungen durchführen. AUCH bei einigen Tests werden Beispieldatendateien verwendet, und es ist wichtig, diese in einem Testverzeichnis zu haben, damit wir keine Testdaten bereitstellen.
Kevin J. Rice
1
@ KevinJ.Rice Sollten Sie nicht testen, ob der Code an Ihren Produktionsstandorten funktioniert?
Endolith
67

Nur 1 Testdatei

Wenn nur 1 Testdatei vorhanden ist, wird empfohlen, diese in einem Verzeichnis der obersten Ebene abzulegen:

module/
    lib/
        __init__.py
        module.py
    test.py

Führen Sie den Test in der CLI aus

python test.py

Viele Testdateien

Wenn viele Testdateien vorhanden sind, legen Sie diese in einem testsOrdner ab:

module/
    lib/
        __init__.py
        module.py
    tests/
        test_module.py
        test_module_function.py
# test_module.py

import unittest
from lib import module

class TestModule(unittest.TestCase):
    def test_module(self):
        pass

if __name__ == '__main__':
    unittest.main()

Führen Sie den Test in der CLI aus

# In top-level /module/ folder
python -m tests.test_module
python -m tests.test_module_function

Verwenden unittest discovery

unittest discovery findet alle Tests im Paketordner.

Erstellen Sie einen __init__.pyIn- tests/Ordner

module/
    lib/
        __init__.py
        module.py
    tests/
        __init__.py
        test_module.py
        test_module_function.py

Führen Sie den Test in der CLI aus

# In top-level /module/ folder

# -s, --start-directory (default current directory)
# -p, --pattern (default test*.py)

python -m unittest discover

Referenz

Unit Test Framework

Steely Wing
quelle
51

Es ist üblich, das Testverzeichnis im selben übergeordneten Verzeichnis wie Ihr Modul / Paket abzulegen. Wenn Ihr Modul also foo.py heißt, sieht Ihr Verzeichnislayout folgendermaßen aus:

parent_dir/
  foo.py
  tests/

Natürlich gibt es keinen Weg, dies zu tun. Sie können auch ein Test-Unterverzeichnis erstellen und das Modul mit dem absoluten Import importieren .

Wo immer Sie Ihre Tests durchführen, würde ich empfehlen, dass Sie die Nase verwenden , um sie auszuführen. Nose durchsucht Ihre Verzeichnisse nach Tests. Auf diese Weise können Sie Tests dort durchführen, wo sie organisatorisch am sinnvollsten sind.

Cristian
quelle
16
Ich würde das gerne machen, aber ich kann es nicht zum Laufen bringen. Um die Tests auszuführen, bin ich im Elternverzeichnis und gebe Folgendes ein: "python tests \ foo_test.py" und in foo_test.py: "aus ..foo importiere dies, das, das andere" Das schlägt fehl mit: "ValueError: Attempted relativer Import in Nicht-Paket "Sowohl parent_dir als auch tests haben eine init .py, daher bin ich mir nicht sicher, warum sie keine Pakete sind. Ich vermute, das liegt daran, dass das Skript der obersten Ebene, das Sie über die Befehlszeile ausführen, nicht als Teil eines Pakets betrachtet werden kann, selbst wenn es sich in einem Verzeichnis mit einer init .py befindet. Wie führe ich die Tests durch? Ich arbeite heute an Windows, segne meine Baumwollsocken.
Jonathan Hartley
4
Der beste Weg, Unit-Tests durchzuführen, besteht darin, Ihre Bibliothek / Ihr Programm zu installieren und dann Unit-Tests mit der Nase durchzuführen. Ich würde virtualenv und virtualenvwrapper empfehlen, um dies viel einfacher zu machen.
Cristian
@Tartley - Sie benötigen eine init .py-Datei in Ihrem ' Test' -Verzeichnis, damit Absolution-Importe funktionieren. Ich habe diese Methode mit der Nase arbeiten, deshalb bin ich mir nicht sicher, warum Sie Probleme haben.
cmcginty
4
Danke Casey - aber ich habe eine init .py Datei in allen relevanten Verzeichnissen. Ich weiß nicht, was ich falsch mache, aber ich habe dieses Problem bei all meinen Python-Projekten und kann nicht verstehen, warum es sonst niemand tut. Oh mein Lieber.
Jonathan Hartley
8
Eine Lösung für mein Problem mit Python2.6 oder neuer besteht darin, die Tests vom Stammverzeichnis Ihres Projekts aus auszuführen: python -m project.package.tests.module_tests (anstelle von python project / package / tests / module_tests.py) . Dadurch wird das Verzeichnis des Testmoduls in den Pfad eingefügt, sodass die Tests einen relativen Import in das übergeordnete Verzeichnis durchführen können, um das zu testende Modul abzurufen.
Jonathan Hartley
32

Wir hatten die gleiche Frage beim Schreiben von Pythoscope ( https://pypi.org/project/pythoscope/ ), das Unit-Tests für Python-Programme generiert. Wir haben Leute zu den Tests in der Python-Liste befragt, bevor wir ein Verzeichnis ausgewählt haben. Es gab viele verschiedene Meinungen. Am Ende haben wir uns entschieden, ein "Tests" -Verzeichnis in dasselbe Verzeichnis wie den Quellcode zu stellen. In diesem Verzeichnis generieren wir eine Testdatei für jedes Modul im übergeordneten Verzeichnis.

Paul Hildebrandt
quelle
2
Genial! Ich habe gerade Pythoscope (tolles Logo) auf einem alten Code ausgeführt und es war erstaunlich. Sofortige Best Practices und Sie können andere Entwickler dazu bringen, die jetzt fehlgeschlagenen Teststubs auszufüllen. Jetzt, um es in Bambus anzuschließen? :)
Will
27

Ich neige auch dazu, meine Komponententests in die Datei selbst zu schreiben, wie Jeremy Cantrell oben bemerkt, obwohl ich dazu neige, die Testfunktion nicht in den Hauptteil zu stellen, sondern alles in eine

if __name__ == '__main__':
   do tests...

Block. Dies führt dazu, dass der Datei Dokumentation als 'Beispielcode' für die Verwendung der zu testenden Python-Datei hinzugefügt wird.

Ich sollte hinzufügen, ich neige dazu, sehr enge Module / Klassen zu schreiben. Wenn Ihre Module eine sehr große Anzahl von Tests erfordern, können Sie sie in eine andere einfügen, aber selbst dann würde ich noch hinzufügen:

if __name__ == '__main__':
   import tests.thisModule
   tests.thisModule.runtests

Auf diese Weise weiß jeder, der Ihren Quellcode liest, wo er nach dem Testcode suchen muss.

Thomas Andrews
quelle
"wie Jeremy Cantrell oben bemerkt" wo?
Endolith
Ich mag diese Arbeitsweise. Der einfachste Editor kann so konfiguriert werden, dass Ihre Datei mit einem Hotkey ausgeführt wird. Wenn Sie also den Code anzeigen, können Sie die Tests sofort ausführen. Leider kann dies in anderen Sprachen als Python schrecklich aussehen. Wenn Sie sich also in einem C ++ - oder Java-Shop befinden, können Ihre Kollegen dies missbilligen. Es funktioniert auch nicht gut mit Tools zur Codeabdeckung.
Keeely
19

Hin und wieder überprüfe ich das Thema der Testplatzierung, und jedes Mal empfiehlt die Mehrheit eine separate Ordnerstruktur neben dem Bibliothekscode, aber ich finde, dass die Argumente jedes Mal gleich und nicht so überzeugend sind. Am Ende platziere ich meine Testmodule irgendwo neben den Kernmodulen.

Der Hauptgrund dafür ist: Refactoring .

Wenn ich Dinge bewege, möchte ich, dass sich Testmodule mit dem Code bewegen. Es ist leicht, Tests zu verlieren, wenn sie sich in einem separaten Baum befinden. Seien wir ehrlich, früher oder später haben Sie eine völlig andere Ordnerstruktur wie Django , Kolben und viele andere. Was in Ordnung ist, wenn es dich nicht interessiert.

Die Hauptfrage, die Sie sich stellen sollten, lautet:

Schreibe ich:

  • a) wiederverwendbare Bibliothek oder
  • b) ein Projekt aufbauen, indem einige halbgetrennte Module gebündelt werden?

Wenn ein:

Ein separater Ordner und der zusätzliche Aufwand zur Aufrechterhaltung seiner Struktur sind möglicherweise besser geeignet. Niemand wird sich darüber beschweren, dass Ihre Tests für die Produktion bereitgestellt werden .

Es ist aber genauso einfach, Tests von der Verteilung auszuschließen, wenn sie mit den Kernordnern gemischt werden. füge dies in die setup.py ein :

find_packages("src", exclude=["*.tests", "*.tests.*", "tests.*", "tests"]) 

Wenn b:

Sie möchten vielleicht - wie jeder von uns -, dass Sie wiederverwendbare Bibliotheken schreiben, aber die meiste Zeit ist ihr Leben an das Leben des Projekts gebunden. Die Fähigkeit, Ihr Projekt einfach zu warten, sollte Priorität haben.

Wenn Sie dann gute Arbeit geleistet haben und Ihr Modul für ein anderes Projekt gut geeignet ist, wird es wahrscheinlich in dieses neue Projekt kopiert - nicht gegabelt oder in eine separate Bibliothek verschoben - und Tests verschoben, die daneben in derselben Ordnerstruktur liegen ist einfach im Vergleich zum Auffischen von Tests in einem Durcheinander, zu dem ein separater Testordner geworden war. (Sie können argumentieren, dass es in erster Linie kein Chaos sein sollte, aber lassen Sie uns hier realistisch sein).

Sie haben also immer noch die Wahl, aber ich würde argumentieren, dass Sie mit gemischten Tests dieselben Ergebnisse erzielen wie mit einem separaten Ordner, jedoch mit weniger Aufwand, um die Dinge sauber zu halten.

Janusz Skonieczny
quelle
1
Was ist eigentlich das Problem bei der Bereitstellung von Tests für die Produktion? Nach meiner Erfahrung ist es sehr nützlich: Ermöglicht Benutzern das tatsächliche Ausführen von Tests in ihrer Umgebung. Wenn Sie Fehlerberichte erhalten, können Sie den Benutzer bitten, die Testsuite auszuführen, um sicherzustellen, dass dort alles in Ordnung ist, und Hot Patches für den Test senden Suite direkt ... außerdem ist es nicht , weil Sie Ihre Tests in module.tests setzen , dass es wird in der Produktion am Ende, wenn Sie etwas falsch in Ihrer Setup - Datei haben ...
anarcat
2
Wie ich in meiner Antwort geschrieben habe, trenne ich normalerweise keine Tests. Aber. Das Einfügen von Tests in das Distributionspaket kann dazu führen, dass Dinge ausgeführt werden, die Sie normalerweise in der Produktionsumgebung nicht benötigen (z. B. Code auf Modulebene). Es hängt natürlich davon ab, wie Tests geschrieben werden, aber um auf der sicheren Seite zu sein, wenn Sie sie weglassen, wird niemand versehentlich etwas Schädliches ausführen. Ich bin nicht dagegen, Tests in Distributionen aufzunehmen, aber ich verstehe, dass dies als Faustregel sicherer ist. Und wenn Sie sie in einem separaten Ordner ablegen, ist es ganz einfach, sie nicht in dist aufzunehmen.
Janusz Skonieczny
15

Ich verwende ein tests/Verzeichnis und importiere dann die Hauptanwendungsmodule mit relativen Importen. In MyApp / tests / foo.py könnte es also Folgendes geben:

from .. import foo

um das MyApp.fooModul zu importieren .

John Millikin
quelle
5
"ValueError: Versuchter relativer Import in Nicht-Paket"
cprn
12

Ich glaube nicht, dass es eine etablierte "Best Practice" gibt.

Ich habe meine Tests in einem anderen Verzeichnis außerhalb des App-Codes abgelegt. Ich füge dann das Hauptverzeichnis der App zu sys.path hinzu (damit Sie die Module von überall importieren können) in meinem Testrunner-Skript (das auch einige andere Aufgaben ausführt), bevor ich alle Tests ausführe. Auf diese Weise muss ich das Testverzeichnis nie aus dem Hauptcode entfernen, wenn ich es freigebe, was mir Zeit und Mühe spart, wenn auch nur eine winzige Menge.

Westbrook
quelle
3
Ich füge dann das Haupt-App-Verzeichnis zu sys.path hinzu. Das Problem ist, wenn Ihre Tests einige Hilfsmodule enthalten (wie z. B. den Test-Webserver) und Sie solche Hilfsmodule aus Ihren richtigen Tests importieren müssen.
Piotr Dobrogost
So sieht es für mich aus:os.sys.path.append(os.dirname('..'))
Matt M.
11

Aufgrund meiner Erfahrung bei der Entwicklung von Test-Frameworks in Python würde ich vorschlagen, Python-Unit-Tests in einem separaten Verzeichnis abzulegen. Pflegen Sie eine symmetrische Verzeichnisstruktur. Dies wäre hilfreich, wenn nur die Kernbibliotheken und nicht die Komponententests verpackt werden sollen. Das Folgende wird durch ein schematisches Diagramm implementiert.

                              <Main Package>
                               /          \
                              /            \
                            lib           tests
                            /                \
             [module1.py, module2.py,  [ut_module1.py, ut_module2.py,
              module3.py  module4.py,   ut_module3.py, ut_module.py]
              __init__.py]

Auf diese Weise können Sie beim Packen dieser Bibliotheken mit einer Drehzahl nur die Hauptbibliotheksmodule (nur) packen. Dies trägt zur Wartbarkeit insbesondere in agilen Umgebungen bei.

Rahul Biswas
quelle
1
Ich habe erkannt, dass ein möglicher Vorteil dieses Ansatzes darin besteht, dass die Tests als eigenständige Anwendung entwickelt (und möglicherweise sogar versioniert) werden können. Zu jedem Vorteil gibt es natürlich einen Nachteil: Die Aufrechterhaltung der Symmetrie bedeutet im Grunde genommen "Double Entry Accounting" und macht das Refactoring mehr zur Pflicht.
Jasha
Weiche Folgefrage: Warum ist die agile Umgebung für diesen Ansatz besonders gut geeignet? (dh was ist mit dem agilen Workflow, der eine symmetrische Verzeichnisstruktur so vorteilhaft macht?)
Jasha
11

Ich empfehle Ihnen, einige der wichtigsten Python-Projekte auf GitHub zu überprüfen und einige Ideen zu erhalten.

Wenn Ihr Code größer wird und Sie mehr Bibliotheken hinzufügen, ist es besser, einen Testordner in demselben Verzeichnis zu erstellen, in dem Sie setup.py haben, und Ihre Projektverzeichnisstruktur für jeden Testtyp zu spiegeln (unittest, Integration, ...).

Zum Beispiel, wenn Sie eine Verzeichnisstruktur haben wie:

myPackage/
    myapp/
       moduleA/
          __init__.py
          module_A.py
       moduleB/
          __init__.py
          module_B.py
setup.py

Nach dem Hinzufügen des Testordners haben Sie eine Verzeichnisstruktur wie:

myPackage/
    myapp/
       moduleA/
          __init__.py
          module_A.py
       moduleB/
          __init__.py
          module_B.py
test/
   unit/
      myapp/
         moduleA/
            module_A_test.py
         moduleB/
            module_B_test.py
   integration/
          myapp/
             moduleA/
                module_A_test.py
             moduleB/
                module_B_test.py
setup.py

Viele richtig geschriebene Python-Pakete verwenden dieselbe Struktur. Ein sehr gutes Beispiel ist das Boto-Paket. Überprüfen Sie https://github.com/boto/boto

Arash
quelle
1
Es wird empfohlen, "tests" anstelle von "test" zu verwenden, da "test" ein in Python integriertes Modul ist. docs.python.org/2/library/test.html
brodul
Nicht immer .. matplotlibhat es zum Beispiel unter matplotlib/lib/matplotlib/tests( github.com/matplotlib/matplotlib/tree/… ), sklearnhat es unter scikitelearn/sklearn/tests( github.com/scikit-learn/scikit-learn/tree/master/sklearn/tests )
alpha_989
7

Wie ich es mache...

Ordnerstruktur:

project/
    src/
        code.py
    tests/
    setup.py

Setup.py zeigt auf src / als Speicherort meiner Projektmodule, dann starte ich:

setup.py develop

Dadurch wird mein Projekt in Site-Pakete eingefügt und auf meine Arbeitskopie verwiesen. Um meine Tests durchzuführen, benutze ich:

setup.py tests

Verwenden Sie den von mir konfigurierten Testläufer.

Dale Reidy
quelle
Sie scheinen den Begriff "Modul" überschrieben zu haben. Die meisten Python-Programmierer würden wahrscheinlich denken, dass das Modul die von Ihnen aufgerufene Datei ist code.py. Es wäre sinnvoller, das Verzeichnis der obersten Ebene "Projekt" zu nennen.
Blokeley
4

Ich bevorzuge Toplevel-Testverzeichnis. Dies bedeutet, dass Importe etwas schwieriger werden. Dafür habe ich zwei Lösungen:

  1. Verwenden Sie Setuptools. Dann können Sie test_suite='tests.runalltests.suite'in gehen setup()und die Tests einfach ausführen:python setup.py test
  2. Stellen Sie PYTHONPATH ein, wenn Sie die Tests ausführen: PYTHONPATH=. python tests/runalltests.py

So wird dieses Zeug von Code in M2Crypto unterstützt:

Wenn Sie lieber Tests mit Nasentests durchführen möchten, müssen Sie möglicherweise etwas anderes tun.

bstpierre
quelle
3

Wir gebrauchen

app/src/code.py
app/testing/code_test.py 
app/docs/..

In jede Testdatei fügen wir ../src/ein sys.path. Es ist nicht die schönste Lösung, funktioniert aber. Ich denke, es wäre großartig, wenn jemand mit so etwas wie Maven in Java auftauchen würde, das Ihnen Standardkonventionen gibt, die einfach funktionieren, egal an welchem ​​Projekt Sie arbeiten.

André
quelle
1

Wenn die Tests einfach sind, fügen Sie sie einfach in die Dokumentzeichenfolge ein - die meisten Test-Frameworks für Python können dies verwenden:

>>> import module
>>> module.method('test')
'testresult'

Für andere komplexere Tests würde ich sie entweder ein- ../tests/test_module.pyoder einfügen tests/test_module.py.


quelle
1

In C # habe ich die Tests im Allgemeinen in eine separate Assembly aufgeteilt.

In Python habe ich bisher entweder Doctests geschrieben, bei denen sich der Test in der Dokumentzeichenfolge einer Funktion befindet, oder sie in den if __name__ == "__main__"Block am unteren Rand des Moduls eingefügt .

George V. Reilly
quelle
0

Wenn ich ein Paket namens "foo" schreibe, werde ich Unit-Tests in ein separates Paket "foo_test" einfügen. Module und Unterpakete haben dann denselben Namen wie das SUT-Paketmodul. Beispielsweise finden sich Tests für ein Modul foo.xy in foo_test.xy. Die Dateien __init__.py jedes Testpakets enthalten dann eine AllTests-Suite, die alle Testsuiten des Pakets enthält. setuptools bietet eine bequeme Möglichkeit, das Haupttestpaket anzugeben, sodass Sie nach "python setup.py development" einfach "python setup.py test" oder "python setup.py test -s foo_test.x.SomeTestSuite" verwenden können nur eine bestimmte Suite.

Sebastian Rittau
quelle
0

Ich lege meine Tests in dasselbe Verzeichnis wie den zu testenden Code (CUT). für foo.pydie Tests wird in foo_ut.pyoder ähnlich sein. (Ich optimiere den Testerkennungsprozess, um diese zu finden.)

Dadurch werden die Tests direkt neben dem Code in einer Verzeichnisliste angezeigt, wodurch deutlich wird, dass Tests vorhanden sind, und das Öffnen der Tests wird so einfach wie möglich, wenn sie sich in einer separaten Datei befinden. (Für Befehlszeileneditoren vim foo*und bei Verwendung eines grafischen Dateisystembrowsers klicken Sie einfach auf die CUT-Datei und dann auf die unmittelbar benachbarte Testdatei.)

Wie andere bereits betont haben , erleichtert dies auch das Refactorisieren und Extrahieren des Codes zur Verwendung an anderer Stelle, falls dies jemals erforderlich sein sollte.

Ich mag die Idee, Tests in einen völlig anderen Verzeichnisbaum zu stellen, wirklich nicht. Warum ist es für Entwickler schwieriger als nötig, die Tests zu öffnen, wenn sie die Datei mit dem CUT öffnen? Es ist nicht so, dass die überwiegende Mehrheit der Entwickler so sehr daran interessiert ist, Tests zu schreiben oder zu optimieren, dass sie jede Barriere ignorieren, anstatt die Barriere als Ausrede zu benutzen. (Im Gegenteil, meiner Erfahrung nach kenne ich viele Entwickler, die sich nicht die Mühe machen, Tests zu schreiben, selbst wenn Sie es so einfach wie möglich machen.)

cjs
quelle
-2

Ich habe vor kurzem angefangen, in Python zu programmieren, daher hatte ich noch keine Chance, die besten Praktiken herauszufinden. Aber ich habe ein Modul geschrieben, das alle Tests findet und ausführt.

Also habe ich:

App /
 appfile.py
Prüfung/
 appfileTest.py

Ich werde sehen müssen, wie es geht, wenn ich zu größeren Projekten übergehe.

Quamrana
quelle
4
Hallo, der camelCase ist ein bisschen komisch in der Python-Welt.
Stuart Axon