Python Language
Enhetstestning
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:
- 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.
- 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