Clean Test Code

Post on 12-Jul-2015

679 views 0 download

Tags:

Transcript of Clean Test Code

David Völkel, @davidvoelkel

Clean Test Code Agile Testing Days 11.11.2014

2

@davidvoelkel

Dev & Consultant

Software Craftsman

Test-Driven Development Software Design @softwerkskammer

Dirty Test Code

technical debt

velocity decrease

costs

latency agile

Clean Tests 4 attributes

reliable

readable

redundancy-free

focused

Conclusion

Q&A

xUnit-Patterns

BDD Functional tests

setup arrange given Input rows

execute act when “Fixture“

verify assert then Output rows

[teardown] [annihilate]

Test Phases

Input Output

Reliable Mind complexity, so avoid

• boolean assertions

assertTrue(age >= 18);

assertThat(age, greaterThan(17))

Reliable Mind complexity, so avoid

• boolean assertions • conditionals & complex loops

boolean containsName = false;

for (String name: names) {

if (name.equals("David")) {

containsName = true;

}

}

assertTrue(containsName); assertThat(names, hasItem("David"));

Readable Read >> write Executable specifications Living documentation

Abstraction avoid irrelevant details

Test Method Body Gerard Meszaros:

„When it is not important for something to be seen in the test method, it is important that it not

be seen in the test method! “

“In the order form the user should be able to finish the order process by pressing the cancel button”

@Test public void cancelProcess_inOrderForm() {

server.runningProcesses(0);

ProcessStatus process = server.startProcess(Process.ORDER);

processInstanceId = process.getInstanceId();

server.runningProcesses(1);

server.expectUserForm(processInstanceId, ORDER_FORM);

Form form = new Form();

form.setOrderDate("01.01.2015");

form.setStandardDelivery(false);

String taskId = server.taskIdFor(processInstanceId);

controller.submit(form,

Mockito.mock(BindingResult.class), null,

taskId, null, CANCEL);

server.runningProcesses(0);

}

“In the order form the user should be able to finish the order process by pressing the cancel button”

@Test public void cancelProcess_inOrderForm() {

server.runningProcesses(0);

ProcessStatus process = server.startProcess(Process.ORDER);

processInstanceId = process.getInstanceId();

server.runningProcesses(1);

server.expectUserForm(processInstanceId, ORDER_FORM);

Form form = new Form();

form.setOrderDate("01.01.2015");

form.setStandardDelivery(false);

String taskId = server.taskIdFor(processInstanceId);

controller.submit(form,

Mockito.mock(BindingResult.class), null,

taskId, null, CANCEL);

server.runningProcesses(0);

}

“In the order form the user should be able to finish the order process by pressing the cancel button”

„Testscript“ “Incidental Details”

@Test public void cancelProcess_inOrderForm() {

server.runningProcesses(0);

ProcessStatus process = server.stareProcess(Process.ORDER);

processInstanceId = process.getInstanceId();

server.runningProcesses(1);

server.expectUserForm(processInstanceId, ORDER_FORM);

Form form = new Form();

form.setOrderDate("01.01.2015");

form.setStandardDelivery(false);

String taskId = server.taskIdFor(processInstanceId);

controller.submit(form,

Mockito.mock(BindingResult.class), null,

taskId, null, CANCEL);

server.runningProcesses(0);

}

ORDER_FORM

CANCEL

runningProcesses(0);

Signal-Noise-Ratio? Single Level of Abstraction?

“In the order form the user should be able to finish the order process by pressing the cancel button”

@Test public void cancelProcess_inOrderForm() {

inForm(ORDER_FORM);

submitFormWithButton(CANCEL);

noProcessRunning();

}

“In the order form the user should be able to finish the order process by pressing the cancel button”

Names

Test class names public class AnOrderProcess {

OrderProcess process;

@Before public void createOrderProcess() {

process = new OrderProcess();

}

object under test

public class AnOrderProcess {

OrderProcess process;

@Before public void createOrderProcess() {

process = new OrderProcess();

}

@Test public void inOrderForm_cancel() {

process.setState(ORDER_FORM);

process.submit(CANCEL_BUTTON);

assertThat("process canceled", process.isCanceled(), equalTo(true));

}

Test method names

object under test

public class AnOrderProcess {

OrderProcess process;

@Before public void createOrderProcess() {

process = new OrderProcess();

}

@Test public void inOrderForm_cancel() {

process.setState(ORDER_FORM);

process.submit(CANCEL_BUTTON);

assertThat("process canceled", process.isCanceled(), equalTo(true));

}

Test method names

object under test

setup

@Before public void createOrderProcess() {

process = new OrderProcess();

}

@Test public void inOrderForm_cancel() {

process.setState(ORDER_FORM);

process.submit(CANCEL_BUTTON);

assertThat("process canceled", process.isCanceled(), equalTo(true));

}

Test method names

setup

execute

@Before public void createOrderProcess() {

process = new OrderProcess();

}

@Test public void inOrderForm_cancel() {

process.setState(ORDER_FORM);

process.submit(CANCEL_BUTTON);

assertThat("process canceled", process.isCanceled(), equalTo(true));

}

Test method names

setup

execute

verify

Audience? test maintainer search test failure cause business

Communicate

comprehensively OuT + setup + execute + verify Distribute: Class, method name, verify

Redundancy-free

Hierarchical tests public class AnOrderProcess {

@Before public void createOrderProcess() {

process = new OrderProcess();

}

public class InOrderForm {

@Before public void inOrderForm() {

process.setState(ORDER_FORM);

}

@Test public void whenCanceled_processIsStopped() {

process.submit(CANCEL_BUTTON);

assertThat("process stopped", process.isStopped(), equalTo(true));

}

@Test public void whenBought_orderIsConfirmed() {

process.submit(BUY_NOW_BUTTON);

assertThat("state", process.getState(), equalTo(ORDER_CONFIRMED));

}

}

extract common setup

OuT

Hierarchical tests

setup

execute verify

Helper methods

Object Mother

Test Data Builder

aCustomer("David"); CustomerTest.aCustomer("David");

CustomerMother.aCustomer("David"); CustomerMother.aCustomer("David", „Völkel"); CustomerMother.aCustomer(null, „Völkel", null, null, null, null, birthday);

aCustomer().withLastName("Völkel") .withBirthDay(birthDay) .build();

Common test data

void assertMoneyEquals(Money expected, Money actual) {

assertEquals("currency", expected.getCurrency(),actual.getCurrency()); assertEquals("amount", expected.getAmount(), actual.getAmount());

} Matcher<Money> equalTo(Money money) {

return allOf( property(money, Money::getCurrency, "currency"), property(money, Money::getAmount, "amount"));

}

Custom asserts via helper methods

Composed Hamcrest Matchers

Common verify

"Single Concept per Test" "One Execute per Test" "One Assert per Test"

Focused

State vs. testability

Focused

„Listen to your tests!“

Hard-to-test Code

Clean Tests 1. reliable 2. readable 3. redundancy-free 4. focussed

Clean Test Code, stay agile!

Resources Books „Clean Code“, Robert C. Martin „xUnit Test Patterns“, Gerard Meszaros „Effective Unittesting“, Lasse Koskela „Growing Object Oriented Software“,

Steve Freeman, Nat Pryce „Specification by Example”, Gojko Adzic

Videos Episodes 20-22 http://cleancoders.com/, Robert C. Martin

David Völkel

codecentric AG

Twitter: @davidvoelkel

david.voelkel@codecentric.de

www.codecentric.de

blog.codecentric.de

Q&A

35