Clean Test Code
-
Upload
david-voelkel -
Category
Software
-
view
679 -
download
0
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
www.codecentric.de
blog.codecentric.de
Q&A
35
Creative Commons ShareAlike 4.0
The images used are public domain and can be found on Wikipedia.
License
36