29
Unit Testing Tips and Tricks: Database Interaction Louis Thomas

Unit Testing Tips and Tricks: Database Interaction Louis Thomas

Embed Size (px)

Citation preview

Page 1: Unit Testing Tips and Tricks: Database Interaction Louis Thomas

Unit Testing Tips and Tricks: Database Interaction

Louis Thomas

Page 2: 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

Page 3: Unit Testing Tips and Tricks: Database Interaction Louis Thomas

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

Page 4: Unit Testing Tips and Tricks: Database Interaction Louis Thomas

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!

Page 5: Unit Testing Tips and Tricks: Database Interaction Louis Thomas

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!

Page 6: Unit Testing Tips and Tricks: Database Interaction Louis Thomas

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!

Page 7: Unit Testing Tips and Tricks: Database Interaction Louis Thomas

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

Page 8: Unit Testing Tips and Tricks: Database Interaction Louis Thomas

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.

Page 9: Unit Testing Tips and Tricks: Database Interaction Louis Thomas

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?)

Page 10: Unit Testing Tips and Tricks: Database Interaction Louis Thomas

Loosening The Coupling

• Introduce interfaces between complex objects.

• Create a mock object to stand in for the complex object.

• Repeat as needed. (Be creative.)

Page 11: Unit Testing Tips and Tricks: Database Interaction Louis Thomas

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

Page 12: Unit Testing Tips and Tricks: Database Interaction Louis Thomas

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();}

Page 13: Unit Testing Tips and Tricks: Database Interaction Louis Thomas

Mock Objects

• Start out as simple as possible (throw exceptions on all methods).

• Add recording of incoming method calls– - ex: RecordingMockObject, Thumbprinters

Page 14: Unit Testing Tips and Tricks: Database Interaction Louis Thomas

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); }}

Page 15: Unit Testing Tips and Tricks: Database Interaction Louis Thomas

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"; } //…}

Page 16: Unit Testing Tips and Tricks: Database Interaction Louis Thomas

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)+")"); } }}

Page 17: Unit Testing Tips and Tricks: Database Interaction Louis Thomas

Mock Objects, cont’d

• Add facility for sending back canned responses – (ex, setNextReply, setFailOnNextRequest)

Page 18: Unit Testing Tips and Tricks: Database Interaction Louis Thomas

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; }}

Page 19: Unit Testing Tips and Tricks: Database Interaction Louis Thomas

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

Page 20: Unit Testing Tips and Tricks: Database Interaction Louis Thomas

Mock Object Frameworks

• EasyMock (http://easymock.org)

• jMock (http://www.jmock.org/)

• YMMV!

Page 21: Unit Testing Tips and Tricks: Database Interaction Louis Thomas

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.

Page 22: Unit Testing Tips and Tricks: Database Interaction Louis Thomas

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

Page 23: Unit Testing Tips and Tricks: Database Interaction Louis Thomas

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(); }

Page 24: Unit Testing Tips and Tricks: Database Interaction Louis Thomas

Testing Database Interactions

• You should be thankful! All the database classes are interfaces already!

• Create mocks and away you go

• Insert / update / delete – easy

Page 25: Unit Testing Tips and Tricks: Database Interaction Louis Thomas

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

Page 26: Unit Testing Tips and Tricks: Database Interaction Louis Thomas

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

Page 27: Unit Testing Tips and Tricks: Database Interaction Louis Thomas

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

Page 28: Unit Testing Tips and Tricks: Database Interaction Louis Thomas

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.

Page 29: Unit Testing Tips and Tricks: Database Interaction Louis Thomas

Summary

• Preaching about TDD

• What is a Unit Test

• Common Unit Testing Patterns

• Unit Testing Database Interactions

• Acceptance Tests With Databases

• Questions / demos!