unit testing with mocks
DESCRIPTION
June 13th, 2009 - CPyUG (China Python User Group)TRANSCRIPT
Unit Testing with Mocksnasi
http://twitter.com/nasiless
Introducing Myself
Architect
Editor
Translator
Unit Testing
A World ...without Unit Testing
Verify
Design
Document
Regression
Edit & Pray
Cover & Modify
Python: unittest Moduleimport unittest
class MyTestCase(unittest.TestCase): def test1(self): """Run test 1""" pass
def test2(self): """Run test 2""" pass
if __name__ == '__main__': unittest.main()
Common unittest Methods
• assertTrue / assertFalse
• assertEquals
• assertRaises
• etc. • setUp
• tearDown
A test is not a unit test if:
• It talks to a database.
• It communicates across a network.
• It touches the file system.
-- Working Effectively with Legacy Code
how ?!
You need mock objects
• pMock (based on jMock)http://pmock.sourceforge.net/
• pyMock (based on EasyMock)http://theblobshop.com/pymock/
• minimock / mock / mox / mocker / ...
Mock Frameworks in Python:
Introducing fudge
• Domain Specific Language
• Powerful Patcher Built-in
• Expect something, Verify it
• Still Alive
http://farmdev.com/projects/fudge
common coding style for testing with mock objects
(1) Create instances of mock objectsmock = fudge.Fake('mock')
(2) Set state and expectations in the mock objectsmock.expects('method') \ .with_args(arg1=1, arg2='2').returns(True)
(3) Invoke domain code with mock objects as parametersmock.method(arg1=1, arg2='2')
(4) Verify consistency in the mock objectsfudge.verify()
Examples
Testing Databaseimport MySQLdb
def Foo(): conn = MySQLdb.connect(host='localhost', user='root', db='test') cursor = conn.cursor()
cursor.execute('SELECT * FROM people') id, name = cursor.fetchone() print id, name
if __name__ == '__main__': Foo()
Mock Databaseimport fudgefrom testmod import Foo
mysqldb = fudge.Fake('MySQLdb')
conn = mysqldb.expects('connect').returns_fake()curs = conn.provides('cursor').returns_fake()curs = curs.expects('execute').returns(1)curs = curs.provides('fetchone').returns((1, 'Nathan'))
@[email protected]_patched_object('testmod', 'MySQLdb', mysqldb)def Test(): Foo() # prints: 1 Nathan
if __name__ == '__main__': Test()
Testing Network
import urllib2
def Foo(): print urllib2.urlopen('http://www.google.com/').read()
if __name__ == '__main__': Foo()
Mock Networkimport fudgefrom cStringIO import StringIOfrom testmod import Foo
urlopen = fudge.Fake('urlopen', callable=True) \ .returns(StringIO('HelloWorld'))
@[email protected]_patched_object('urllib2', 'urlopen', urlopen)def Test(): Foo() # prints: HelloWorld
if __name__ == '__main__': Test()
Testing File System
import os
def Foo(): print os.listdir('.') open('a.txt', 'w').write('HelloWorld')
if __name__ == '__main__': Foo()
Mock File Systemimport fudgefrom cStringIO import StringIOfrom testmod import Foo
listdir = fudge.Fake(callable=True).returns(['a.txt', 'b.jpg'])buf = StringIO()myopen = lambda filename, mode: buf
@[email protected]_patched_object('os', 'listdir', listdir)@fudge.with_patched_object('__builtin__', 'open', myopen)def Test(): Foo() # prints: ['a.txt', 'b.jpg'] print buf.getvalue() # prints: HelloWorld
if __name__ == '__main__': Test()
Pros and Cons
Q & A