Unit Testing Tips and Tricks: Database Interaction Louis Thomas

Preview:

Citation preview

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 testor (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

public class DefaultWallClock implements WallClock { public static final WallClock INSTANCE=new DefaultWallClock(); public long getTime() { return System.currentTimeMillis(); }}

Interface

Wrapper for normal system service

public interface WallClock { long getTime();}

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: MockClientSessionpublic 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: WallClockpublic 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!

Recommended