Sök…


Anmärkningar

Det finns flera enhetstestverktyg för Python. Det här dokumentationsämnet beskriver den grundläggande unittest modulen. Andra testverktyg inkluderar py.test och nosetests . Denna python-dokumentation om testning jämför flera av dessa verktyg utan att gå in i djupet.

Testa undantag

Program kastar fel när till exempel felinmatning ges. På grund av detta måste man se till att ett fel kastas när faktiskt felinmatning ges. Därför måste vi kontrollera om det finns ett exakt undantag, för detta exempel kommer vi att använda följande undantag:

class WrongInputException(Exception):
    pass

Detta undantag tas upp när fel inmatning ges, i följande sammanhang där vi alltid förväntar oss ett nummer som textinmatning.

def convert2number(random_input):
    try:
        my_input = int(random_input)
    except ValueError:
        raise WrongInputException("Expected an integer!")
    return my_input

För att kontrollera om ett undantag har tagits upp använder vi assertRaises att kontrollera om detta undantag. assertRaises kan användas på två sätt:

  1. Med det vanliga funktionssamtalet. Det första argumentet tar undantagstypen, det andra är en konverterbar (vanligtvis en funktion) och resten av argumenten skickas till denna konverterbara.
  2. Med en with klausul, ger endast undantagstypen funktionen. Detta har som fördel att mer kod kan köras, men bör användas med försiktighet eftersom flera funktioner kan använda samma undantag som kan vara problematiskt. Ett exempel: med self.assertRaises (WrongInputException): convert2number ("inte ett nummer")

Det första har implementerats i följande testfall:

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()

Det kan också vara ett behov av att kontrollera om ett undantag som inte borde ha kastats. Men ett test misslyckas automatiskt när ett undantag kastas och därför kanske inte är nödvändigt alls. Bara för att visa alternativen, den andra testmetoden visar ett fall på hur man kan kontrollera att ett undantag inte kastas. I princip fångar detta undantag och misslyckas sedan testet med metoden fail .

Spottfunktioner med unittest.mock.create_autospec

Ett sätt att håna en funktion är att använda create_autospec funktionen, som kommer att håna ut ett objekt enligt dess specifikationer. Med funktioner kan vi använda detta för att se till att de anropas på rätt sätt.

Med en funktion multiply i custom_math.py :

def multiply(a, b):
    return a * b

Och en funktion multiples_of 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

Vi kan testa multiples_of ensamma genom att håna multiply . Exemplet nedan använder Python standardbibliotek unittest, men det kan också användas med andra testramar, som pytest eller näsa:

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

Testa inställning och nedbrytning inom ett unestest.TestCase

Ibland vill vi förbereda ett sammanhang för varje test som ska köras. setUp metoden körs före varje test i klassen. tearDown körs i slutet av varje test. Dessa metoder är valfria. Kom ihåg att TestCases ofta används i kooperativ multipelarv så du bör vara noga med att alltid kalla super i dessa metoder så att basklassens setUp och tearDown metoder också kommer att tearDown . TestCase av TestCase ger tomma setUp och tearDown metoder så att de kan anropas utan att göra undantag:

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()

Observera att i python2.7 + finns det också addCleanup metoden som registrerar funktioner som ska kallas efter testet har körts. I motsats till tearDown som bara kommer att setUp om setUp lyckas kommer funktioner som är registrerade via addCleanup att addCleanup och med i händelse av ett obehandlat undantag i setUp . Som ett konkret exempel kan denna metod ofta ses att man tar bort olika håravfall som registrerades under testet:

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)

En annan fördel med att registrera städningar på detta sätt är att det gör att programmeraren kan sätta upp rensningskoden bredvid installationskoden och den skyddar dig om en underklasser glömmer att kalla super i tearDown .

Påstå om undantag

Du kan testa att en funktion kastar ett undantag med det inbyggda unittest genom två olika metoder.

Med hjälp av en context manager

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)

Detta kör koden inuti kontekthanteraren och om den lyckas misslyckas testet eftersom undantaget inte höjdes. Om koden ger ett undantag från rätt typ fortsätter testet.

Du kan också få innehållet i det upphöjda undantaget om du vill utföra ytterligare påståenden mot det.

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')

Genom att tillhandahålla en konverterbar 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)

Undantaget för att kontrollera måste vara den första parametern och en utrullningsbar funktion måste ges som den andra parametern. Eventuella andra parametrar som anges överförs direkt till den funktion som anropas, så att du kan specificera parametrarna som utlöser undantaget.

Att välja påståenden inom Unittests

Medan Python har assert uttalande , har ramverket Python enhetstestning bättre påståenden specialiserade för tester: de är mer informativ om misslyckanden, och inte beroende på utförande är debugläge.

Kanske är den enklaste påståendet assertTrue , som kan användas så här:

import unittest

class SimplisticTest(unittest.TestCase):
    def test_basic(self):
        self.assertTrue(1 + 1 == 2)

Det här går bra, men ersätter raden ovan med

        self.assertTrue(1 + 1 == 3)

kommer misslyckas.

assertTrue påståendet är troligtvis den mest allmänna påståendet, eftersom allt testat kan gjutas som något booleskt tillstånd, men det finns ofta bättre alternativ. När man testar för jämställdhet, som ovan, är det bättre att skriva

        self.assertEqual(1 + 1, 3)

När förstnämnda misslyckas är meddelandet

======================================================================

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

men när det sistnämnda misslyckas är meddelandet

======================================================================

FAIL: test (__main__.TruthTest)

----------------------------------------------------------------------

Traceback (most recent call last):

  File "stuff.py", line 6, in test

    self.assertEqual(1 + 1, 3)
AssertionError: 2 != 3

vilket är mer informativt (det utvärderade faktiskt resultatet av vänster sida).

Du kan hitta listan över påståenden i standarddokumentationen . I allmänhet är det en bra idé att välja den påstående som är den mest specifika anpassningen av villkoret. Såsom visas ovan, för att hävda att 1 + 1 == 2 är det bättre att använda assertEqual än assertTrue . På samma sätt, för att hävda att a is None , är det bättre att använda assertIsNone än assertEqual .

Observera också att påståendena har negativa former. Således assertEqual har sin negativa motpart assertNotEqual , och assertIsNone har sin negativa motsvarighet assertIsNotNone . Att använda de negativa motsvarigheterna när det är lämpligt kommer att leda till tydligare felmeddelanden.

Enhetstester med pytest

installera pytest:

pip install pytest

göra testerna redo:

mkdir tests
touch tests/test_docker.py

Funktioner att testa i 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)

Testet importerar 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

hånar en fil som objekt i 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

Apapatchning med pytest i 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

Exempelstest, måste börja med prefixet test_ i filen 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])

köra testerna en i taget:

py.test -k test_docker_install tests
py.test -k test_copy_file_to_docker tests
py.test -k test_docker_exec_something tests

kör alla tester i tests mapp:

py.test -k test_ tests


Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow