public
Last active

Examples of pytest, especially funcargs

  • Download Gist
00-intro_errorreporting.txt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
These are snippets of py.test in action, used in a talk given at
PyCon AU 2012 in Hobart, Tasmania. They are all relevant for
py.test 2.2 except where specified. Where taken from open source
projects I have listed a URL, some examples are from the py.test
documentation, some are from my workplace.
 
Apart from things called test_*, these functions should probably
be in your conftest.py, although they can generally start life in
your test files.
 
Talk info: http://2012.pycon-au.org/schedule/52/view_talk
Slides: http://www.slideshare.net/pfctdayelise/funcargs-other-fun-with-pytest
 
http://pytest.org/
 
http://stackoverflow.com/questions/tagged/py.test
http://codespeak.net/mailman/listinfo/py-dev
http://lists.idyll.org/listinfo/testing-in-python
 
##############
# informative error reporting
 
# tb==native
 
Traceback (most recent call last):
File "/workspace/Review/GFESuite/tests/unit/formatters/test_Precis.py",
line 882, in test_lowDetail12hourWxBrackets
assert 'morningZ' in words
AssertionError: assert 'morningZ' in 'morning shower or two'
 
 
# tb==short
 
tests/unit/formatters/test_Precis.py:882: in test_lowDetail12hourWxBrackets
> assert 'morningZ' in words
E assert 'morningZ' in 'morning shower or two'
 
 
# tb==long
 
mockAccessor =
 
def test_lowDetail12hourWxBrackets(mockAccessor):
"""
Initial 6 hours of wx is correctly being washed out to the first 12 hours.
"""
mockAccessor.mockGrids([
Flat("Sky", 0, 24, 0),
Flat("Wx", 0, 6, "Sct:SH:m::"),
Flat("Wx", 6, 24, "NoWx"),
])
_icon, words = precisIconWords(mockAccessor, period=6, detail='low')
assert 'early' not in words
> assert 'morningZ' in words
E assert 'morningZ' in 'morning shower or two'
 
 
tests/unit/formatters/test_Precis.py:882: AssertionError
01-extensible.py
Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
# add back in unittest assert statements
# apparently cribbed from something in nose
 
def pytest_namespace():
"""Make unittest assert methods available.
This is useful for things such floating point checks with assertAlmostEqual.
"""
 
import unittest
 
class Dummy(unittest.TestCase):
def dummy(self):
pass
obj = Dummy('dummy')
 
names = {name: member
for name, member in inspect.getmembers(obj)
if name.startswith('assert') and '_' not in name}
 
return names
 
 
# test file
def test_kn2kmh():
py.test.assertAlmostEqual(UnitConvertor.kn2kmh(10), 18.52, places=4)
 
 
###########################################################
# add a hook for winpdb
 
def pytest_addoption(parser):
"""pytest hook that adds a GFE specific option.
"""
 
# Add options.
group = parser.getgroup('graphical forecast editor options')
group.addoption('--winpdb', dest='usewinpdb', action='store_true', default=False,
help=('start the WinPDB Python debugger before calling each test function. '
'Suggest only using this with a single test at a time (i.e. -k .'))
 
def pytest_configure(config):
# Only do these if this process is the master.
if not hasattr(config, 'slaveinput'):
# Activate winpdb plugin if appropriate.
if config.getvalue("usewinpdb"):
config.pluginmanager.register(WinPdbInvoke(), 'winpdb')
 
class WinPdbInvoke:
 
def __init__(self):
print "initialising winpdb invoker"
 
def pytest_pyfunc_call(self, pyfuncitem):
import rpdb2
rpdb2.start_embedded_debugger('0')
 
 
# then run: py.test -k test_some_specific_thing --winpdb
02-marks.py
Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
# SKIP
# inside a test, if you need to check something after the environment
# has been loaded
 
if not config.get('ifpServer.allowOfficalWrites'):
py.test.skip('Official DB writes are not allowed.')
 
 
# decorators:
 
import sys
 
win32only = pytest.mark.skipif("sys.platform != 'win32'")
 
@win32only
def test_foo():
....
 
@py.test.mark.skipif('True')
def test_foo1():
print "foo1"
 
@py.test.mark.skipif('False')
def test_foo2():
print "foo2"
 
def test_foo3():
py.test.skip('inside skip')
print "foo3"
 
 
# XFAIL
@py.test.mark.xfail
def test_foo4():
assert False
 
@py.test.mark.xfail(reason='This is a bad idea')
def test_foo5():
assert False
 
@py.test.mark.xfail(reason='Maybe this was a bad idea once')
def test_foo6():
assert True
 
def test_foo7():
# force test to be recorded as an xfail,
# even if it would otherwise pass
py.test.xfail()
assert True
 
# output:
 
test_foo4 xfail
test_foo5 xfail
test_foo6 XPASS
test_foo7 xfail
 
# with --runxfail:
 
test_foo4 FAILED
test_foo5 FAILED
test_foo6 PASSED
test_foo7 FAILED
# plus tracebacks
 
 
# example custom marks
 
@py.test.mark.slow
 
@py.test.mark.dstAffected
 
@py.test.mark.mantis1543
 
@py.test.mark.flaky
 
@py.test.mark.unicode
 
@py.test.mark.regression
 
 
# in 2.2 - parametrize
 
@pytest.mark.parametrize(("input", "expected"), [
("3+5", 8),
("2+4", 6),
("6*9", 42),
])
def test_eval(input, expected):
assert eval(input) == expected
03-generatetests.py
Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
# code
def isSquare(n):
n = n ** 0.5
return int(n) == n
 
# test file
def pytest_generate_tests(metafunc):
squares = [1, 4, 9, 16, 25, 36, 49]
for n in range(1, 50):
expected = n in squares
if metafunc.function.__name__ == 'test_isSquare':
metafunc.addcall(id=n, funcargs=dict(n=n, expected=expected))
 
 
def test_isSquare(n, expected):
assert isSquare(n) == expected
 
 
# then:
# conftest.py
def pytest_generate_tests(__multicall__, metafunc):
"""Supports parametrised tests using generate_ fns.
Use multicall to call any other pytest_generate_tests hooks first.
If the test_ fn has a generate_ fn then call it with the metafunc
to let it parametrise the test.
"""
__multicall__.execute()
name = metafunc.function.__name__.replace('test_', 'generate_')
fn = getattr(metafunc.module, name, None)
if fn:
fn(metafunc)
 
 
# generate function is simplified, no boilerplate!
# and we can have one per test function with multiple pairs in a single module, woot!
def generate_isSquare(metafunc):
squares = [1, 4, 9, 16, 25, 36, 49]
for n in range(1, 50):
expected = n in squares
metafunc.addcall(id=n, funcargs=dict(n=n, expected=expected))
04-monkeypatch.py
Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
import os.path
 
def getssh(): # pseudo application code
return os.path.join(os.path.expanduser("~admin"), '.ssh')
 
 
def test_getssh(monkeypatch):
 
def mockreturn(path):
return '/abc'
monkeypatch.setattr(os.path, 'expanduser', mockreturn)
x = getssh()
assert x == '/abc/.ssh'
 
 
######################
# a funcarg to hide/abstract away some monkeypatching
 
def pytest_funcarg__noPreviousWarnings(request):
"""pytest funcarg to avoid retrieving REAL previously issued warnings"""
_ = request.getfuncargvalue("textImporter")
 
def setup():
import RecognisedWarnings as RW
monkeypatch = request.getfuncargvalue("monkeypatch")
noPreviousWarnings = lambda _x, _y, _z: None
monkeypatch.setattr(RW, '_getFDBViewerXML', noPreviousWarnings)
 
def teardown(obj):
pass
 
return request.cached_setup(setup, teardown, scope='function')
05-funcargs.py
Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84
# http://anders.conbere.org/blog/2011/05/03/setup_and_teardown_methods_with_py.test/
 
# conftest.py
def setup_fixtures():
db.insert('...')
return db
 
def teardown_fixtures(db):
db.destroy('..')
 
def py_test_funcarg__db(request):
return request.cached_setup(
setup = setup_fixtures,
teardown = teardown_fixtures,
scope = "module")
 
# test_db.py
def test_db(db):
assert(len(db.query(x=y)) >= 1)
 
 
#####################
# a real DB example
# still far from a good example for most use cases I suspect, what with the lack of ORM and all
 
def setupTestDb():
"""Setup test gfedb ensuring we only do this once."""
 
import gfeDB
if gfeDB.DB_NAME == 'gfedb':
from NeoConfig import config as neoConfig
name = 'gfedbtest{}'.format(neoConfig['instance.port'])
cmd = """\
mysql -u root -e "DROP DATABASE IF EXISTS {name};
CREATE DATABASE {name};
USE {name};
GRANT ALL ON * TO gfe;
GRANT ALL ON * TO gfe@localhost;
FLUSH PRIVILEGES;";
mysqldump -u root --no-data gfedb | mysql -u root {name}
""".format(name=name)
subprocess.check_output(cmd, shell=True)
gfeDB.DB_NAME = name
 
 
def pytest_funcarg__testDb(request):
"""pytest funcarg for a test gfedb."""
 
return request.cached_setup(setup=setupTestDb,
scope='session')
 
 
def pytest_funcarg__emptyDb(request):
"""pytest funcarg to truncate all tables in the test gfedb."""
 
_ = request.getfuncargvalue('testDb')
 
def setup():
from NeoConfig import config as neoConfig
name = 'gfedbtest{}'.format(neoConfig['instance.port'])
cmd = """\
mysql -u root -e "USE {name};
DELETE FROM fire_event_tb;
DELETE FROM forecast_tb;
DELETE FROM issuance_tb;
DELETE FROM precis_pop_areas_tb;
DELETE FROM warning_tb;"
""".format(name=name)
subprocess.check_output(cmd, shell=True)
 
return request.cached_setup(setup=setup,
scope='function')
 
###########################
# funcarg to express a prerequisite
 
 
# https://github.com/lunaryorn/pyudev/blob/develop/tests/test_libudev.py
 
def pytest_funcarg__libudev(request):
try:
return _libudev.load_udev_library()
except ImportError:
pytest.skip('udev not available')
06-frameworks.py
Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
# django example
# http://pytest-django.readthedocs.org/en/latest/helpers.html
 
from myapp.views import my_view
 
def test_details(rf):
request = rf.get('/customer/details')
response = my_view(request)
assert response.status_code == 200
 
def test_an_admin_view(admin_client):
response = admin_client.get('/admin/')
assert response.status_code == 200
 
 
##############################
# Google App Engine
 
# I wonder if these examples should be using monkeypatch to do os.environ.update??
 
# http://pypi.python.org/pypi/pytest_gae/0.2.1
 
 
import os
 
from webtest import TestApp
from main import make_application
 
 
def pytest_funcarg__anon_app(request):
os.environ.update({'USER_EMAIL': '',
'USER_ID': '',
'AUTH_DOMAIN': 'google',
'USER_IS_ADMIN': '0'})
return TestApp(make_application())
 
 
def pytest_funcarg__user_app(request):
os.environ.update({'USER_EMAIL': 'simple@google.com',
'USER_ID': '1',
'AUTH_DOMAIN': 'google',
'USER_IS_ADMIN': '0'})
return TestApp(make_application())
 
 
def pytest_funcarg__admin_app(request):
os.environ.update({'USER_EMAIL': 'admin@google.com',
'USER_ID': '2',
'AUTH_DOMAIN': 'google',
'USER_IS_ADMIN': '1'})
return TestApp(make_application())
 
 
def test_index(anon_app):
assert "Index" in anon_app.get('/index')
 
 
def test_user_with_user(user_app):
assert "User" in user_app.get('/users')
 
 
def test_user_with_anon(anon_app):
assert '302 Moved Temporarily' == anon_app.get('/users').status
 
 
def test_user_with_admin(admin_app):
assert "Admin" in admin_app.get('/users')
07-generatefuncargs.py
Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
# https://bitbucket.org/hpk42/py/src/0fca612a4bbd/conftest.py:
 
def pytest_generate_tests(metafunc):
multi = getattr(metafunc.function, 'multi', None)
if multi is not None:
assert len(multi.kwargs) == 1
for name, l in multi.kwargs.items():
for val in l:
metafunc.addcall(funcargs={name: val})
elif 'anypython' in metafunc.funcargnames:
for name in ('python2.4', 'python2.5', 'python2.6',
'python2.7', 'python3.1', 'pypy-c', 'jython'):
metafunc.addcall(id=name, param=name)
 
 
def pytest_funcarg__anypython(request):
name = request.param
executable = getexecutable(name)
if executable is None:
if sys.platform == "win32":
executable = winpymap.get(name, None)
if executable:
executable = py.path.local(executable)
if executable.check():
return executable
py.test.skip("no %s found" % (name,))
return executable
08-funcargsfuture.py
Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
#probable in 2.3 (not yet released)
 
# content of conftest.py
import pytest
import smtplib
 
@pytest.factory(scope="session",
params=["merlinux.eu", "mail.python.org"])
def smtp(testcontext):
smtp = smtplib.SMTP(testcontext.param)
def fin():
print ("finalizing %s" % smtp)
smtp.close()
testcontext.addfinalizer(fin)
return smtp
 
 
# content of test_module.py
def test_ehlo(smtp):
response = smtp.ehlo()
assert response[0] == 250
assert "merlinux" in response[1]
assert 0 # for demo purposes
 
def test_noop(smtp):
response = smtp.noop()
assert response[0] == 250
assert 0 # for demo purposes
 
 
$ py.test --collectonly
=========================== test session starts ============================
platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev8
plugins: xdist, bugzilla, cache, oejskit, cli, pep8, cov
collecting ... collected 4 items
<Module 'test_module.py'>
<Function 'test_ehlo[merlinux.eu]'>
<Function 'test_noop[merlinux.eu]'>
<Function 'test_ehlo[mail.python.org]'>
<Function 'test_noop[mail.python.org]'>
 
============================= in 0.02 seconds =============================
09-testevolution.py
Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
# v0
# Feb 2010
# data/test/dbconfig/TEXT/Misc/District_TestScript_2.py
{
"name": "Thunderstorms with heavy rain are not dry",
"commentary": "Mantis 01530",
"productType": "District",
"createGrids": [
("Fcst", "Wx", "WEATHER", 0, 24, "Chc:TS:!::r", "all"),
],
"notCheckStrings": [
"dry"
],
"fileChanges": [
("District_NSWRO_Definition", "TextUtility", "add", defaultEditAreas, "undo"),
],
"cmdLineVars": str({
('Generate Days', 'productIssuance'): 'All Periods',
('Issuance Type', 'issuanceType'): 'Morning',
('Issued By', 'issuedBy'): None,
('CompleteUpdate', 'completeUpdate'): 'yes'}),
},
 
 
# v1
# early 2011 - move to pytest, directory restructure
 
def test_Thunderstorms_with_heavy_rain_are_not_dry(formatterTester):
formatterTester.run({
"commentary": "Mantis 01530",
"productType": "District",
"createGrids": [
Flat("Wx", 0, 24, "Chc:TS:!::r"),
],
"notCheckStrings": [
"dry"
],
"cmdLineVars": cmdLineVarsIssuanceMorning
}, defaults())
 
# v2
# feb 2011 - introduction of gridCreator
 
def test_Thunderstorms_with_heavy_rain_are_not_dry(formatterTester, gridCreator):
"""Mantis 01530
"""
gridCreator.createGrids([
Flat("Wx", 0, 24, "Chc:TS:!::r"),
])
formatterTester.run({
"productType": "District",
"notCheckStrings": [
"dry"
],
"cmdLineVars": cmdLineVarsIssuanceMorning
}, defaults())
 
 
# lots of specifying areas like this:
 
siteID = siteConfig.GFESUITE_SITEID
DistrictEditAreaDictionary = {
"NSWRO" : ['NSW_CW013'],
"VICRO" : ['VIC_CW010', 'VIC_CW011'],
"QLDRO" : [],
"SARO" : [],
"TASRO" : [],
"WARO" : [],
"NTRO" : [],
}
area = DistrictEditAreaDictionary[siteID]
 
 
# v3
# March 2011
 
def pytest_funcarg__districtArea(request):
 
def start():
 
areas = {
"VICRO": DefaultEditArea("VIC_PW007", "Central"),
"NSWRO": DefaultEditArea("NSW_PW014", "Riverrina"),
"TASRO": DefaultEditArea("TAS_PW002", "North East"),
"SARO": DefaultEditArea("SA_PW012", "North West Pastoral"),
}
 
return __areaStart(areas)
 
return request.cached_setup(setup=start, scope='session')
 
 
def test_Thunderstorms_with_heavy_rain_are_not_dry(formatterTester, gridCreator, districtArea):
"""Mantis 01530
"""
gridCreator.createGrids([
Flat("Wx", 0, 24, "Chc:TS:!::r"),
])
formatterTester.run({
"productType": "District",
"notCheckStrings": [
"dry"
],
"cmdLineVars": cmdLineVarsIssuanceMorning(districtArea.aac)
}, defaults(districtArea.aac))
 
 
 
# v4
# Nov 2011
# tests/system/formatters/wxPhrase/test_attributes.py
 
@py.test.mark.mantis1530
def test_thunderstormsWithHeavyRainAreNotDry(mockAccessor):
"""'heavy rain' and 'dry' attributes were getting mixed up.
"""
mockAccessor.mockGrids([
Flat("Wx", 0, 24, "Chc:TS:!::r"),
])
words = weatherWordsLandAreal(mockAccessor)
assert "dry" not in words

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.