Python Language
Unit Testing
Suche…
Bemerkungen
Es gibt verschiedene Gerätetest-Tools für Python. Dieses Dokumentationsthema beschreibt das grundlegende unittest
. Andere Testwerkzeuge umfassen py.test
und nosetests
. Diese Python-Dokumentation zum Testen vergleicht mehrere dieser Tools, ohne in die Tiefe zu gehen.
Ausnahmen testen
Programme werfen Fehler, wenn zum Beispiel eine falsche Eingabe erfolgt. Aus diesem Grund muss sichergestellt werden, dass ein Fehler ausgegeben wird, wenn eine falsche Eingabe erfolgt. Aus diesem Grund müssen wir nach einer genauen Ausnahme suchen. In diesem Beispiel werden wir die folgende Ausnahme verwenden:
class WrongInputException(Exception):
pass
Diese Ausnahme wird ausgelöst, wenn falsche Eingaben gemacht werden, in dem folgenden Kontext, in dem immer eine Zahl als Texteingabe erwartet wird.
def convert2number(random_input):
try:
my_input = int(random_input)
except ValueError:
raise WrongInputException("Expected an integer!")
return my_input
Um zu prüfen, ob eine Ausnahme assertRaises
wurde, verwenden wir assertRaises
, um diese Ausnahme zu überprüfen. assertRaises
kann auf zwei Arten verwendet werden:
- Verwenden des regulären Funktionsaufrufs. Das erste Argument hat den Ausnahmetyp, zweitens eine aufrufbare Funktion (normalerweise eine Funktion), und die übrigen Argumente werden an diese aufrufbare Komponente übergeben.
- Verwenden Sie eine
with
Klausel, und geben Sie der Funktion nur den Ausnahmetyp an. Dies hat den Vorteil, dass mehr Code ausgeführt werden kann, der jedoch mit Vorsicht verwendet werden sollte, da mehrere Funktionen dieselbe Ausnahme verwenden können, was problematisch sein kann. Ein Beispiel: with self.assertRaises (WrongInputException): convert2number ("keine Zahl")
Dieser erste wurde in dem folgenden Testfall implementiert:
import unittest
class ExceptionTestCase(unittest.TestCase):
def test_wrong_input_string(self):
self.assertRaises(WrongInputException, convert2number, "not a number")
def test_correct_input(self):
try:
result = convert2number("56")
self.assertIsInstance(result, int)
except WrongInputException:
self.fail()
Es kann auch erforderlich sein, nach einer Ausnahme zu suchen, die nicht hätte ausgelöst werden sollen. Ein Test schlägt jedoch automatisch fehl, wenn eine Ausnahme ausgelöst wird, und ist daher möglicherweise überhaupt nicht erforderlich. Um die Optionen zu zeigen, zeigt die zweite Testmethode einen Fall, wie geprüft werden kann, ob eine Ausnahme nicht ausgelöst wird. Grundsätzlich wird die Ausnahme erkannt und der Test mit der fail
Methode fail
.
Verspottungsfunktionen mit unittest.mock.create_autospec
Eine Möglichkeit zum create_autospec
einer Funktion ist die Verwendung der Funktion create_autospec
, mit der ein Objekt gemäß seinen Angaben verspottet wird. Mit Funktionen können wir dies nutzen, um sicherzustellen, dass sie entsprechend aufgerufen werden.
Mit einer Funktion in custom_math.py
multiply
:
def multiply(a, b):
return a * b
Und eine Funktion multiples_of
in process_math.py
:
from custom_math import multiply
def multiples_of(integer, *args, num_multiples=0, **kwargs):
"""
:rtype: list
"""
multiples = []
for x in range(1, num_multiples + 1):
"""
Passing in args and kwargs here will only raise TypeError if values were
passed to multiples_of function, otherwise they are ignored. This way we can
test that multiples_of is used correctly. This is here for an illustration
of how create_autospec works. Not recommended for production code.
"""
multiple = multiply(integer,x, *args, **kwargs)
multiples.append(multiple)
return multiples
Wir können multiples_of
alleine testen, multiply
wir multiply
. Im folgenden Beispiel wird die Python-Standardbibliothek unittest verwendet, dies kann jedoch auch mit anderen Testframeworks, wie z.
from unittest.mock import create_autospec
import unittest
# we import the entire module so we can mock out multiply
import custom_math
custom_math.multiply = create_autospec(custom_math.multiply)
from process_math import multiples_of
class TestCustomMath(unittest.TestCase):
def test_multiples_of(self):
multiples = multiples_of(3, num_multiples=1)
custom_math.multiply.assert_called_with(3, 1)
def test_multiples_of_with_bad_inputs(self):
with self.assertRaises(TypeError) as e:
multiples_of(1, "extra arg", num_multiples=1) # this should raise a TypeError
Testen Sie Setup und Teardown innerhalb eines Tests. TestCase
Manchmal möchten wir einen Kontext für jeden Test erstellen, unter dem ausgeführt wird. Die setUp
Methode wird vor jedem Test in der Klasse ausgeführt. tearDown
wird am Ende jedes Tests ausgeführt. Diese Methoden sind optional. Denken Sie daran, dass Testfälle häufig in kooperativer Mehrfachvererbung verwendet werden. Sie sollten also immer den super
- setUp
in diesen Methoden tearDown
, damit auch die Methoden setUp
und tearDown
der setUp
aufgerufen werden. Die TestCase
von TestCase
stellt leere setUp
und tearDown
Methoden tearDown
, sodass sie ohne Ausnahmen aufgerufen werden können:
import unittest
class SomeTest(unittest.TestCase):
def setUp(self):
super(SomeTest, self).setUp()
self.mock_data = [1,2,3,4,5]
def test(self):
self.assertEqual(len(self.mock_data), 5)
def tearDown(self):
super(SomeTest, self).tearDown()
self.mock_data = []
if __name__ == '__main__':
unittest.main()
Beachten Sie, dass es in python2.7 + auch die addCleanup
Methode gibt, die Funktionen registriert, die nach dem Ausführen des Tests aufgerufen werden sollen. Im Gegensatz zu tearDown
das nur aufgerufen wird, wenn setUp
erfolgreich ist, werden über addCleanup
registrierte Funktionen auch im Fall einer nicht behandelten Ausnahme in setUp
. Als ein konkretes Beispiel kann diese Methode häufig gesehen werden, wenn verschiedene Mocks entfernt werden, die während des Tests registriert wurden:
import unittest
import some_module
class SomeOtherTest(unittest.TestCase):
def setUp(self):
super(SomeOtherTest, self).setUp()
# Replace `some_module.method` with a `mock.Mock`
my_patch = mock.patch.object(some_module, 'method')
my_patch.start()
# When the test finishes running, put the original method back.
self.addCleanup(my_patch.stop)
Ein weiterer Vorteil der Registrierung von Bereinigungen auf diese Weise ist, dass der Programmierer den Bereinigungscode neben den Setup-Code stellen kann und Sie tearDown
falls ein Subclasser vergisst, super
in tearDown
.
Ausnahmen geltend machen
Sie können mit zwei integrierten Methoden testen, ob eine Funktion mit dem integrierten Test eine Ausnahme auslöst.
Verwenden eines Kontextmanagers
def division_function(dividend, divisor):
return dividend / divisor
class MyTestCase(unittest.TestCase):
def test_using_context_manager(self):
with self.assertRaises(ZeroDivisionError):
x = division_function(1, 0)
Dadurch wird der Code im Kontext-Manager ausgeführt. Wenn er erfolgreich ist, schlägt der Test fehl, da die Ausnahme nicht ausgelöst wurde. Wenn der Code eine Ausnahme des richtigen Typs auslöst, wird der Test fortgesetzt.
Sie können auch den Inhalt der ausgelösten Ausnahme abrufen, wenn Sie weitere Zusicherungen dagegen ausführen möchten.
class MyTestCase(unittest.TestCase):
def test_using_context_manager(self):
with self.assertRaises(ZeroDivisionError) as ex:
x = division_function(1, 0)
self.assertEqual(ex.message, 'integer division or modulo by zero')
Durch die Bereitstellung einer aufrufbaren Funktion
def division_function(dividend, divisor):
"""
Dividing two numbers.
:type dividend: int
:type divisor: int
:raises: ZeroDivisionError if divisor is zero (0).
:rtype: int
"""
return dividend / divisor
class MyTestCase(unittest.TestCase):
def test_passing_function(self):
self.assertRaises(ZeroDivisionError, division_function, 1, 0)
Die zu prüfende Ausnahme muss der erste Parameter sein und eine aufrufbare Funktion muss als zweiter Parameter übergeben werden. Alle anderen angegebenen Parameter werden direkt an die aufgerufene Funktion übergeben, sodass Sie die Parameter angeben können, die die Ausnahme auslösen.
Assertions innerhalb Unittests auswählen
Während Python eine assert
Anweisung hat , hat das Test-Framework für Python-Einheiten bessere Assertions für Tests: Sie sind informativer bei Fehlern und hängen nicht vom Debug-Modus der Ausführung ab.
Die einfachste Behauptung ist assertTrue
, die folgendermaßen verwendet werden kann:
import unittest
class SimplisticTest(unittest.TestCase):
def test_basic(self):
self.assertTrue(1 + 1 == 2)
Dies wird gut funktionieren, aber die Zeile oben durch ersetzen
self.assertTrue(1 + 1 == 3)
wird versagen.
Die assertTrue
Behauptung ist wahrscheinlich die allgemeinste Behauptung, da alles, was getestet wurde, als boolesche Bedingung angesehen werden kann, aber oft gibt es bessere Alternativen. Wenn Sie wie oben auf Gleichheit prüfen, sollten Sie besser schreiben
self.assertEqual(1 + 1, 3)
Wenn der Erstere fehlschlägt, wird die Nachricht angezeigt
======================================================================
FAIL: test (__main__.TruthTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "stuff.py", line 6, in test
self.assertTrue(1 + 1 == 3)
AssertionError: False is not true
Wenn letzteres fehlschlägt, wird die Nachricht angezeigt
======================================================================
FAIL: test (__main__.TruthTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "stuff.py", line 6, in test
self.assertEqual(1 + 1, 3)
AssertionError: 2 != 3
was informativer ist (es wertete tatsächlich das Ergebnis der linken Seite aus).
Die Liste der Zusicherungen finden Sie in der Standarddokumentation . Im Allgemeinen ist es eine gute Idee, die Assertion zu wählen, die der Bedingung am besten entspricht. Wie oben gezeigt, ist es daher besser, assertEqual
zu verwenden als assertTrue
, um zu behaupten, dass 1 + 1 == 2
ist. assertIsNone
zu behaupten, dass a is None
, ist es besser, assertIsNone
zu verwenden als assertEqual
.
Beachten Sie auch, dass die Aussagen negative Formen haben. So hat assertEqual
seinen negativen Gegenwert assertNotEqual
und assertIsNone
seinen negativen Gegenwert assertIsNotNone
. Die Verwendung der negativen Gegenstücke führt gegebenenfalls zu klareren Fehlermeldungen.
Unit-Tests mit Pytest
pytest installieren:
pip install pytest
Tests vorbereiten:
mkdir tests
touch tests/test_docker.py
Funktionen zum Testen in docker_something/helpers.py
:
from subprocess import Popen, PIPE
# this Popen is monkeypatched with the fixture `all_popens`
def copy_file_to_docker(src, dest):
try:
result = Popen(['docker','cp', src, 'something_cont:{}'.format(dest)], stdout=PIPE, stderr=PIPE)
err = result.stderr.read()
if err:
raise Exception(err)
except Exception as e:
print(e)
return result
def docker_exec_something(something_file_string):
fl = Popen(["docker", "exec", "-i", "something_cont", "something"], stdin=PIPE, stdout=PIPE, stderr=PIPE)
fl.stdin.write(something_file_string)
fl.stdin.close()
err = fl.stderr.read()
fl.stderr.close()
if err:
print(err)
exit()
result = fl.stdout.read()
print(result)
Der Test importiert test_docker.py
:
import os
from tempfile import NamedTemporaryFile
import pytest
from subprocess import Popen, PIPE
from docker_something import helpers
copy_file_to_docker = helpers.copy_file_to_docker
docker_exec_something = helpers.docker_exec_something
Verspotten einer Datei wie eines Objekts in test_docker.py
:
class MockBytes():
'''Used to collect bytes
'''
all_read = []
all_write = []
all_close = []
def read(self, *args, **kwargs):
# print('read', args, kwargs, dir(self))
self.all_read.append((self, args, kwargs))
def write(self, *args, **kwargs):
# print('wrote', args, kwargs)
self.all_write.append((self, args, kwargs))
def close(self, *args, **kwargs):
# print('closed', self, args, kwargs)
self.all_close.append((self, args, kwargs))
def get_all_mock_bytes(self):
return self.all_read, self.all_write, self.all_close
Affen-Patches mit pytest in test_docker.py
:
@pytest.fixture
def all_popens(monkeypatch):
'''This fixture overrides / mocks the builtin Popen
and replaces stdin, stdout, stderr with a MockBytes object
note: monkeypatch is magically imported
'''
all_popens = []
class MockPopen(object):
def __init__(self, args, stdout=None, stdin=None, stderr=None):
all_popens.append(self)
self.args = args
self.byte_collection = MockBytes()
self.stdin = self.byte_collection
self.stdout = self.byte_collection
self.stderr = self.byte_collection
pass
monkeypatch.setattr(helpers, 'Popen', MockPopen)
return all_popens
Beispieltests müssen mit dem Präfix test_
in der Datei test_docker.py
:
def test_docker_install():
p = Popen(['which', 'docker'], stdout=PIPE, stderr=PIPE)
result = p.stdout.read()
assert 'bin/docker' in result
def test_copy_file_to_docker(all_popens):
result = copy_file_to_docker('asdf', 'asdf')
collected_popen = all_popens.pop()
mock_read, mock_write, mock_close = collected_popen.byte_collection.get_all_mock_bytes()
assert mock_read
assert result.args == ['docker', 'cp', 'asdf', 'something_cont:asdf']
def test_docker_exec_something(all_popens):
docker_exec_something(something_file_string)
collected_popen = all_popens.pop()
mock_read, mock_write, mock_close = collected_popen.byte_collection.get_all_mock_bytes()
assert len(mock_read) == 3
something_template_stdin = mock_write[0][1][0]
these = [os.environ['USER'], os.environ['password_prod'], 'table_name_here', 'test_vdm', 'col_a', 'col_b', '/tmp/test.tsv']
assert all([x in something_template_stdin for x in these])
Ausführen der Tests nacheinander:
py.test -k test_docker_install tests
py.test -k test_copy_file_to_docker tests
py.test -k test_docker_exec_something tests
alle Tests in den laufenden tests
Ordner:
py.test -k test_ tests