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:

  1. 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.
  2. 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


Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow