TDD from scratch§
If it's not tested, it's broken
Twitter: @andreacrotti
Slides: https://github.com/AndreaCrotti/ep2013

If it's not tested, it's broken
Twitter: @andreacrotti
Slides: https://github.com/AndreaCrotti/ep2013
Python is awesome, but:
Traceback (most recent call last):
File "rare_cond.py", line 21, in <module>
smart_function(42)
File "rare_cond.py", line 7, in smart_function
report_error(argO)
NameError: global name 'argO' is not defined
def smart_function(arg0):
if rare_failure_condition():
report_error(argO)
else:
normal_behaviour(arg0)
['W', 'O', 'R', 'D', '1']
>>> uppercase_words("word1")
def uppercase_words(words):
"""Take a list of words and upper case them
"""
return [w.upper() for w in words]
Does not fails, but still clearly wrong
import MySQLdb
import report
class TestReporting(unittest.TestCase):
def setUp(self):
dbc = MySQLdb.connect(host='host', user='user', passwd='passwd', port='port')
cursor = dbc.cursor(MySQLdb.cursors.DictCursor)
# insert 10 values in the db
def test_report(self):
rep = report.get_report()
self.assertEqual(len(rep), 10)
how do I hack it together -> How can I prove it works
def mysum(a, b):
return a + b
def mysubstract(a, b):
return a - b
def test_ops():
assert mysum(0, 0) == 0
assert mysum(1, -1) == 0
assert mysubstract(1, 1) == 0
def test_combined():
a = 10
bvals = range(100)
for b in bvals:
assert mysubstract(mysum(a, b), a) == b
In addition to returning a value, it also modifies some state or has an observable interaction with calling functions or the outside world
def silly_function(value):
global GLOBAL_VALUE
GLOBAL_VALUE += 1
return (value * 2) + GLOBAL_VALUE
>>> funcs.silly_function(1)
3
>>> funcs.silly_function(1)
4
Depends on the global state -> side effect -> hard to tests
from time import asctime
class Report(object):
def report(self):
return ("at %s everything fine" % asctime())
How do I test this??
class ReportDep(object):
def __init__(self, timefunc=asctime):
self.timefunc = timefunc
def report(self):
return ("at %s everything fine" % self.timefunc())
def test_report():
func = lambda: "now"
assert ReportDep(func).report() == 'at now everything fine'
UGLY let's leave it to the Java guys.
library.py:
def lib_func():
return 1000
prog.py:
import library
def dependent():
# the lib_func can be changed at run-time
print(library.lib_func)
print(library.lib_func())
dependent()
library.lib_func = lambda: 42
dependent()
Mock the behaviour of an object that we don't want to run.
class ComplexObject(object):
def method(self):
print("Very complex and expensive")
class Obj(object):
def __init__(self):
self.c = ComplexObject()
self.c.method()
fake_complex_object_auto = Mock(autospec=lib.ComplexObject)
@patch('lib.ComplexObject', new=fake_complex_object_auto)
def test_obj(self):
v = lib.Obj()
lib.py:
from os import listdir
def filter_dirs(pth):
for l in listdir(pth):
if 'x' in l:
yield l
test_lib.py:
class TestLib(unittest.TestCase):
@patch('lib.listdir', new=lambda x: ['one', 'two', 'x'])
def test_filter_dirs(self):
res = list(lib.filter_dirs('.'))
self.assertEqual(len(res), 1)
class Queue(object):
def __init__(self):
self.queue = []
def empty(self):
return False
# return self.queue == []
def test_queue_empty():
q = Queue()
assert q.empty(), "Queue is not empty in the beginning"
if __name__ == '__main__':
test_queue_empty()
def long_crappy_function():
"""Do a bit of everything
"""
ls_cmd = 'ls'
p = subprocess.Popen(ls_cmd, stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
out, err = p.communicate()
res = []
for line in out:
if 'to-match' in line:
res.append(line)
dbc = MySQLdb.connect(host='host', user='user',
passwd='passwd', port='port')
cursor = dbc.cursor(MySQLdb.cursors.DictCursor)
for r in res:
cursor.execute('INSERT INTO table VALUES (%s)' % r)
http://nedbatchelder.com/code/coverage/
As simple as:
nosetests show_cov.py --with-cov --cov-report=html
def smart_division(a, b):
"""Run a 'smart' division
"""
if b == 0:
raise Exception("Can not divide by 0")
res = a / b
back_res = res * b
if back_res != a:
return a / float(b)
else:
return res
Twitter: @andreacrotti Slides: https://github.com/AndreaCrotti/ep2013