Dies ist der Artikel zum 11. Tag von Python Part 2 Adventskalender 2015.
In diesem Artikel werden die bei der Verwendung von pytest erhaltenen Tipps im umgekehrten Format zusammengefasst. Ein Beispielprojekt (bestätigt mit Python 3.5.0 / pytest 2.8.4), das den Inhalt dieses Artikels enthält, wird im folgenden Repository abgelegt. Lesen Sie daher auch dieses. https://github.com/FGtatsuro/pytest_sample
pytest ist, wie der Name schon sagt, eine in Python geschriebene Testbibliothek. Ähnliche Bibliotheken sind unittest und Nase.
Da ich mit den beiden oben genannten Tools nicht vertraut bin, kann ich keine Bewertung im Vergleich zu ihnen vornehmen, aber ich persönlich war der Meinung, dass die folgenden Punkte charakteristisch sind.
Sie können den Standardwert der Option für pytest in setup.cfg
angeben.
setup.cfg
#Die Optionen, die angegeben werden können, sind py.test --Siehe Hilfe
[pytest]
addopts = -v
(Referenz) http://pytest.org/latest/customize.html?highlight=setup%20cfg#adding-default-options
Der Pytest selbst verfügt nicht über eine Timeout-Funktion, kann jedoch vom Plug-In pytest-timeout unterstützt werden.
pytest-Installation des Timeout-Plug-Ins
$ pip install pytest-timeout
Es gibt verschiedene Möglichkeiten, das Zeitlimit festzulegen, die wie folgt kombiniert werden können.
setup.cfg
an.Standard-Timeout-Zeit
[pytest]
addopts = -v
timeout = 5
Angeben der Zeitüberschreitungszeit durch Annotation
import time
@pytest.mark.timeout(10)
def test_timeout():
time.sleep(8)
Wenn der HTTP-Zugriff in einem Integrationstest usw. erfolgt, ist es zweckmäßig, die HTTP-Anforderung / Antwort zum Zeitpunkt der Testausführung in der Standardausgabe bestätigen zu können (in vielen Fällen zumindest bei der Implementierung des Tests). Am Beispiel von Anfragen kann der Zweck erreicht werden, indem ein Handler wie folgt definiert wird.
Beispiele für die Handler-Implementierung für Anforderungen
import requests
# _Die Protokollierung ist ein selbst definiertes Modul
from ._logging import get_logger
logger = get_logger(__name__)
class ResponseHandler(object):
def __call__(self, resp, *args, **kwargs):
logger.debug('### Request ###')
logger.debug('Method:{0}'.format(resp.request.method))
logger.debug('URL:{0}'.format(resp.request.url))
logger.debug('Header:{0}'.format(resp.request.headers))
logger.debug('Body:{0}'.format(resp.request.body))
logger.debug('### Response ###')
logger.debug('Status:{0}'.format(resp.status_code))
logger.debug('Header:{0}'.format(resp.headers))
logger.debug('Body:{0}'.format(resp.text))
class HttpBinClient(object):
'''
The client for https://httpbin.org/
'''
base = 'https://httpbin.org'
def __init__(self):
self.session = requests.Session()
#Registrieren Sie den erstellten Handler
# http://docs.python-requests.org/en/latest/user/advanced/?highlight=response#event-hooks
self.session.hooks = {'response': ResponseHandler()}
def ip(self):
return self.session.get('{0}/ip'.format(self.base))
Mit den Standardeinstellungen von pytest wird der Inhalt der Standardausgabe bei Ausführung des Tests jedoch von pytest erfasst (wird zum Melden der Testergebnisse verwendet), sodass die Ausgabe des Handlers nicht so bestätigt werden kann, wie sie ist. Um dies zu überprüfen, müssen Sie den Wert der Option "Capture" auf "No" setzen.
--capture=Ausführungsbeispiel von Nr
$ py.test --capture=no
=========================================================================================== test session starts ============================================================================================
...
tests/test_calc.py::test_add
PASSED
tests/test_client.py::test_ip
DEBUG:sample.client:2015-12-10 11:26:17,265:### Request ###
DEBUG:sample.client:2015-12-10 11:26:17,265:Method:GET
DEBUG:sample.client:2015-12-10 11:26:17,265:URL:https://httpbin.org/ip
DEBUG:sample.client:2015-12-10 11:26:17,265:Header:{'Accept-Encoding': 'gzip, deflate', 'Connection': 'keep-alive', 'User-Agent': 'python-requests/2.8.1', 'Accept': '*/*'}
DEBUG:sample.client:2015-12-10 11:26:17,265:Body:None
DEBUG:sample.client:2015-12-10 11:26:17,265:### Response ###
DEBUG:sample.client:2015-12-10 11:26:17,265:Status:200
DEBUG:sample.client:2015-12-10 11:26:17,265:Header:{'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Server': 'nginx', 'Access-Control-Allow-Credentials': 'true', 'Content-Length': '33', 'Connection': 'keep-alive', 'Date': 'Thu, 10 Dec 2015 02:26:17 GMT'}
DEBUG:sample.client:2015-12-10 11:26:17,315:Body:{
"origin": xxxxx
}
...
Wenn es schwierig ist, die Option jedes Mal anzugeben, empfiehlt es sich, den Standardwert anzugeben.
Standardwert für die Erfassungsoption
[pytest]
addopts = -v --capture=no
timeout = 5
(Referenz) http://pytest.org/latest/capture.html?highlight=capture
Mit der Option "junit-xml" können Sie einen Testbericht im XUnit-Format in eine angegebene Datei ausgeben. Dies macht es einfach, mit Jenkins zu arbeiten.
Berichtsausgabe im XUnit-Format
[pytest]
addopts = -v --capture=no --junit-xml=results/results.xml
timeout = 5
Bei der Überprüfung der Testergebnisse mit Jenkins ist es zweckmäßig, die einzelnen Testmethoden und die Standardausgabe zu diesem Zeitpunkt überprüfen zu können. Wie oben erwähnt, enthält der Testbericht nicht die Standardausgabe, wenn der Standardwert der Option "Capture" auf "Nein" gesetzt ist. Daher ist es besser, den Wert zur Laufzeit zu überschreiben.
--capture=Geben Sie sys zur Laufzeit an
$ py.test --capture=sys
=========================================================================================== test session starts ============================================================================================
...
tests/test_calc.py::test_add PASSED
#HTTP-Anfrage/Kein Antwortprotokoll
tests/test_client.py::test_ip PASSED
tests/test_timeout.py::test_timeout PASSED
...
#HTTP-Anforderung für Testbericht/Enthält ein Antwortprotokoll
$ cat results/results.xml
<?xml version="1.0" encoding="utf-8"?><testsuite errors="0" failures="0" name="pytest" skips="0" tests="3" time="9.110"><testcase classname="tests.test_calc" file="tests/test_calc.py" line="5" name="test_add" time="0.00024390220642089844"><system-out>
</system-out></testcase><testcase classname="tests.test_client" file="tests/test_client.py" line="7" name="test_ip" time="0.9390749931335449"><system-out>
DEBUG:sample.client:2015-12-10 12:29:33,753:### Request ###
DEBUG:sample.client:2015-12-10 12:29:33,754:Method:GET
DEBUG:sample.client:2015-12-10 12:29:33,754:URL:https://httpbin.org/ip
DEBUG:sample.client:2015-12-10 12:29:33,754:Header:{'Connection': 'keep-alive', 'User-Agent': 'python-requests/2.8.1', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*'}
DEBUG:sample.client:2015-12-10 12:29:33,754:Body:None
DEBUG:sample.client:2015-12-10 12:29:33,754:### Response ###
DEBUG:sample.client:2015-12-10 12:29:33,754:Status:200
DEBUG:sample.client:2015-12-10 12:29:33,754:Header:{'Content-Type': 'application/json', 'Date': 'Thu, 10 Dec 2015 03:29:34 GMT', 'Connection': 'keep-alive', 'Access-Control-Allow-Origin': '*', 'Content-Length': '33', 'Access-Control-Allow-Credentials': 'true', 'Server': 'nginx'}
DEBUG:sample.client:2015-12-10 12:29:33,811:Body:{
"origin": "124.33.163.178"
}
</system-out></testcase><testcase classname="tests.test_timeout" file="tests/test_timeout.py" line="8" name="test_timeout" time="8.001494884490967"><system-out>
</system-out></testcase></testsuite>%
Durch die Verknüpfung mit setuptools können Sie Tests ausführen, ohne die Ausführungsumgebung zu verschmutzen (ohne den Inhalt von = pip freeze
zu ändern). Ich denke, dies ist in dem Sinne nützlich, dass Sie unnötige Probleme vermeiden können, wenn Sie die Möglichkeit haben, den Test anders als Sie selbst auszuführen, und der Executor Python nicht genug verwendet, um die Umgebung mit "virtualenv" zu schneiden.
Ein Beispiel für "setup.py" ist unten gezeigt.
setup.py(Zusammenarbeit zwischen Setup-Tools und Pytest)
import os
import sys
from setuptools import setup, find_packages
from setuptools.command.test import test as TestCommand
#Implementierung eines Befehls zur Ausführung von Pytest
class PyTest(TestCommand):
#Bei der Angabe von Pytest-Optionen--pytest-args='{options}'Benutzen
user_options = [
('pytest-args=', 'a', 'Arguments for pytest'),
]
def initialize_options(self):
TestCommand.initialize_options(self)
self.pytest_target = []
self.pytest_args = []
def finalize_options(self):
TestCommand.finalize_options(self)
self.test_args = []
self.test_suite = True
def run_tests(self):
import pytest
errno = pytest.main(self.pytest_args)
sys.exit(errno)
version = '0.1'
# setup.Pytest muss in py importiert werden
setup_requires=[
'pytest'
]
install_requires=[
'requests',
]
tests_require=[
'pytest-timeout',
'pytest'
]
setup(name='pytest_sample',
...
setup_requires=setup_requires,
install_requires=install_requires,
tests_require=tests_require,
# 'test'Ist mit "Befehl zum Ausführen von Pytest" verbunden
cmdclass={'test': PyTest},
)
Nachdem Sie setup.py
geändert haben, können Sie den Test mit python setup.py test
ausführen.
Testausführung über Setup-Tools
$ python setup.py test
...
tests/test_calc.py::test_add
PASSED
tests/test_client.py::test_ip
DEBUG:sample.client:2015-12-10 12:54:20,426:### Request ###
DEBUG:sample.client:2015-12-10 12:54:20,426:Method:GET
DEBUG:sample.client:2015-12-10 12:54:20,426:URL:https://httpbin.org/ip
DEBUG:sample.client:2015-12-10 12:54:20,426:Header:{'Connection': 'keep-alive', 'User-Agent': 'python-requests/2.8.1', 'Accept': '*/*', 'Accept-Encoding': 'gzip, deflate'}
DEBUG:sample.client:2015-12-10 12:54:20,426:Body:None
DEBUG:sample.client:2015-12-10 12:54:20,426:### Response ###
DEBUG:sample.client:2015-12-10 12:54:20,426:Status:200
DEBUG:sample.client:2015-12-10 12:54:20,426:Header:{'Server': 'nginx', 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Credentials': 'true', 'Connection': 'keep-alive', 'Content-Length': '33', 'Date': 'Thu, 10 Dec 2015 03:54:20 GMT', 'Content-Type': 'application/json'}
DEBUG:sample.client:2015-12-10 12:54:20,484:Body:{
"origin": "124.33.163.178"
}
PASSED
tests/test_timeout.py::test_timeout
PASSED
...
Wenn Sie Pytest-Optionen angeben möchten, verwenden Sie die in der obigen Implementierung definierte Option "--pytest-args".
Testausführung über Setup-Tools(Mit Optionen)
$ python setup.py test --pytest-args='--capture=sys'
...
tests/test_calc.py::test_add PASSED
tests/test_client.py::test_ip PASSED
tests/test_timeout.py::test_timeout PASSED
...
(Referenz) http://pytest.org/latest/goodpractises.html
(Ergänzung) Als ich das Dokument beim Schreiben erneut las, stellte ich fest, dass es eine Bibliothek gibt, die dasselbe tut. Es kann gut sein, dies zu verwenden. https://pypi.python.org/pypi/pytest-runner
Mit dem Plug-In pytest-xdist können Sie Tests parallel ausführen.
Parallele Ausführung von Tests
#Parallele Ausführung in 2 Prozessen
$ py.test -n 2
Da es in einem separaten Prozess ausgeführt wird, muss die Abhängigkeit der Bibliothek in der Umgebung, in der es ausgeführt wird, im Voraus aufgelöst werden. Daher ist es nicht mit der Ausführung über setuptools kompatibel, wodurch Abhängigkeiten zur Laufzeit aufgelöst werden.
Mit der Option "lf" können Sie nur die Tests wiederholen, die im vorherigen Lauf fehlgeschlagen sind.
Informationen zu fehlgeschlagenen Tests werden in ".cache / v / cache / lastfailed" direkt unter dem Verzeichnis aufgezeichnet, in dem der Test ausgeführt wurde. Wenn Sie mit anderen Tools arbeiten möchten (z. B. Nur bei fehlgeschlagenem Test erneut ausführen), möchten Sie möglicherweise direkt auf diese Datei verweisen.
$ py.test --capture=sys
collected 3 items
...
tests/test_calc.py::test_add PASSED
tests/test_client.py::test_ip PASSED
tests/test_timeout.py::test_timeout FAILED
...
#Informationen zu fehlgeschlagenen Tests
$ cat .cache/v/cache/lastfailed
{
"tests/test_timeout.py::test_timeout": true
}
#Testfix...
#Führen Sie nur fehlgeschlagene Tests erneut aus
$ py.test --capture=sys --lf
pytest kann die auszuführenden Tests nach verschiedenen Bedingungen filtern.
Zunächst aus der Methode zur Angabe des zu spielenden Moduls / der zu spielenden Methode.
Modul/Methodenspezifikation
#Geben Sie das Modul an
$ py.test tests/test_calc.py
#Geben Sie im Modul eine Methode an
$ py.test tests/test_calc.py::test_add
Sie können auch nur die Module / Methoden ausführen, die der Zeichenfolge entsprechen
Filtern nach String-Matching
# 'calc'Modul mit der Zeichenfolge/Führen Sie die Methode aus
$ py.test -k calc
Sie können auch nach der Markierung des Dekorateurs filtern. Da es und / oder
unterstützt, ist es möglich, Bedingungen wie mit / ohne Mehrfachmarkierungen anzugeben.
Filtern nach Markierung
#Markierung: pytest.mark.<Es kann eine beliebige Zeichenfolge angegeben werden>
@pytest.mark.slow
@pytest.mark.httpaccess
def test_ip():
...
@pytest.mark.slow
@pytest.mark.timeout(10)
def test_timeout():
...
# @pytest.mark.Führen Sie Tests mit langsam durch
$ py.test -m slow
# @pytest.mark.slow/@pytest.mark.Führen Sie einen Test mit beiden httpaccess aus
$ py.test -m 'slow and httpaccess'
# @pytest.mark.Es hat langsam, [email protected]ühren Sie Tests ohne httpaccess aus
$ py.test -m 'slow and not httpaccess'
(Referenz) https://pytest.org/latest/example/markers.html#mark-examples
Es gibt verschiedene andere Funktionen. Wenn Sie interessiert sind, sollten Sie das Offizielle Dokument lesen.
Morgen 12 ist @shinyorke.
Recommended Posts