Unit Testing Tips and Tricks: Database Interaction

Download Report

Transcript Unit Testing Tips and Tricks: Database Interaction

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!