33rd degree 2013, bad tests, good tests

79
Bad Tests, Good Tests Tomek Kaczanowski http://twitter.com/#!/devops_borat

Upload: tomek-kaczanowski

Post on 09-May-2015

961 views

Category:

Technology


1 download

TRANSCRIPT

Page 1: 33rd Degree 2013, Bad Tests, Good Tests

Bad Tests, Good Tests

Tomek Kaczanowski

http://twitter.com/#!/devops_borat

Page 2: 33rd Degree 2013, Bad Tests, Good Tests

Tomek Kaczanowski

• Developer

• Team lead

• Blogger

• http://kaczanowscy.pl/tomek

• Book author

• http://practicalunittesting.com

• Working at CodeWise (Krakow, Poland)

• ...we are hiring, wanna join us?

JUnit version coming soon!

Page 3: 33rd Degree 2013, Bad Tests, Good Tests

Why bother with tests?

• System works as expected

• Changes do not hurt

• Documentation

Page 4: 33rd Degree 2013, Bad Tests, Good Tests

Tests help to achieve quality

Not sure when I saw this picture – probably in GOOS?

Page 5: 33rd Degree 2013, Bad Tests, Good Tests

What happens if we do it wrong?

• Angry clients

• Depressed developers

http://www.joshcanhelp.com

Page 6: 33rd Degree 2013, Bad Tests, Good Tests

When I started out with unit tests, I was enthralled with the promise of ease and security that they would bring to my projects. In practice, however, the theory of sustainable software through unit tests started to break down. This difficulty continued to build up, until I finally threw my head back in anger and declared that "Unit Tests have become more trouble than they are worth."

Llewellyn Falco and Michael Kennedy, Develop Mentor August 09

Page 7: 33rd Degree 2013, Bad Tests, Good Tests

http://chrispiascik.com/daily-drawings/express-yourself/

Page 8: 33rd Degree 2013, Bad Tests, Good Tests

write the right test

Page 9: 33rd Degree 2013, Bad Tests, Good Tests

write the right test

write this test right

Page 10: 33rd Degree 2013, Bad Tests, Good Tests

Before we begin

• All of the examples are real but were:

• obfuscated

• to protect the innocents :)

• truncated

• imagine much more complex domain objects

• Asking questions is allowed

• ...but being smarter than me is not ;)

Page 11: 33rd Degree 2013, Bad Tests, Good Tests

We don't need no stinkin' asserts! public void testAddChunks() {

System.out.println("*************************************");

System.out.println("testAddChunks() ... ");

ChunkMap cm = new ChunkMap(3);

cm.addChunk(new Chunk("chunk"));

List testList = cm.getChunks("chunk",null);

if (testList.isEmpty())

fail("there should be at least one list!");

Chunk chunk = cm.getActualChunk("chunk",null);

if (chunk.getElements().isEmpty())

fail("there should be at least one element!");

if (cm.getFinalChunkNr() != 1)

fail("there should be at least one chunk!");

// iterate actual chunk

for (Iterator it = chunk.getElements().iterator();

it.hasNext();) {

Element element = (Element) it.next();

System.out.println("Element: " + element);

}

showChunks(cm);

System.out.println("testAddChunks() OK ");

}

Courtesy of @bocytko

Page 12: 33rd Degree 2013, Bad Tests, Good Tests

Success is not an option...

/**

* Method testFailure.

*/

public void testFailure() {

try {

Message message = new Message(null,true);

fail();

} catch(Exception ex) {

ExceptionHandler.log(ExceptionLevel.ANY,ex);

fail();

}

}

Courtesy of @bocytko

Page 13: 33rd Degree 2013, Bad Tests, Good Tests

What has happened? Well, it failed...

public void testSimple() {

IData data = null;

IFormat format = null;

LinkedList<String> attr = new LinkedList<String>();

attr.add("A");

attr.add("B");

try {

format = new SimpleFormat("A");

data.setAmount(Amount.TEN);

data.setAttributes(attr);

IResult result = format.execute();

System.out.println(result.size());

Iterator iter = result.iterator();

while (iter.hasNext()) {

IResult r = (IResult) iter.next();

System.out.println(r.getMessage());

...

}

catch (Exception e) {

fail();

}

} Courtesy of @bocytko

Page 14: 33rd Degree 2013, Bad Tests, Good Tests

What has happened? Well, it failed...

public void testSimple() {

IData data = null;

IFormat format = null;

LinkedList<String> attr = new LinkedList<String>();

attr.add("A");

attr.add("B");

try {

format = new SimpleFormat("A");

data.setAmount(Amount.TEN);

data.setAttributes(attr);

IResult result = format.execute();

System.out.println(result.size());

Iterator iter = result.iterator();

while (iter.hasNext()) {

IResult r = (IResult) iter.next();

System.out.println(r.getMessage());

...

}

catch (Exception e) {

fail();

}

}

data is null - ready or not, NPE is coming!

Courtesy of @bocytko

Page 15: 33rd Degree 2013, Bad Tests, Good Tests

No smoke without tests class SystemAdminSmokeTest extends GroovyTestCase {

void testSmoke() {

def ds = new org.h2.jdbcx.JdbcDataSource(

URL: 'jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;MODE=Oracle',

user: 'sa', password: '')

def jpaProperties = new Properties()

jpaProperties.setProperty(

'hibernate.cache.use_second_level_cache', 'false')

jpaProperties.setProperty(

'hibernate.cache.use_query_cache', 'false')

def emf = new LocalContainerEntityManagerFactoryBean(

dataSource: ds, persistenceUnitName: 'my-domain',

jpaVendorAdapter: new HibernateJpaVendorAdapter(

database: Database.H2, showSql: true,

generateDdl: true), jpaProperties: jpaProperties)

…some more code below

}

Page 16: 33rd Degree 2013, Bad Tests, Good Tests

No smoke without tests class SystemAdminSmokeTest extends GroovyTestCase {

void testSmoke() {

// do not remove below code

// def ds = new org.h2.jdbcx.JdbcDataSource(

// URL: 'jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;MODE=Oracle',

// user: 'sa', password: '')

//

// def jpaProperties = new Properties()

// jpaProperties.setProperty(

// 'hibernate.cache.use_second_level_cache', 'false')

// jpaProperties.setProperty(

// 'hibernate.cache.use_query_cache', 'false')

//

// def emf = new LocalContainerEntityManagerFactoryBean(

// dataSource: ds, persistenceUnitName: 'my-domain',

// jpaVendorAdapter: new HibernateJpaVendorAdapter(

// database: Database.H2, showSql: true,

// generateDdl: true), jpaProperties: jpaProperties)

…some more code below, all commented out :(

}

Page 17: 33rd Degree 2013, Bad Tests, Good Tests

Let's follow the leader!

@Test

public class ExampleTest {

public void testExample() {

assertTrue(true);

}

}

Page 18: 33rd Degree 2013, Bad Tests, Good Tests

Uh-oh, I feel lonely...

@Test

public class ExampleTest {

public void testExample() {

assertTrue(true);

}

}

Page 19: 33rd Degree 2013, Bad Tests, Good Tests

Conclusions

• Automation!

• Running

• Verification

• Do not live with broken window

• And remember there is no one else to fix them but

you!

• It is a full time job!

• You should be informed why your test failed

• Master your tools

• …at least learn the basics!

Page 20: 33rd Degree 2013, Bad Tests, Good Tests

Use of the real objects obscures the test

@Test

public void shouldGetTrafficTrend() {

//given

TrafficTrendProvider trafficTrendProvider

= mock(TrafficTrendProvider.class);

Report report = new Report(null, "", 1, 2, 3,

BigDecimal.ONE, BigDecimal.ONE, 1);

TrafficTrend trafficTrend = new TrafficTrend(report, report,

new Date(), new Date(), new Date(), new Date());

given(trafficTrendProvider.getTrafficTrend())

.willReturn(trafficTrend);

TrafficService service

= new TrafficService(trafficTrendProvider);

//when

TrafficTrend result = service.getTrafficTrend();

//then

assertThat(result).isEqualTo(trafficTrend);

}

Page 21: 33rd Degree 2013, Bad Tests, Good Tests

Use of the real objects obscures the test

@Test

public void shouldGetTrafficTrend() {

//given

TrafficTrendProvider trafficTrendProvider

= mock(TrafficTrendProvider.class);

TrafficTrend trafficTrend = mock(TrafficTrend.class);

given(trafficTrendProvider.getTrafficTrend())

.willReturn(trafficTrend);

TrafficService service

= new TrafficService(trafficTrendProvider);

//when

TrafficTrend result = service.getTrafficTrend();

//then

assertThat(result).isEqualTo(trafficTrend);

}

Page 22: 33rd Degree 2013, Bad Tests, Good Tests

Mock'em All!

@Test

public void shouldAddTimeZoneToModelAndView() {

//given

UserFacade userFacade = mock(UserFacade.class);

ModelAndView modelAndView = mock(ModelAndView.class);

given(userFacade.getTimezone()).willReturn("timezone X");

//when

new UserDataInterceptor(userFacade)

.postHandle(null, null, null, modelAndView);

//then

verify(modelAndView).addObject("timezone", "timezone X");

}

Page 23: 33rd Degree 2013, Bad Tests, Good Tests

Mock'em All!

@Test

public void shouldAddTimeZoneToModelAndView() {

//given

UserFacade userFacade = mock(UserFacade.class);

ModelAndView modelAndView = mock(ModelAndView.class);

given(userFacade.getTimezone()).willReturn("timezone X");

//when

new UserDataInterceptor(userFacade)

.postHandle(null, null, null, modelAndView);

//then

verify(modelAndView).addObject("timezone", "timezone X");

}

ModelAndView from SpringMVC – a mere container for data, without any behaviour

Page 24: 33rd Degree 2013, Bad Tests, Good Tests

Use the front door

@Test

public void shouldAddTimeZoneToModelAndView() {

//given

UserFacade userFacade = mock(UserFacade.class);

ModelAndView modelAndView = new ModelAndView();

given(userFacade.getTimezone()).willReturn("timezone X");

//when

new UserDataInterceptor(userFacade)

.postHandle(null, null, null, modelAndView);

//then

assertThat(modelAndView).contains("timezone", "timezone X");

}

a pseudocode but that is what we mean

Page 25: 33rd Degree 2013, Bad Tests, Good Tests

Mock'em All!

Public class Util {

public String getUrl(User user, String timestamp) {

String name = user.getFullName();

String url = baseUrl

+"name="+URLEncoder.encode(name, "UTF-8")

+"&timestamp="+timestamp;

return url;

}

public String getUrl(User user) {

Date date = new Date();

Long time = date.getTime()/1000; //convert ms to seconds

String timestamp = time.toString();

return getUrl(user, timestamp);

}

}

Developer wants to check whether timestamp is added to the URL when this method is used

Page 26: 33rd Degree 2013, Bad Tests, Good Tests

Mock'em All!

Public class Util {

public String getUrl(User user, String timestamp) {

...

}

public String getUrl(User user) {

...

}

}

@Test

public void shouldUseTimestampMethod() {

//given

Util util = new Util();

Util spyUtil = spy(util);

//when

spyUtil.getUrl(user);

//then

verify(spyUtil).getUrl(eq(user), anyString());

}

Page 27: 33rd Degree 2013, Bad Tests, Good Tests

Use the front door

@Test

public void shouldAddTimestampToGeneratedUrl() {

//given

TimeProvider timeProvider = mock(TimeProvider.class);

Util util = new Util(timeProvider);

when(timeProvider.getTime()).thenReturn("12345");

util.set(timeProvider);

//when

String url = util.getUrl(user);

//then

assertThat(url).contains("timestamp=12345");

}

Dependency injection will save us

Page 28: 33rd Degree 2013, Bad Tests, Good Tests

Single Responsibility Principle

A test should have one and only one reason to fail.

Page 29: 33rd Degree 2013, Bad Tests, Good Tests

Testing two things at once

@DataProvider

public Object[][] data() {

return new Object[][] { {"48", true}, {"+48", true},

{"++48", true}, {"+48503", true}, {"+4", false},

{"++4", false}, {"", false},

{null, false}, {" ", false}, };

}

@Test(dataProvider = "data")

public void testQueryVerification(String query, boolean expected) {

assertEquals(expected, FieldVerifier.isValidQuery(query));

}

Page 30: 33rd Degree 2013, Bad Tests, Good Tests

Testing two things at once

@DataProvider

public Object[][] data() {

return new Object[][] { {"48", true}, {"+48", true},

{"++48", true}, {"+48503", true}, {"+4", false},

{"++4", false}, {"", false},

{null, false}, {" ", false}, };

}

@Test(dataProvider = "data")

public void testQueryVerification(String query, boolean expected) {

assertEquals(expected, FieldVerifier.isValidQuery(query));

}

testQueryVerification1() {

assertEquals(true, FieldVerifier.isValidQuery(„48”));

}

testQueryVerification2() {

assertEquals(true, FieldVerifier.isValidQuery(„+48”));

}

testQueryVerification3() {

assertEquals(true, FieldVerifier.isValidQuery(„++48”));

}

testQueryVerification4() {

assertEquals(true, FieldVerifier.isValidQuery(„+48503”));

}

...

Page 31: 33rd Degree 2013, Bad Tests, Good Tests

Concentrate on one feature

@DataProvider

public Object[][] validQueries() {

return new Object[][] { {"48"}, {"48123"},

{"+48"}, {"++48"}, {"+48503"}};

}

@Test(dataProvider = "validQueries")

public void shouldRecognizeValidQueries(String validQuery) {

assertTrue(FieldVerifier.isValidQuery(validQuery));

}

@DataProvider

public Object[][] invalidQueries() {

return new Object[][] {

{"+4"}, {"++4"}, {""}, {null}, {" "} };

}

@Test(dataProvider = "invalidQueries")

public void shouldRejectInvalidQueries(String invalidQuery) {

assertFalse(FieldVerifier.isValidQuery(invalidQuery));

}

Page 32: 33rd Degree 2013, Bad Tests, Good Tests

“And”

@Test

public void shouldReturnRedirectViewAndSendEmail() {

//given

given(bindingResult.hasErrors()).willReturn(false);

given(userData.toEntity()).willReturn(user);

given(userService.saveNewUser(eq(userData.toEntity())))

.willReturn(user);

//when

ModelAndView userRegisterResult = userRegisterController

.registerUser(userData, bindingResult, request);

//then

assertThat(userRegisterResult.getViewName())

.isEqualTo("redirect:/signin");

verify(mailSender).sendRegistrationInfo(user);

}

Page 33: 33rd Degree 2013, Bad Tests, Good Tests

One feature at a time

@Test

public void shouldRedirectToSigninPageWhenRegistrationSuceeded () {

...

}

@Test

public void shouldNotifyAboutNewUserRegistration() {

...

}

Hint: forget about methods

Page 34: 33rd Degree 2013, Bad Tests, Good Tests

Readability is the king

Page 35: 33rd Degree 2013, Bad Tests, Good Tests

Who the heck is “user_2” ?

@DataProvider

public static Object[][] usersPermissions() {

return new Object[][]{

{"user_1", Permission.READ},

{"user_1", Permission.WRITE},

{"user_1", Permission.REMOVE},

{"user_2", Permission.WRITE},

{"user_2", Permission.READ},

{"user_3", Permission.READ}

};

}

Page 36: 33rd Degree 2013, Bad Tests, Good Tests

Ah, logged user can read and write...

@DataProvider

public static Object[][] usersPermissions() {

return new Object[][]{

{ADMIN, Permission.READ},

{ADMIN, Permission.WRITE},

{ADMIN, Permission.REMOVE},

{LOGGED, Permission.WRITE},

{LOGGED, Permission.READ},

{GUEST, Permission.READ}

};

}

Page 37: 33rd Degree 2013, Bad Tests, Good Tests

domain1, domain2, domain3, ...

Page 38: 33rd Degree 2013, Bad Tests, Good Tests

domain1, domain2, domain3, ...

Page 39: 33rd Degree 2013, Bad Tests, Good Tests

domain1, domain2, domain3, ...

Page 40: 33rd Degree 2013, Bad Tests, Good Tests

Do not make me learn the API!

server = new MockServer(responseMap, true,

new URL(SERVER_ROOT).getPort(), false);

Page 41: 33rd Degree 2013, Bad Tests, Good Tests

Do not make me learn the API!

server = new MockServer(responseMap, true,

new URL(SERVER_ROOT).getPort(), false);

private static final boolean RESPONSE_IS_A_FILE = true;

private static final boolean NO_SSL = false;

server = new MockServer(responseMap, RESPONSE_IS_A_FILE,

new URL(SERVER_ROOT).getPort(), NO_SSL);

Page 42: 33rd Degree 2013, Bad Tests, Good Tests

Do not make me learn the API!

server = new MockServer(responseMap, true,

new URL(SERVER_ROOT).getPort(), false);

server = createFileNonSSLMockServer(responseMap);

Page 43: 33rd Degree 2013, Bad Tests, Good Tests

Do not make me learn the API!

server = new MockServer(responseMap, true,

new URL(SERVER_ROOT).getPort(), false);

server = new MockServerBuilder()

.withResponse(responseMap)

.withResponseType(FILE)

.withUrl(SERVER_ROOT)

.withoutSsl().create();

server = MockServerBuilder

.createFileNoSSLServer(responseMap, SERVER_ROOT);

Page 44: 33rd Degree 2013, Bad Tests, Good Tests

Naming is really important

Page 45: 33rd Degree 2013, Bad Tests, Good Tests

Test methods names are important

• When test fails

• Relation to focused tests

Page 46: 33rd Degree 2013, Bad Tests, Good Tests

Test methods names are important

@Test

public void testOperation() {

configureRequest("/validate")

rc = new RequestContext(parser, request)

assert rc.getConnector() == null

assert rc.getOperation().equals("validate")

}

Page 47: 33rd Degree 2013, Bad Tests, Good Tests

“should” is better than “test”

• shouldRejectInvalidRequests()

• shouldSaveNewUserToDatabase()

• constructorShouldFailWithNegativePrice()

• shouldReturnOnlyUsersWithGivenName()

• testOperation()

• testQuery()

• testConstructor()

• testFindUsersWithFilter()

Page 48: 33rd Degree 2013, Bad Tests, Good Tests

“should” is better than “test”

• Starting test method names

with “should” steers you in

the right direction.

• “test” prefix makes your test

method a limitless bag

where you throw everything

worth testing

http://www.greenerideal.com/

http://jochopra.blogspot.com/

Page 49: 33rd Degree 2013, Bad Tests, Good Tests

Test methods names are important

@Test public void testQuery(){ when(q.getResultList()).thenReturn(null); assertNull(dao.findByQuery(Transaction.class, q, false)); assertNull(dao.findByQuery(Operator.class, q, false)); assertNull(dao.findByQuery(null, null, false)); List result = new LinkedList(); when(q.getResultList()).thenReturn(result); assertEquals(dao.findByQuery(Transaction.class, q, false), result); assertEquals(dao.findByQuery(Operator.class, q, false), result); assertEquals(dao.findByQuery(null, null, false), null); when(q.getSingleResult()).thenReturn(null); assertEquals(dao.findByQuery(Transaction.class, q, true).size(), 0); assertEquals(dao.findByQuery(Operator.class, q, true).size(), 0); assertEquals(dao.findByQuery(null, null, true), null); when(q.getSingleResult()).thenReturn(t); assertSame(dao.findByQuery(Transaction.class, q, true).get(0), t); when(q.getSingleResult()).thenReturn(o); assertSame(dao.findByQuery(Operator.class, q, true).get(0), o); when(q.getSingleResult()).thenReturn(null); assertSame(dao.findByQuery(null, null, true), null); }

Page 50: 33rd Degree 2013, Bad Tests, Good Tests

Test methods names are important

@Test public void shouldReturnNullListWhenDaoReturnsNull { when(q.getResultList()).thenReturn(null); assertNull(dao.findByQuery(Transaction.class, q, false)); assertNull(dao.findByQuery(Operator.class, q, false)); assertNull(dao.findByQuery(null, null, false)); } public void shouldReturnEmptyListWhenDaoReturnsIt { List result = new LinkedList(); when(q.getResultList()).thenReturn(result); assertEquals(dao.findByQuery(Transaction.class, q, false), result); assertEquals(dao.findByQuery(Operator.class, q, false), result); assertEquals(dao.findByQuery(null, null, false), null); } public void shouldReturnNullSingleResultWhenDaoReturnsNull { when(q.getSingleResult()).thenReturn(null); assertEquals(dao.findByQuery(Transaction.class, q, true).size(), 0); assertEquals(dao.findByQuery(Operator.class, q, true).size(), 0); assertEquals(dao.findByQuery(null, null, true), null); } public void shouldReturnSingleResultReturnedByDao { when(q.getSingleResult()).thenReturn(t); assertSame(dao.findByQuery(Transaction.class, q, true).get(0), t); when(q.getSingleResult()).thenReturn(o); assertSame(dao.findByQuery(Operator.class, q, true).get(0), o); when(q.getSingleResult()).thenReturn(null); assertSame(dao.findByQuery(null, null, true), null); }

Page 51: 33rd Degree 2013, Bad Tests, Good Tests

Assertion part is freaking huge public void shouldPreDeployApplication() {

// given

Artifact artifact = mock(Artifact.class);

when(artifact.getFileName()).thenReturn("war-artifact-2.0.war");

ServerConfiguration config

= new ServerConfiguration(ADDRESS, USER, KEY_FILE, TOMCAT_PATH, TEMP_PATH);

Tomcat tomcat = new Tomcat(HTTP_TOMCAT_URL, config);

String destDir = new File(".").getCanonicalPath() + SLASH + "target" + SLASH;

new File(destDir).mkdirs();

// when

tomcat.preDeploy(artifact, new FakeWar(WAR_FILE_LENGTH));

//then

JSch jsch = new JSch();

jsch.addIdentity(KEY_FILE);

Session session = jsch.getSession(USER, ADDRESS, 22);

session.setConfig("StrictHostKeyChecking", "no");

session.connect();

Channel channel = session.openChannel("sftp");

session.setServerAliveInterval(92000);

channel.connect();

ChannelSftp sftpChannel = (ChannelSftp) channel;

sftpChannel.get(TEMP_PATH + SLASH + artifact.getFileName(), destDir);

sftpChannel.exit();

session.disconnect();

File downloadedFile = new File(destDir, artifact.getFileName());

assertThat(downloadedFile).exists().hasSize(WAR_FILE_LENGTH);

}

Page 52: 33rd Degree 2013, Bad Tests, Good Tests

Just say it

public void shouldPreDeployApplication() {

// given

Artifact artifact = mock(Artifact.class);

when(artifact.getFileName())

.thenReturn(ARTIFACT_FILE_NAME);

ServerConfiguration config

= new ServerConfiguration(ADDRESS, USER,

KEY_FILE, TOMCAT_PATH, TEMP_PATH);

Tomcat tomcat = new Tomcat(HTTP_TOMCAT_URL, config);

// when

tomcat.preDeploy(artifact, new FakeWar(WAR_FILE_LENGTH));

// then

SSHServerAssert.assertThat(ARTIFACT_FILE_NAME)

.existsOnServer(tomcat).hasSize(WAR_FILE_LENGTH);

}

Page 53: 33rd Degree 2013, Bad Tests, Good Tests

Just say it

public void shouldPreDeployApplication() {

// given

Artifact artifact = mock(Artifact.class);

when(artifact.getFileName())

.thenReturn(ARTIFACT_FILE_NAME);

ServerConfiguration config

= new ServerConfiguration(ADDRESS, USER,

KEY_FILE, TOMCAT_PATH, TEMP_PATH);

Tomcat tomcat = new Tomcat(HTTP_TOMCAT_URL, config);

// when

tomcat.preDeploy(artifact, new FakeWar(WAR_FILE_LENGTH));

// then

assertThatFileIsOnServer(ARTIFACT_FILE_NAME,

Tomcat, WAR_FILE_LENGTH);

}

WHY NOT USE WHY NOT CREATE

A PRIVATE ASSERTION METHOD?

Page 54: 33rd Degree 2013, Bad Tests, Good Tests

Asserting using private methods

@Test

public void testChargeInRetryingState() throws Exception {

// given

TxDTO request = createTxDTO(RequestType.CHARGE);

AndroidTransaction androidTransaction = ...

request.setTransaction(androidTransaction);

// when

final TxDTO txDTO = processor.processRequest(request);

// then

List<AndroidTransactionStep> steps

= new ArrayList<>(androidTransaction.getSteps());

AndroidTransactionStep lastStep = steps.get(steps.size() - 1);

assertEquals(lastStep.getTransactionState(), CHARGED_PENDING);

assertEquals(txDTO.getResultCode(), CHARGED);

}

Page 55: 33rd Degree 2013, Bad Tests, Good Tests

Asserting using private methods

@Test

public void testChargeInRetryingState() throws Exception {

// given

TxDTO request = createTxDTO(RequestType.CHARGE);

AndroidTransaction androidTransaction = ...

request.setTransaction(androidTransaction);

// when

final TxDTO txDTO = processor.processRequest(request);

// then

List<AndroidTransactionStep> steps

= new ArrayList<>(androidTransaction.getSteps());

AndroidTransactionStep lastStep = steps.get(steps.size() - 1);

assertEquals(lastStep.getTransactionState(), CHARGED_PENDING);

assertEquals(txDTO.getResultCode(), CHARGED);

}

WHY NOT CREATE A PRIVATE ASSERTION METHOD?

Page 56: 33rd Degree 2013, Bad Tests, Good Tests

Asserting using private methods

@Test

public void testChargeInRetryingState() throws Exception {

// given

TxDTO request = createTxDTO(RequestType.CHARGE);

AndroidTransaction androidTransaction = ...

// when

final TxDTO txDTO = processor.processRequest(request);

// then

assertState(request, androidTransaction,

CHARGED, CHARGE_PENDING, AS_ANDROID_TX_STATE,

ClientMessage.SUCCESS, ResultCode.SUCCESS);

}

Page 57: 33rd Degree 2013, Bad Tests, Good Tests

Matchers vs. private methods assertState(TxDTO txDTO, AndroidTransaction androidTransaction,

AndroidTransactionState expectedAndroidState,

AndroidTransactionState expectedPreviousAndroidState,

ExtendedState expectedState,

String expectedClientStatus, ResultCode expectedRequestResultCode) {

final List<AndroidTransactionStep> steps

= new ArrayList<>(androidTransaction.getTransactionSteps());

final boolean checkPreviousStep = expectedAndroidState != null;

assertTrue(steps.size() >= (checkPreviousStep ? 3 : 2));

if (checkPreviousStep) {

AndroidTransactionStep lastStep = steps.get(steps.size() - 2);

assertEquals(lastStep.getTransactionState(),

expectedPreviousAndroidState);

}

final AndroidTransactionStep lastStep = steps.get(steps.size() - 1);

assertEquals(lastStep.getTransactionState(), expectedAndroidState);

assertEquals(lastStep.getMessage(), expectedClientStatus);

assertEquals(txDTO.getResultCode(), expectedRequestResultCode);

assertEquals(androidTransaction.getState(), expectedAndroidState);

assertEquals(androidTransaction.getExtendedState(), expectedState);

if (expectedClientStatus == null) {

verifyZeroInteractions(client);

}

}

Page 58: 33rd Degree 2013, Bad Tests, Good Tests

Matchers vs. private methods

@Test

public void testChargeInRetryingState() throws Exception {

// given

TxDTO request = createTxDTO(CHARGE);

AndroidTransaction androidTransaction = ...

// when

final TxDTO txDTO = processor.processRequest(request);

// then

assertThat(androidTransaction).hasState(CHARGED)

.hasMessage(ClientMessage.SUCCESS)

.hasPreviousState(CHARGE_PENDING)

.hasExtendedState(null);

assertEquals(txDTO.getResultCode(), ResultCode.SUCCESS);

}

Page 59: 33rd Degree 2013, Bad Tests, Good Tests

Asserting implementation details

public void invalidTxShouldBeCanceled() {

...

String fileContent =

FileUtils.getContentOfFile("response.csv");

assertTrue(fileContent.contains(

"CANCEL,123,123cancel,billing_id_123_cancel,SUCCESS,"));

}

Page 60: 33rd Degree 2013, Bad Tests, Good Tests

Asserting implementation details

public void invalidTxShouldBeCanceled() {

...

String fileContent =

FileUtils.getContentOfFile("response.csv");

assertTrue(fileContent.contains(

"CANCEL,123,123cancel,billing_id_123_cancel,SUCCESS,"));

}

public void invalidTxShouldBeCanceled() {

...

String fileContent =

FileUtils.getContentOfFile("response.csv");

TxDTOAssert.assertThat(fileContent)

.hasTransaction("123cancel").withResultCode(SUCCESS);

}

Page 61: 33rd Degree 2013, Bad Tests, Good Tests

Know your tools

• Unit testing framework

• Use of temporary file rule

• Listeners

• Concurrency

• @Before/@After

• Parametrized tests

• Test dependencies

• Additional libraries

• Hamcrest, FEST, Mockito,

catch-exception, awaitility,

JunitParams, tempus-fugit, …

• Build tool

• Parallel execution

• CI

• IDE

• Templates

• Shortcuts

Page 62: 33rd Degree 2013, Bad Tests, Good Tests

Expected exceptions

@Test(expected=IndexOutOfBoundsException.class)

public void shouldThrowExceptionGettingElementOutsideTheList() {

MyList<Integer> list = new MyList<Integer>();

list.add(0);

list.add(1);

list.get(2);

}

Page 63: 33rd Degree 2013, Bad Tests, Good Tests

Expected exceptions

@Test(expected=IndexOutOfBoundsException.class)

public void shouldThrowExceptionGettingElementOutsideTheList() {

MyList<Integer> list = new MyList<Integer>();

list.add(0);

list.add(1);

list.get(2);

}

@Test

public void shouldThrowExceptionGettingtElementOutsideTheList() {

MyList<Integer> list = new MyList<Integer>();

list.add(0);

list.add(1);

catchException(list).get(2);

assertThat(caughtException())

.isExactlyInstanceOf(IndexOutOfBoundsException.class);

}

http://code.google.com/p/catch-exception/

Page 64: 33rd Degree 2013, Bad Tests, Good Tests

Expected exceptions (with catch-exception)

@Test

public void shouldThrowException() throws SmsException {

catchException(gutExtractor)

.extractGut(„invalid gut”);

then(caughtException())

.isInstanceOf(SmsException.class)

.hasMessage("Invalid gut")

.hasNoCause();

}

http://code.google.com/p/catch-exception/

Page 65: 33rd Degree 2013, Bad Tests, Good Tests

Awaitility

@Test

public void updatesCustomerStatus() throws Exception {

// Publish an asynchronous event:

publishEvent(updateCustomerStatusEvent);

// Awaitility lets you wait until

// the asynchronous operation completes:

await().atMost(5, SECONDS)

.until(costumerStatusIsUpdated());

...

}

http://code.google.com/p/awaitility/

Page 66: 33rd Degree 2013, Bad Tests, Good Tests

What do you really want to test?

@Test

public void shouldAddAUser() {

User user = new User();

userService.save(user);

assertEquals(dao.getNbOfUsers(), 1);

}

Page 67: 33rd Degree 2013, Bad Tests, Good Tests

You wanted to see that the number increased

@Test

public void shouldAddAUser() {

int nb = dao.getNbOfUsers();

User user = new User();

userService.save(user);

assertEquals(dao.getNbOfUsers(), nb + 1);

}

Because: 1) This is closer to what you wanted to test 2) There is no assumption about the database “users” table being empty

Page 68: 33rd Degree 2013, Bad Tests, Good Tests

The dream of stronger, random-powered tests

public void myTest() {

SomeObject obj = new SomeObject(

randomName(), randomValue(), ....);

// testing of obj here

}

Does it make your test stronger?

Page 69: 33rd Degree 2013, Bad Tests, Good Tests

The dream of stronger, random-powered tests

public void myTest() {

SomeObject obj = new SomeObject(

randomName(), randomValue(), ....);

// testing of obj here

}

Does it make your test stronger?

...or does it only bring confusion?

Test failed

Expected

SomeObject(„a”, „b”, ....)

but got

SomeObject(„*&O*$NdlF”, „#idSLNF”, ....)

Page 70: 33rd Degree 2013, Bad Tests, Good Tests

Random done wrong

public void myTest() {

SomeObject obj = new SomeObject(

a, b, c, productCode());

// testing of obj here

}

private String productCode(){

String[] codes = {"Code A", "Code B",

"Code C", "Code D"};

int index = rand.nextInt(codes.length);

return codes[index];

}

Page 71: 33rd Degree 2013, Bad Tests, Good Tests

Ceremony

@Test

public void shouldBeAdministrator() {

//given

User user = new Administrator();

//when

boolean administrator = user.isAdministrator();

boolean advertiser = user.isAdvertiser();

boolean domainer = user.isDomainer();

//then

assertThat(administrator).isTrue();

assertThat(advertiser).isFalse();

assertThat(domainer).isFalse();

}

Page 72: 33rd Degree 2013, Bad Tests, Good Tests

Ceremony

@Test

public void shouldBeAdministrator() {

User user = new Administrator();

assertThat(user.isAdministrator()).isTrue();

assertThat(user.isAdvertiser()).isFalse();

assertThat(user.isDomainer()).isFalse();

}

Page 73: 33rd Degree 2013, Bad Tests, Good Tests

Asking for troubles...

LoggingPropertyConfigurator configurator = mock(...);

BaseServletContextListener baseServletContextListener =

= new BaseServletContextListener(configurator)

@Test public void shouldLoadConfigProperties() {

baseServletContextListener.contextInitialized();

verify(configurator).configure(any(Properties.class));

}

@Test(expected = LoggingInitialisationException.class)

public void shouldThrowExceptionIfCantLoadConfiguration() {

System.setProperty("logConfig", "nonExistingFile");

baseServletContextListener.contextInitialized();

}

Should load some default config

Should load this specific file

Page 74: 33rd Degree 2013, Bad Tests, Good Tests

Asking for troubles...

LoggingPropertyConfigurator configurator = mock(...);

BaseServletContextListener baseServletContextListener =

= new BaseServletContextListener(configurator)

@Test public void shouldLoadConfigProperties() {

baseServletContextListener.contextInitialized();

verify(configurator).configure(any(Properties.class));

}

@Test(expected = LoggingInitialisationException.class)

public void shouldThrowExceptionIfCantLoadConfiguration() {

System.setProperty("logConfig", "nonExistingFile");

baseServletContextListener.contextInitialized();

}

@Before

public void cleanSystemProperties() {

...

}

Page 75: 33rd Degree 2013, Bad Tests, Good Tests

Test-last? No!

• makes people not write tests at all

• makes people do only happy path testing

• tests reflect the implementation

Page 76: 33rd Degree 2013, Bad Tests, Good Tests

For six or eight hours spread over the next few weeks I struggled to get the first test written and running. Writing tests for Eclipse plug-ins is not trivial, so it’s not surprising I had some trouble. [...] In six or eight hours of solid programming time, I can still make significant progress. If I’d just written some stuff and verified it by hand, I would probably have the final answer to whether my idea is actually worth money by now. Instead, all I have is a complicated test that doesn’t work, a pile of frustration, eight fewer hours in my life, and the motivation to write another essay.

Kent Beck, Just Ship, Baby

Always TDD?

Page 77: 33rd Degree 2013, Bad Tests, Good Tests

There is so much more to discuss…

• Integration / end-to-end tests which are not parametrized (so they all try to set up jetty on port 8080),

• Tests which should be really unit, but use Spring context to create objects,

• Tests with a lot of dependencies between them (a nightmare to maintain!),

• Tests which are overspecified and will fail whenever you touch the production code,

• Tests with monstrous objects-creation code,

• Tests which run slow,

• Tests which try to cover the deficiencies of production code and end up being a total mess,

• Tests which verify methods instead of verifying responsibilities of a class,

• Happy path tests,

• etc., etc.

Page 78: 33rd Degree 2013, Bad Tests, Good Tests

Treat tests as the first class citizens

• do it everyday or forget about it

• use the right tool for the job

• and learn to use it!

• do not live with broken windows

• respect KISS, SRP, DRY (?)

• write good code, and you will also write

good tests

• or rather write good tests and you

will get good code for free

• code review your tests

• do more than happy path testing

• do not make the reader learn the API,

make it obvious

• bad names lead to bad tests

• make tests readable using matchers,

builders and good names

• test behaviour not methods

• be pragmatic about the tests you write

• TDD always?

• what is the best way to test it?

unit/integration/end-to-end ?

• automate!

• always concentrate on what is worth

testing

• ask yourself questions like: 'is it

really important that X should send

message Y to Z?'

• use the front door – state testing before

interaction testing (mocks)

Page 79: 33rd Degree 2013, Bad Tests, Good Tests

You can learn more about writing

high quality tests by reading my

book – „Practical Unit Testing”.

You can also participate in

writing of my new (free!) e-

book devoted to bad and good

tests.

Thank you!