290 likes | 394 Views
Unit Testing Tips and Tricks: Database Interaction. Louis Thomas. Overview. Preaching about TDD What is a Unit Test Common Unit Testing Patterns Unit Testing Database Interactions Acceptance Tests With Databases. Are you test infected?. There are two kinds of people:
E N D
Unit Testing Tips and Tricks: Database Interaction Louis Thomas
Overview Preaching about TDD What is a Unit Test Common Unit Testing Patterns Unit Testing Database Interactions Acceptance Tests With Databases
Are you test infected? • There are two kinds of people: • People who don’t like it • Can’t be done attitude • People who do like it • “I think I can” attitude
Test Driven Development Is Hard • Conceptually, it’s simple! (Sure, just like OO is simple…) • It’s a learning process. It will be hard at first. • Be creative!
I’m Guilty… • I'm guilty. I am a hack. I am an "impurist". I often don't do "pure" TDD. • I find TDD easier to do for some tasks than others. I do whatever I feel like. However, I like what I get with TDD, so I lean towards it • Unit tests are good even if you don’t do TDD!
There Are Many Kinds Of Tests • Acceptance tests, user test, integration tests, unit tests; black box, white box… • All tests have merit if they can detect bugs. • Tests only have value if they are run!
Unit Tests • From developer's point of view. • Tests the smallest amount of a system that is interesting. Often just one part of one class! • Highly automated
Unit Test Rule Of Thumb • If you are having trouble writing a unit test or (for those of you who aren't test infected)if it's "impossible" to write a test for your system, • You are trying to test to much. Test a smaller chunk.
But How? • Sometimes objects have complex behaviors, extensive state, and tight relationships. This makes tests difficult:set up is difficult and time consuming, and objects cannot be isolated. • (But wait, that’s not right! Right?)
Loosening The Coupling • Introduce interfaces between complex objects. • Create a mock object to stand in for the complex object. • Repeat as needed. (Be creative.)
Creating Interfaces • If it's our object, just create an interface! • if it's not our object, • create a mock that extends the object and overrides all its methods (works sometimes) • create an interface anyway and create an adapter for the foreign object • Example: WallClock
Example: WallClock Interface public interface WallClock { long getTime(); } Wrapper for normal system service public class DefaultWallClock implements WallClock { public static final WallClock INSTANCE=new DefaultWallClock(); public long getTime() { return System.currentTimeMillis(); } }
Mock Objects • Start out as simple as possible (throw exceptions on all methods). • Add recording of incoming method calls • - ex: RecordingMockObject, Thumbprinters
Example: ReportingMockObject public class ReportingMockObject { StringBuffer m_stringBuffer=new StringBuffer(); //---------------------------------------------------------------- public String getActivityRecordAndReset() { String sActivityRecord=m_stringBuffer.toString(); m_stringBuffer=new StringBuffer(); return sActivityRecord; } //---------------------------------------------------------------- public void recordActivity(String sMessage) { m_stringBuffer.append(sMessage); } }
Example: MockClientSession public class MockClientSession extends ReportingMockObject implements ClientSession { public MockClientSession() { } public void flushOutgoingBuffer() { recordActivity("fOB"); } public void setInterval(int nUpdateIntervalMilliseconds) { recordActivity("sI("+nUpdateIntervalMilliseconds+")"); } public void notifyNewOutgoingData() { recordActivity("nNOD"); } public String getClientName() { recordActivity("gCN"); return "mockClient"; } //… }
Example: MockMultiTableSessionListener public class MockMultiTableSessionListener extends ReportingMockObject implements MultiTableSession.Listener { public interface Thumbprinter { String getThumbprint(MultiTableSession.Update update); String getThumbprint(SessionState sessionState); } //################################################################ private final Thumbprinter m_thumbprinter; //… public MockMultiTableSessionListener(Thumbprinter thumbprinter) { m_thumbprinter=thumbprinter; } //… public void sessionStateNotification(SessionState sessionState) { if (true==m_bLogSessionStateNotification) { recordActivity("sSN("+m_thumbprinter.getThumbprint(sessionState)+")"); } } }
Mock Objects, cont’d • Add facility for sending back canned responses • (ex, setNextReply, setFailOnNextRequest)
Example: WallClock public class MockWallClock implements WallClock { private long[] m_nextTimes; private int m_nNextTimeIndex; public void setNextTime(long nNextTime) { setNextTimeList(new long[] {nNextTime}); } public void setNextTimeList(long[] nextTimes) { Require.neqNull(nextTimes, "nextTimes"); m_nextTimes=nextTimes; m_nNextTimeIndex=0; } public long getTime() { Assert.neqNull(m_nextTimes, "m_nextTimes"); long nNextTime=m_nextTimes[m_nNextTimeIndex]; m_nNextTimeIndex++; if (m_nextTimes.length==m_nNextTimeIndex) { m_nextTimes=null; } return nNextTime; } }
Mock Objects, cont’d • Do whatever you need • Often one mock object will support all tests for a given object, but can create special ones for certain tests • Often, one mock object will support tests for many objects that interact with it
Mock Object Frameworks • EasyMock (http://easymock.org) • jMock (http://www.jmock.org/) • YMMV!
Object Mother (?) • Sometimes you will need a complex data structure set up. Refactor mercilessly. • Especially if you need canned data that is ancillary to the test, it is often worth while to factor creation out into a static method in a util class so you can use it as necessary thereafter.
Testing Accessor • Problem: there are private methods you would like to test, or private members you would like to inspect for your test • You could make them public, but they really are private • Alternative: an inner class! TestingAccessor
Example: TestingAccessor //################################################################ // testing private WallClock m_wallClock=DefaultWallClock.instance; private IStepper m_getConStepper=DefaultStepper.instance; private IStepper m_maintStepper=DefaultStepper.instance; public class TestingAccessor { public void setWallClock(WallClock wallClock) {m_wallClock=wallClock;} public void setGetConStepper(IStepper stepper) {m_getConStepper=stepper;} public void setMaintStepper(IStepper stepper) {m_maintStepper=stepper;} public void setNextOverdueConnectionCheck(long tsNextOverdueConnectionCheck) {m_tsNextOverdueConnectionCheck=tsNextOverdueConnectionCheck;} public int getAllConnectionsSize() {return m_allConnections.size();} public int getUnusedConnectionsSize() {return m_unusedConnections.size();} public int getTotalConnections() {return m_nTotalConnections;} public void cacheMaintenaceThread() {DBConnectionPool.this.cacheMaintenaceThread();} public void doNotifyAll() {synchronized (m_oStateLock) {m_oStateLock.notifyAll();}} } public TestingAccessor getTestingAccessor() { return new TestingAccessor(); }
Testing Database Interactions • You should be thankful! All the database classes are interfaces already! • Create mocks and away you go • Insert / update / delete – easy
Testing Database Interactions, Cont’d • Read – trickier • Can use hard coded expectations • Mocks will act as factories: statements return record sets • load your mock statement with the mock record set to return. • load your mock connection with the mock statement to return. • Can start out with mocks with hard coded returns, but will probably refactor into more general objects. • Ex: Simulated Database Framework
Acceptance Tests With Databases • An acceptance test: Want to test the "whole" app. • Good for testing that the database really likes the SQL we hardcoded in the unit tests, and really responds the way we expect
Acceptance Tests With Databases, Cont’d • Big question is, how can we automate? I built up a toolkit as I went. • BulkLoadData: reads CSV files and loads data into DB (use Excel to edit) • ExecuteSqlScript: processes a text file of SQL commands. • Used to create tables, etc. • ExecuteDatabaseSetupScript: allows me to write little scripts • Knows about 4 commands, including BulkLoadData and ExecuteSqlScript • TestResource framework
Acceptance Tests With Databases, Cont’d • Big question is, how can we automate? I built up a toolkit as I went. • TestResource framework • I can define test resources my test needs, setup/teardown methods, and dependencies. • Resources will set themselves up from any initial state (ex, delete all rows in table and reload) • Now, the acceptance test can just declare all the resources it needs, and framework will set them up. Just needs to mark which resources it dirties, so they can be reset for subsequent tests.
Summary Preaching about TDD What is a Unit Test Common Unit Testing Patterns Unit Testing Database Interactions Acceptance Tests With Databases Questions / demos!