Upload
others
View
0
Download
0
Embed Size (px)
Citation preview
1
1 Copyright 2014 Gerard Meszaroshttp://craft2014.testAutomationPatterns.com
Maximizing Expressiveness ofAutomated Tests
Gerard MeszarosIndependent Consultant
CTO of [email protected]
These Slides:http://craft2014.testAutomationPatterns.com
2 Copyright 2014 Gerard Meszaroshttp://craft2014.testAutomationPatterns.com
Product & I.T.
I.T.
EmbeddedTelecom
My Background
Gerard [email protected]
•Software developer
•Development manager
•Project Manager
•Software architect
•OOA/OOD Mentor
•Requirements (Use Case) Mentor
•XP/TDD Mentor
•Agile PM Mentor
•Test Automation Consultant & Trainer
•Lean/Agile Coach/Consultant
2
3 Copyright 2014 Gerard Meszaroshttp://craft2014.testAutomationPatterns.com
Agenda• Motivation
– Why Test Craftsmanship Matters• Managing Scope vs Detail
– In Functional (Acceptance) Tests• Achieving a Test-Friendly Architecture• Managing Scope vs Detail
– In Unit Tests
4 Copyright 2014 Gerard Meszaroshttp://craft2014.testAutomationPatterns.com
Do Your Tests Look Like:public void testAddItemQuantity_severalQuantity () throws Exception {
try {
// Setup Fixturefinal int QUANTITY = 5;Address billingAddress = new Address("1222 1st St SW",
"Calgary", "Alberta", "T2N 2V2", "Canada");Address shippingAddress = new Address("1333 1st St
SW", "Calgary", "Alberta", "T2N 2V2","Canada");
Customer customer = new Customer(99, "John", "Doe",new BigDecimal("30"), billingAddress,shippingAddress);
Product product = new Product(88, "SomeWidget", newBigDecimal("19.99"));
Invoice invoice = new Invoice(customer);// Exercise SUTinvoice.addItemQuantity(product, QUANTITY);// Verify OutcomeList lineItems = invoice.getLineItems();if (lineItems.size() == 1) {LineItem actualLineItem =
(LineItem)lineItems.get(0);assertEquals(invoice, actualLineItem.getInvoice());assertEquals(product, actualLineItem.getProduct());assertEquals(quantity,
actualLineItem.getQuantity());assertEquals(new BigDecimal("30"),
actualLineItem.getPercentDiscount());assertEquals(new BigDecimal("19.99"),
actualLineItem.getUnitPrice());assertEquals(new BigDecimal(“69.96"),
actualLineItem.getExtendedPrice());} else {assertTrue(“Invoice should have exactly one line
item“, false);}
} finally {
deleteObject(expectedLineItem);deleteObject(invoice);deleteObject(product);deleteObject(customer);deleteObject(billingAddress);deleteObject(shippingAddress);
:}
You might be questioning their value!
3
5 Copyright 2014 Gerard Meszaroshttp://craft2014.testAutomationPatterns.com
Where Automation Provides Value• Detecting Differences
– Find unexpected differences between differentenvironments (e.g. different browsers)
• “Pin the functionality”– Find unexpected changes between versions as the
product evolves over time• Avoid Wasting Effort
– testing manually when known issues exist– Testing things manually when it could be automated– Focus manual test effort on what automation doesn’t
6 Copyright 2014 Gerard Meszaroshttp://craft2014.testAutomationPatterns.com
Where Automation Provides Value (2)• Communicating Expectations
– Avoid building the wrong product– Behaviour / Example-Driven Development (BDD / EDD)– Acceptance Test Driven Development (ATDD)
• Find Issues Sooner– By running the tests more frequently (ideally after every
change)– Enabled by full automation (near zero cost)– Requires robust test automation
4
7 Copyright 2014 Gerard Meszaroshttp://craft2014.testAutomationPatterns.com
Where Automation Adds Cost• Direct Costs:
– Cost to write tests initially– Cost to investigate failing tests– Cost to fix failing tests
• Exacerbated by:– Too Many Tests– Too Fragile Tests– Too Complex Tests
8 Copyright 2014 Gerard Meszaroshttp://craft2014.testAutomationPatterns.com
TestAutomation
Effort
After Automation
Economics of Maintainability
Test Automation is a lot easier to sell on• Cost reduction than• Software Quality Improvement or• Quality of Life Improvement
DevelopmentEffort
saved effort
time
Increasedeffort(Hump) Ongoing
effortInitialeffort
Cost of TestAutomation + Ongoing
Maintenance
5
9 Copyright 2014 Gerard Meszaroshttp://craft2014.testAutomationPatterns.com
TestAutomation
Effort
saved effort
Economics of Maintainability
Test Automation is a lot easier to sell on• Cost reduction than• Software Quality Improvement or• Quality of Life Improvement
time
Initialeffort
Ongoingeffort
Cost of TestAutomation + Ongoing
Maintenance
Increasedeffort(Hump)
Unsustainable Automation
DevelopmentEffort
10 Copyright 2014 Gerard Meszaroshttp://craft2014.testAutomationPatterns.com
Agenda• Motivation• Managing Scope vs Detail
– In Functional (Acceptance) Tests• Achieving a Test-Friendly Architecture• Managing Scope vs Detail
– In Unit Tests
6
11 Copyright 2014 Gerard Meszaroshttp://craft2014.testAutomationPatterns.com
Test Automation Pyramid
Unit Tests
ComponentTests
SystemTests
Pyramid originally proposed by Mike Cohn
Exploratory Tests
• Large numbers of verysmall unit tests– Ensures integrity of code
• Smaller number offunctional tests for majorcomponents– Verify integration of units
• Even fewer tests for theentire application &workflow– Ensure application(s) support
users’ requirements• Tools to support effective
exploratory testing
12 Copyright 2014 Gerard Meszaroshttp://craft2014.testAutomationPatterns.com
Need to specify at the right detail and scope.Behavior Specification at Right Level
Broad Narrow
Detail
High
L
ow
Scope
Unit Tests
ComponentTests
SystemTests
Exploratory Tests
7
13 Copyright 2014 Gerard Meszaroshttp://craft2014.testAutomationPatterns.com
Need to specify at the right detail and scope.Behavior Specification at Right Level
Broad Narrow
Detail
High
L
ow
Scope
ComponentTests
SystemTests
14 Copyright 2014 Gerard Meszaroshttp://craft2014.testAutomationPatterns.com
ComponentTests
SystemTests
• Specify broad scope at minimum detail• Specify most detailed req’ts at narrowest scope
Behavior Specification at Right Level
Rules &Algorithms
Multi-Use Case
WorkflowsTransactions(Use Cases)
IncompleteSpec
Toomuch detailUnmaintainableBroad Narrow
Detail
High
L
ow
Scope
8
15 Copyright 2014 Gerard Meszaroshttp://craft2014.testAutomationPatterns.com
Use Cases & User Stories
ConfigureNotification
Rules
SuspendNotification
ResumeNotification
AccountHolder
ProcessTransaction
TransactionSettlement
Not
ify
base
d on
cha
rge
Type
Not
ify
base
d on
cha
rge
Cont
inen
tN
otif
y ba
sed
on c
harg
e Co
untr
yN
otif
y ba
sed
on c
harg
e Ci
ty &
Sta
te
Susp
end
Not
ific
atio
n
16 Copyright 2014 Gerard Meszaroshttp://craft2014.testAutomationPatterns.com
Testing Notifications - 1Given:
User and Accounts
Example:
When:Notification
Rule is Configured
Then:Notification RuleConfigured and
Active
Then:Notification Rule
is Active
9
17 Copyright 2014 Gerard Meszaroshttp://craft2014.testAutomationPatterns.com
Testing Notifications - 2
Then: ExpectedNotifications
Broad Scope (Multi-Actor);Medium Detail (Too much for the scope)
Example:
When: TheTransactions tobe processed
Use Case:Process
Transactions
18 Copyright 2014 Gerard Meszaroshttp://craft2014.testAutomationPatterns.com
Issues With This Test• Difficult to understand which TX’s should notify
– because cause (rules) and effect (notification) are farapart
• Only verifies one simple combination of rules– We will require many more tests to test all the other
combinations– Lots of repetition of workflow & data across test cases
• Simplest workflow;– More complex workflows will be even longer and harder
to understand• Tests will take a long time to run
– Due to need to configure first, then run transactionprocessing
10
19 Copyright 2014 Gerard Meszaroshttp://craft2014.testAutomationPatterns.com
What Can We Do?• Make tests shorter
– Fewer steps• Make tests less detailed
– Omit unnecessary information• Focus tests on specific aspects
of behaviour– Test/Spec Algorithms & Rules directly– Test less functionality, but more
thoroughly
Fine advice, but too vague
Raising abstractionlevel can help with this
and with this
RequiresTest-FriendlyArchitecture
20 Copyright 2014 Gerard Meszaroshttp://craft2014.testAutomationPatterns.com
Changing Level of Abstraction/Detail• Too detailed for verifying workflow and• Too broadly scoped for checking the rules• Need to Reduce Detail or Reduce Scope
Rules &Algorithms
Multi-Use Case
WorkflowsTransactions(Use Cases)
Toomuch detailUnmaintainable
IncompleteSpec
Broad Narrow
Detail
High
L
ow
Scope
11
21 Copyright 2014 Gerard Meszaroshttp://craft2014.testAutomationPatterns.com
Omitting Unnecessary Information• Ask not what you can include in a test…
• Ask instead:
• What can I leave out of this test?
“If it isn’t essential to conveying the essence ofthe behavior, it is essential to not include it.”
22 Copyright 2014 Gerard Meszaroshttp://craft2014.testAutomationPatterns.com
Specifying WorkflowGiven:
User and Accounts
Example:
When:Notification
Rule is Configured
Then:Notification RuleConfigured and
Active
Then:Notification Rule
is Active
12
23 Copyright 2014 Gerard Meszaroshttp://craft2014.testAutomationPatterns.com
Specifying WorkflowExample:
24 Copyright 2014 Gerard Meszaroshttp://craft2014.testAutomationPatterns.com
Specifying Workflow
Then: ExpectedNotifications
Example:
When: TheTransactions tobe processed
Use Case:Process
Transactions
13
25 Copyright 2014 Gerard Meszaroshttp://craft2014.testAutomationPatterns.com
Specifying WorkflowExample:
26 Copyright 2014 Gerard Meszaroshttp://craft2014.testAutomationPatterns.com
Specifying WorkflowExample:
14
27 Copyright 2014 Gerard Meszaroshttp://craft2014.testAutomationPatterns.com
Specifying Notification WorkflowExample:
Broad Scope (Multi-Actor);Minimum Detail (per Actor/Transaction);
Given:User &
Thresholds
When:Transactions
Are Processed
Then:We Expect
Notifications
The overall workflow should be defined at a very high level
credit
credit
28 Copyright 2014 Gerard Meszaroshttp://craft2014.testAutomationPatterns.com
Specifying Suspension WorkflowGiven BobMa has set notification threshold to $10,000
for all transactions on his accountWhen the bank processes debit for 15,000 to his accountThen BobMa receives notification for debit 15,000
When BobMa suspends notificationAnd the bank processes debit for 15,000 to his accountThen BobMa receives no notification
When BobMa resumes notificationAnd the bank processes debit for 15,000 to his accountThen BobMa receives notification for debit 15,000Broad Scope; Minimum Detail;
Irrelevant Details Omitted!
Over threshold
Over threshold
Over threshold
15
29 Copyright 2014 Gerard Meszaroshttp://craft2014.testAutomationPatterns.com
Changing Level of Abstraction/Detail• So, where should omitted details go?
Rules &Algorithms
Multi-Use Case
WorkflowsTransactions(Use Cases)
IncompleteSpec
Toomuch detailUnmaintainableBroad Narrow
Detail
High
L
ow
Scope
30 Copyright 2014 Gerard Meszaroshttp://craft2014.testAutomationPatterns.com
Single Use Case TestUse Case:
ManageNotifications
Data to be shown onManage Accounts Tab
Side effect of AddingA Notification
Medium Scope (Single Actor)Medium Detail (Transaction, not UI);
Example:
Data to be shown onManage Accounts Tab
Data to be shownon Manage
Notifications Tab
16
31 Copyright 2014 Gerard Meszaroshttp://craft2014.testAutomationPatterns.com
Changing Level of Abstraction/Detail• Need the detail to specify notification rules• Therefore, need to reduce scope even more
Rules &Algorithms
Multi-Use Case
WorkflowsTransactions(Use Cases)
IncompleteSpec
Toomuch detailUnmaintainableBroad Narrow
Detail
High
L
ow
Scope
32 Copyright 2014 Gerard Meszaroshttp://craft2014.testAutomationPatterns.com
Business Rule Specs
Configuration Process TransactionThreshold per Charge Type
Narrow Scope (Single Rule)High Detail (Everything that matters)
Example:
Given theserules
When we askNotificationRequired?with this transaction:
Then: Theanswer should be
17
33 Copyright 2014 Gerard Meszaroshttp://craft2014.testAutomationPatterns.com
Agenda• Motivation• Managing Scope vs Detail
– In Functional (Acceptance) Tests• Achieving a Test-Friendly Architecture• Managing Scope vs Detail
– In Unit Tests
34 Copyright 2014 Gerard Meszaroshttp://craft2014.testAutomationPatterns.com
• Must test through User Interface• Must load lots of data
Test - After Architecture
System Under Test
Should weNotify?Process
Transaction
ConfigureNotificationThreshold
NotificationRules
TransactionInterface
ConfigurationUser
Interface
NotificationLog
WorkflowTest
DoNotification.
18
35 Copyright 2014 Gerard Meszaroshttp://craft2014.testAutomationPatterns.com
• Need to provide API’s to invoke functionalitydirectly bypassing the UI
• Supports Workflow and• Use Case / Transaction test automation
System Under Test
Test-Driven Architecture
Should weNotify?
DoNotification.
ProcessTransaction
ConfigureNotificationThreshold
NotificationRules
NotificationLog
TransactionInterface
ConfigurationUser
InterfaceWorkflow
Test
36 Copyright 2014 Gerard Meszaroshttp://craft2014.testAutomationPatterns.com
Test-Driven Architecture – Use Case Tests
System Under Test
• Same API supports Transaction (Use Case)tests:
Should weNotify?
DoNotification.
ProcessTransaction
ConfigureNotificationThreshold
NotificationRules
NotificationLog
TransactionInterface
ConfigurationUser
Interface
ConfigurationTX Test
19
37 Copyright 2014 Gerard Meszaroshttp://craft2014.testAutomationPatterns.com
Test-Driven Architecture – Business Rules• Encapsulation of Rules in “Rules Component”
• which can be accessed directly via the Test
Should weNotify?
DoNotification.
ProcessTransaction
ConfigureNotificationThreshold
NotificationRules
NotificationLog
TransactionInterface
ConfigurationUser
Interface
NotificationMethod Test
NotificationRules FakeSystem Under Test
NotificationRule Test
38 Copyright 2014 Gerard Meszaroshttp://craft2014.testAutomationPatterns.com
• Encapsulation of Rules in “Rules Component”• which can be accessed directly via the Test
• Component Accept Rules via “Data Injection”– Simplify Test Automation by avoiding need to configure
• Forces Design-for-testability– Only When Automation a Dev’t Responsibility
Business Rules Tests Encourage:
Should weNotify?
NotificationRules Fake
NotificationRule Test
20
39 Copyright 2014 Gerard Meszaroshttp://craft2014.testAutomationPatterns.com
Agenda• Motivation• Managing Scope vs Detail
– In Functional (Acceptance) Tests• Achieving a Test-Friendly Architecture• Managing Scope vs Detail
– In Unit Tests
40 Copyright 2014 Gerard Meszaroshttp://craft2014.testAutomationPatterns.com
(Non-)Maintainable Test Codepublic void testAddItemQuantity_severalQuantity ()
throws Exception {try {
// Setup Fixturefinal int QUANTITY = 5;Address billingAddress = new Address("1222 1st St
SW", "Calgary", "Alberta", "T2N 2V2","Canada");
Address shippingAddress = new Address("1333 1stSt SW", "Calgary", "Alberta", "T2N 2V2","Canada");
Customer customer = new Customer(99, "John","Doe", new BigDecimal("30"),billingAddress, shippingAddress);
Product product = new Product(88, "SomeWidget",new BigDecimal("19.99"));
Invoice invoice = new Invoice(customer);// Exercise SUTinvoice.addItemQuantity(product, QUANTITY);// Verify OutcomeList lineItems = invoice.getLineItems();if (lineItems.size() == 1) {LineItem actualLineItem =
(LineItem)lineItems.get(0);assertEquals(invoice,
actualLineItem.getInvoice());assertEquals(product,
actualLineItem.getProduct());assertEquals(quantity,
actualLineItem.getQuantity());assertEquals(new BigDecimal("30"),
actualLineItem.getPercentDiscount());assertEquals(new BigDecimal("19.99"),
actualLineItem.getUnitPrice());assertEquals(new BigDecimal(“69.96"),
actualLineItem.getExtendedPrice());} else {assertTrue(“Invoice should have exactly one
line item“, false);}
:}
public voidtestAddItemQuantity_severalQuantity (){// Given:
QUANTITY = 5 ;product = givenAnyProduct();invoice = givenAnEmptyInvoice();// When:invoice.addItemQuantity(
product, QUANTITY);// Then:assertExactlyOneLineItem(
invoice,expectedItem(
invoice,product, QUANTITY,product.getPrice()*
QUANTITY) );}
21
41 Copyright 2014 Gerard Meszaroshttp://craft2014.testAutomationPatterns.com
Example
• Test addItemQuantity and removeLineItemmethods of Invoice
CustomerFirstNameLastNameDiscount
InvoiceAddressshipping
billing
1
billing
Product
LineItemQuantityUnitPrice
ExtendedPricePercentDiscount
invoicesinvoicedCustomer
shipping
42 Copyright 2014 Gerard Meszaroshttp://craft2014.testAutomationPatterns.com
The Whole Testpublic void testAddItemQuantity_severalQuantity () throws Exception {
// Setup Fixturefinal int QUANTITY = 5;Address billingAddress = new Address("1222 1st St SW", "Calgary",
"Alberta", "T2N 2V2", "Canada");Address shippingAddress = new Address("1333 1st St SW", "Calgary",
"Alberta", "T2N 2V2", "Canada");Customer customer = new Customer(99, "John", "Doe", new BigDecimal("30"),
billingAddress, shippingAddress);Product product = new Product(88, "SomeWidget", new BigDecimal("19.99"));Invoice invoice = new Invoice(customer);// Exercise SUTinvoice.addItemQuantity(product, QUANTITY);// Verify OutcomeList lineItems = invoice.getLineItems();if (lineItems.size() == 1) {
LineItem actualLineItem = (LineItem)lineItems.get(0);assertEquals(invoice, actualLineItem.getInvoice());assertEquals(product, actualLineItem.getProduct());assertEquals(quantity, actualLineItem.getQuantity());assertEquals(new BigDecimal("30"), actualLineItem.getPercentDiscount());assertEquals(new BigDecimal("19.99"), actualLineItem.getUnitPrice());assertEquals(new BigDecimal(“69.96"),
actualLineItem.getExtendedPrice());} else {
assertTrue(“Invoice should have exactly one line item“, false);} :
}
Given: ???
When we calladdItemQuantity
Then: ???
22
43 Copyright 2014 Gerard Meszaroshttp://craft2014.testAutomationPatterns.com
Obtuse Assertion
Verifying the Outcome
List lineItems = invoice.getLineItems();
if (lineItems.size() == 1) {
LineItem actualLineItem = (LineItem)lineItems.get(0);
assertEquals(invoice, actualLineItem.getInvoice());
assertEquals(product, actualLineItem.getProduct());
assertEquals(quantity, actualLineItem.getQuantity());
assertEquals(new BigDecimal("30"),actualLineItem.getPercentDiscount());
assertEquals(new BigDecimal("19.99"),actualLineItem.getUnitPrice());
assertEquals(new BigDecimal(“69.96"),actualLineItem.getExtendedPrice());
} else {
assertTrue(“Invoice should have exactly one line item“,false);
}
44 Copyright 2014 Gerard Meszaroshttp://craft2014.testAutomationPatterns.com
Use Better Assertion
List lineItems = invoice.getLineItems();
if (lineItems.size() == 1) {
LineItem actualLineItem = (LineItem)lineItems.get(0);
assertEquals(invoice, actualLineItem.getInvoice());
assertEquals(product, actualLineItem.getProduct());
assertEquals(quantity, actualLineItem.getQuantity());
assertEquals(new BigDecimal("30"),actualLineItem.getPercentDiscount());
assertEquals(new BigDecimal("19.99"),actualLineItem.getUnitPrice());
assertEquals(new BigDecimal(“69.96"),actualLineItem.getExtendedPrice());
} else {
fail("invoice should have exactly one line item");
}}
Refactoring
23
45 Copyright 2014 Gerard Meszaroshttp://craft2014.testAutomationPatterns.com
Hard-WiredTest Data
Use Better Assertion
List lineItems = invoice.getLineItems();
if (lineItems.size() == 1) {
LineItem actualLineItem = (LineItem)lineItems.get(0);
assertEquals(invoice, actualLineItem.getInvoice());
assertEquals(product, actualLineItem.getProduct());
assertEquals(quantity, actualLineItem.getQuantity());
assertEquals(new BigDecimal("30"),actualLineItem.getPercentDiscount());
assertEquals(new BigDecimal("19.99"),actualLineItem.getUnitPrice());
assertEquals(new BigDecimal(“69.96"),actualLineItem.getExtendedPrice());
} else {
fail("invoice should have exactly one line item");
}}
Fragile Tests
Refactoring
46 Copyright 2014 Gerard Meszaroshttp://craft2014.testAutomationPatterns.com
Expected Object
List lineItems = invoice.getLineItems();
if (lineItems.size() == 1) {
LineItem actualLineItem = (LineItem)lineItems.get(0);
LineItem expectedLineItem =newLineItem(invoice, product, QUANTITY);
assertEquals(expectedLineItem.getInvoice(),actualLineItem.getInvoice());
assertEquals(expectedLineItem.getProduct(),actualLineItem.getProduct());
assertEquals(expectedLineItem.getQuantity(),actualLineItem.getQuantity());
assertEquals(expectedLineItem.getPercentDiscount(),actualLineItem.getPercentDiscount());
assertEquals(expectedLineItem.getUnitPrice(),actualLineItem.getUnitPrice());
assertEquals(expectedLineItem.getExtendedPrice(),actualLineItem.getExtendedPrice());
} else {
fail("invoice should have exactly one line item");
}}
Pattern
24
47 Copyright 2014 Gerard Meszaroshttp://craft2014.testAutomationPatterns.com
Verbose Test
Expected Object
List lineItems = invoice.getLineItems();
if (lineItems.size() == 1) {
LineItem actualLineItem = (LineItem)lineItems.get(0);
LineItem expectedLineItem = newLineItem(invoice,product, QUANTITY, product.getPrice()*QUANTITY );
assertEquals(expectedLineItem.getInvoice(),actualLineItem.getInvoice());
assertEquals(expectedLineItem.getProduct(),actualLineItem.getProduct());
assertEquals(expectedLineItem.getQuantity(),actualLineItem.getQuantity());
assertEquals(expectedLineItem.getPercentDiscount(),actualLineItem.getPercentDiscount());
assertEquals(expectedLineItem.getUnitPrice(),actualLineItem.getUnitPrice());
assertEquals(expectedLineItem.getExtendedPrice(),actualLineItem.getExtendedPrice());
} else {
fail("invoice should have exactly one line item");
}}
Pattern
48 Copyright 2014 Gerard Meszaroshttp://craft2014.testAutomationPatterns.com
Introduce Custom Assert
List lineItems = invoice.getLineItems();
if (lineItems.size() == 1) {
LineItem actualLineItem = (LineItem)lineItems.get(0);
LineItem expectedLineItem = newLineItem(invoice,product, QUANTITY, product.getPrice()*QUANTITY );
assertLineItemsEqual(expectedLineItem, actualLineItem);
} else {
fail("invoice should have exactly one line item");
}
Refactoring
CustomAssertion
25
49 Copyright 2014 Gerard Meszaroshttp://craft2014.testAutomationPatterns.com
ConditionalTest LogicConditionalTest Logic
Introduce Custom Assert
List lineItems = invoice.getLineItems();
if (lineItems.size() == 1) {
LineItem actualLineItem = (LineItem)lineItems.get(0);
LineItem expectedLineItem = newLineItem(invoice,product, QUANTITY, product.getPrice()*QUANTITY );
assertLineItemsEqual(expectedLineItem, actualLineItem);
} else {
fail("invoice should have exactly one line item");
}
Refactoring
50 Copyright 2014 Gerard Meszaroshttp://craft2014.testAutomationPatterns.com
Replace Conditional Logic with Guard Assertion
List lineItems = invoice.getLineItems();
assertEquals("number of items",lineItems.size(),1);
LineItem actualLineItem = (LineItem)lineItems.get(0);
LineItem expectedLineItem = newLineItem(invoice,product, QUANTITY, product.getPrice()*QUANTITY );
assertLineItemsEqual(expectedLineItem, actualLineItem);
Refactoring
26
51 Copyright 2014 Gerard Meszaroshttp://craft2014.testAutomationPatterns.com
The Whole Testpublic void testAddItemQuantity_severalQuantity () throws Exception {
// Setup Fixturefinal int QUANTITY = 5;Address billingAddress = new Address("1222 1st St SW", "Calgary",
"Alberta", "T2N 2V2", "Canada");Address shippingAddress = new Address("1333 1st St SW",
"Calgary", "Alberta", "T2N 2V2", "Canada");Customer customer = new Customer(99, "John", "Doe", new
BigDecimal("30"), billingAddress, shippingAddress);Product product = new Product(88, "SomeWidget", new
BigDecimal("19.99"));Invoice invoice = new Invoice(customer);// Exercise SUTinvoice.addItemQuantity(product, QUANTITY);// Verify Outcome
assertEquals("number of items",lineItems.size(),1);
LineItem actualLineItem = (LineItem)lineItems.get(0);
LineItem expectedLineItem = newLineItem(invoice, product,QUANTITY);
assertLineItemsEqual(expectedLineItem, actualLineItem);}
52 Copyright 2014 Gerard Meszaroshttp://craft2014.testAutomationPatterns.com
Hard-Coded Test Datapublic void testAddItemQuantity_severalQuantity () {
final int QUANTITY = 5;
Address billingAddress = new Address("1222 1st St SW","Calgary", "Alberta", "T2N 2V2", "Canada");
Address shippingAddress = new Address("1333 1st St SW","Calgary", "Alberta", "T2N 2V2", "Canada");
Customer customer = new Customer(99, "John", "Doe", newBigDecimal("30"), billingAddress, shippingAddress);
Product product = new Product(88, "SomeWidget", newBigDecimal("19.99"));
Invoice invoice = new Invoice(customer);
// Exercise SUT
invoice.addItemQuantity(product, QUANTITY);
Hard-codedTest Data
(Obscure Test)
UnrepeatableTests
Code Smell
27
53 Copyright 2014 Gerard Meszaroshttp://craft2014.testAutomationPatterns.com
Distinct Generated Valuespublic void testAddItemQuantity_severalQuantity () {
final int QUANTITY = 5 ;
Address billingAddress = new Address(getUniqueString(),getUniqueString(), getUniqueString(),getUniqueString(), getUniqueString());
Address shippingAddress = new Address(getUniqueString(),getUniqueString(), getUniqueString(),getUniqueString(), getUniqueString());
Customer customer = new Customer(getUniqueInt(), getUniqueString(),getUniqueString(), getUniqueDiscount(),billingAddress, shippingAddress);
Product product = new Product(getUniqueInt(), getUniqueString(),getUniqueNumber());
Invoice invoice = new Invoice(customer);
Pattern
54 Copyright 2014 Gerard Meszaroshttp://craft2014.testAutomationPatterns.com
Distinct Generated Valuespublic void testAddItemQuantity_severalQuantity () {
final int QUANTITY = 5 ;
Address billingAddress = new Address(getUniqueString(),getUniqueString(), getUniqueString(),getUniqueString(), getUniqueString());
Address shippingAddress = new Address(getUniqueString(),getUniqueString(), getUniqueString(),getUniqueString(), getUniqueString());
Customer customer1 = new Customer(getUniqueInt(), getUniqueString(),getUniqueString(), getUniqueDiscount(),billingAddress, shippingAddress);
Product product = new Product(getUniqueInt(), getUniqueString(),getUniqueNumber());
Invoice invoice = new Invoice(customer);
IrrelevantInformation
(Obscure Test)
Pattern
28
55 Copyright 2014 Gerard Meszaroshttp://craft2014.testAutomationPatterns.com
Creation Methodpublic void testAddItemQuantity_severalQuantity () {
final int QUANTITY = 5;Address billingAddress = createAnonymousAddress();
Address shippingAddress = createAnonymousAddress();
Customer customer = createCustomer( billingAddress,shippingAddress);
Product product = createAnonymousProduct();
Invoice invoice = new Invoice(customer);
Pattern
56 Copyright 2014 Gerard Meszaroshttp://craft2014.testAutomationPatterns.com
Obscure Test - Irrelevant Informationpublic void testAddItemQuantity_severalQuantity () {
final int QUANTITY = 5;
Address billingAddress = createAnonymousAddress();
Address shippingAddress = createAnonymousAddress();
Customer customer = createCustomer(billingAddress, shippingAddress);
Product product = createAnonymousProduct();
Invoice invoice = new Invoice(customer);
// Exercise
invoice.addItemQuantity(product, QUANTITY);
// Verify
LineItem expectedLineItem = newLineItem(invoice,product, QUANTITY, product.getPrice()*QUANTITY );
List lineItems = invoice.getLineItems();
assertEquals("number of items", lineItems.size(), 1);
LineItem actualLineItem = (LineItem)lineItems.get(0);
assertLineItemsEqual(expectedLineItem, actualLineItem);
}
IrrelevantInformation
(Obscure Test)
Code Smell
29
57 Copyright 2014 Gerard Meszaroshttp://craft2014.testAutomationPatterns.com
Remove Irrelevant Informationpublic void testAddItemQuantity_severalQuantity () {
final int QUANTITY = 5 ;
Customer customer = createAnonymousCustomer();`
Product product = createAnonymousProduct();
Invoice invoice = new Invoice(customer);
// Exercise
invoice.addItemQuantity(product, QUANTITY);
// Verify
LineItem expectedLineItem = newLineItem(invoice,product, QUANTITY, product.getPrice()*QUANTITY );
List lineItems = invoice.getLineItems();
assertEquals("number of items", lineItems.size(), 1);
LineItem actualLineItem = (LineItem)lineItems.get(0);
assertLineItemsEqual(expectedLineItem, actualLineItem);}
IrrelevantInformation
(Obscure Test)
Refactoring
58 Copyright 2014 Gerard Meszaroshttp://craft2014.testAutomationPatterns.com
Remove Irrelevant Informationpublic void testAddItemQuantity_severalQuantity () {
final int QUANTITY = 5 ;
Product product = createAnonymousProduct();
Invoice invoice = createAnonymousInvoice()
// Exercise
invoice.addItemQuantity(product, QUANTITY);
// Verify
LineItem expectedLineItem = newLineItem(invoice,product, QUANTITY, product.getPrice()*QUANTITY );
List lineItems = invoice.getLineItems();
assertEquals("number of items", lineItems.size(), 1);
LineItem actualLineItem = (LineItem)lineItems.get(0);
assertLineItemsEqual(expectedLineItem, actualLineItem);}
Refactoring
30
59 Copyright 2014 Gerard Meszaroshttp://craft2014.testAutomationPatterns.com
Mechanicshides Intent
Introduce Custom Assertionpublic void testAddItemQuantity_severalQuantity () {
final int QUANTITY = 5 ;
Product product = createAnonymousProduct();
Invoice invoice = createAnonymousInvoice()
// Exercise
invoice.addItemQuantity(product, QUANTITY);
// Verify
LineItem expectedLineItem = newLineItem(invoice,product, QUANTITY, product.getPrice()*QUANTITY );
List lineItems = invoice.getLineItems();
assertEquals("number of items", lineItems.size(), 1);
LineItem actualLineItem = (LineItem)lineItems.get(0);
assertLineItemsEqual(expectedLineItem, actualLineItem);}
Refactoring
60 Copyright 2014 Gerard Meszaroshttp://craft2014.testAutomationPatterns.com
Introduce Custom Assertionpublic void testAddItemQuantity_severalQuantity () {
final int QUANTITY = 5 ;
Product product = createAnonymousProduct();
Invoice invoice = createAnonymousInvoice()
// Exercise
invoice.addItemQuantity(product, QUANTITY);
// Verify
LineItem expectedLineItem = newLineItem(invoice,product, QUANTITY, product.getPrice()*QUANTITY );
assertExactlyOneLineItem(invoice, expectedLineItem );
}
Refactoring
31
61 Copyright 2014 Gerard Meszaroshttp://craft2014.testAutomationPatterns.com
The Whole Test – Donepublic void testAddItemQuantity_severalQuantity () {
final int QUANTITY = 5 ;Product product = createAnonymousProduct();Invoice invoice = createAnonymousInvoice();// Exerciseinvoice.addItemQuantity(product, QUANTITY);// VerifyLineItem expectedLineItem = newLineItem(invoice,
product, QUANTITY, product.getPrice()*QUANTITY );assertExactlyOneLineItem(invoice, expectedLineItem );
}
•Use Domain-Specific Language•Say Only What is Relevant
Given anempty invoice
when I calladdItemQuantity
The invoice will end upwith exactly 1 lineItem
on it.
62 Copyright 2014 Gerard Meszaroshttp://craft2014.testAutomationPatterns.com
The Whole Test – Donepublic void testAddItemQuantity_severalQuantity () {
final int QUANTITY = 5 ;Product product = createAnonymousProduct();Invoice invoice = createAnonymousInvoice();// Wheninvoice.addItemQuantity(product, QUANTITY);// ThenLineItem expectedLineItem = newLineItem(invoice,
product, QUANTITY, product.getPrice()*QUANTITY );assertExactlyOneLineItem(invoice, expectedLineItem );
}
•Use Domain-Specific Language•Say Only What is Relevant
32
63 Copyright 2014 Gerard Meszaroshttp://craft2014.testAutomationPatterns.com
The Whole Test – Done@Test public voidtestAddItemQuantity_severalQuantity () {
final int QUANTITY = 5 ;Product product = createAnonymousProduct();Invoice invoice = createAnonymousInvoice();// Wheninvoice.addItemQuantity(product, QUANTITY);// ThenLineItem expectedLineItem = newLineItem(invoice,
product, QUANTITY, product.getPrice()*QUANTITY );assertExactlyOneLineItem(invoice, expectedLineItem );
}
•Use Domain-Specific Language•Say Only What is Relevant
64 Copyright 2014 Gerard Meszaroshttp://craft2014.testAutomationPatterns.com
The Whole Test – Done@Test public voidaddItem_severalQuantity_itemValueIsQuantityTimesProductPrice() {
final int QUANTITY = 5 ;Product product = createAnonymousProduct();Invoice invoice = createAnonymousInvoice();// Wheninvoice.addItemQuantity(product, QUANTITY);// ThenLineItem expectedLineItem = newLineItem(invoice,
product, QUANTITY, product.getPrice()*QUANTITY );assertExactlyOneLineItem(invoice, expectedLineItem );
}
•Use Domain-Specific Language•Say Only What is Relevant
Rename
Constantly Strive to Improve Readability
33
65 Copyright 2014 Gerard Meszaroshttp://craft2014.testAutomationPatterns.com
The Whole Test – Done@Test public voidaddItem_severalQuantity_itemValueIsQuantityTimesProductPrice() {
final int QUANTITY = 5 ;Product product = createAnonymousProduct();Invoice invoice = createAnonymousInvoice();// Wheninvoice.addItemQuantity(product, QUANTITY);// ThenshouldBeExactlyOneLineItemOn(invoice,
expectedLineItem(invoice, product, QUANTITY,product.getPrice()*QUANTITY) );
}
•Use Domain-Specific Language•Say Only What is Relevant
Constantly Strive to Improve Readability
66 Copyright 2014 Gerard Meszaroshttp://craft2014.testAutomationPatterns.com
The Whole Test – Done@Test public voidaddItem_severalQuantity_itemValueIsQuantityTimesProductPrice() {
final int QUANTITY = 5 ;Product product = createIrrelevantProduct();Invoice invoice = createIrrelevantInvoice();// Wheninvoice.addItemQuantity(product, QUANTITY);// ThenshouldBeExactlyOneLineItemOn(invoice,
expectedLineItem(invoice, product, QUANTITY,product.getPrice()*QUANTITY) );
}
•Use Domain-Specific Language•Say Only What is Relevant
Constantly Strive to Improve Readability
34
67 Copyright 2014 Gerard Meszaroshttp://craft2014.testAutomationPatterns.com
The Whole Test – Done@Test public voidaddItem_severalQuantity_itemValueIsQuantityTimesProductPrice() {
final int QUANTITY = 5 ;Product product = givenAnyProduct();Invoice invoice = givenAnEmptyInvoice();// Wheninvoice.addItemQuantity(product, QUANTITY);// ThenshouldBeExactlyOneLineItemOn(invoice,
expectedLineItem(invoice, product, QUANTITY,product.getPrice()*QUANTITY) );
}
•Use Domain-Specific Language•Say Only What is Relevant
Constantly Strive to Improve Readability
68 Copyright 2014 Gerard Meszaroshttp://craft2014.testAutomationPatterns.com
Test CoverageTestInvoiceLineItems {
addItem_singleQuantity_itemValueIsProductPrice…{..}addItem_severalQuantity_itemValueIsQuantityTi… {..}addItem_duplicateProduct_singleItemHasSumOfQ... {..}addItem_differentProduct_oneItemPerProduct() {..}addItem_zeroQuantity_noItem… {..}addItem_severalQuantity_... {..}addItem_discountedPrice_... {..}removeItem_noItemsLeft… {..}removeItem_oneItemLeft… {..}removeItem_ severalItemsLeft… {..}
}
35
69 Copyright 2014 Gerard Meszaroshttp://craft2014.testAutomationPatterns.com
Test CoverageTestInvoiceLineItems {
addItem_singleQuantity_itemValueIsProductPrice…{..}addItem_severalQuantity_itemValueIsQuantityTi… {..}addItem_duplicateProduct_singleItemHasSumOfQ... {..}addItem_differentProduct_oneItemPerProduct() {..}addItem_zeroQuantity_noItem… {..}addItem_severalQuantity_... {..}addItem_discountedPrice_... {..}removeItem_noItemsLeft… {..}removeItem_oneItemLeft… {..}removeItem_ severalItemsLeft… {..}
}
70 Copyright 2014 Gerard Meszaroshttp://craft2014.testAutomationPatterns.com
Rapid Test Writing
final int QUANTITY2 = 2 ;
invoice.addItemQuantity(product1, QUANTITY2);
Given anempty invoice
when I calladdItemQuantitytwice with same
product
The invoice will end up withexactly 1 lineItem on it for thesum of the two calls to add..().
@Test public voidaddItem_duplicateProduct_singleItemHasSumOfQuantities () {
final int QUANTITY = 1 ;
Product product1 = givenAnyProduct();
Invoice invoice = givenAnEmptyInvoice();
// When
invoice.addItemQuantity(product1, QUANTITY);
// ThenshouldBeExactlyOneLineItemOn(invoice,
expectedLineItem(invoice, product, QUANTITY,product.getPrice() * QUANTITY) );
}
expectedLineItem(invoice, product, QUANTITY+QUANTITY2,product.getPrice() * (QUANTITY+QUANTITY2) );
GGM53
Slide 70
GGM53 Redo using new naming conventionsGerard Meszaros, 10/19/2012
36
71 Copyright 2014 Gerard Meszaroshttp://craft2014.testAutomationPatterns.com
Test CoverageTestInvoiceLineItems {
addItem_singleQuantity_itemValueIsProductPrice…{..}addItem_severalQuantity_itemValueIsQuantityTi… {..}addItem_duplicateProduct_singleItemHasSumOfQ...{..}addItem_differentProduct_oneItemPerProduct( ) {..}addItem_zeroQuantity_noItem… {..}addItem_severalQuantity_... {..}addItem_discountedPrice_... {..}removeItem_noItemsLeft… {..}removeItem_oneItemLeft… {..}removeItem_ severalItemsLeft… {..}
}
72 Copyright 2014 Gerard Meszaroshttp://craft2014.testAutomationPatterns.com
Rapid Test Writing@Test public void
addItem_differentProduct_oneItemPerProduct () {
final int QUANTITY = 1;
Product product1 = givenAnyProduct();
Invoice invoice = givenAnEmptyInvoice();
// Exercise
invoice.addItemQuantity(product1, QUANTITY);
// VerifyshouldBeExactlyOneLineItemOn(invoice,
expectedLineItem(invoice, product1, QUANTITY,product1.getPrice() * QUANTITY1)
);}
Product product2 = givenAnyProduct();
shouldBeExactlyTwoLineItems(invoice,
invoice.addItemQuantity(product2, QUANTITY2);
when I calladdItemQuantity
twice withdifferent products
expectedLineItem(invoice, product2, QUANTITY2,product2.getPrice() * QUANTITY2 ) );
}
final int QUANTITY2 = 2;
Given anempty invoice
The invoice will end up with 2 lineItems onit, one for each of the two calls to add..().
37
73 Copyright 2014 Gerard Meszaroshttp://craft2014.testAutomationPatterns.com
@Testpublic void GenerateInvoiceShould…() throws Ex… {
// setup and exercise omitted
Outside-In Development
Hmmm, this is getting ugly!Let’s try another way ….
// verify the actual invoice header matches the expected headerassertNotNull(“Number”, newInvoice.getNumber());assertEquals(“Name”, account. getName(), newInvoice.getName());assertEquals(“Address”, account. getAddr(), newInvoice.getAddr());assertEquals(“City”, account. getC
74 Copyright 2014 Gerard Meszaroshttp://craft2014.testAutomationPatterns.com
@Testpublic void GenerateInvoiceShould…() throws Ex… {
// setup and exercise omittedassertInvoiceHeaderIs( newInvoice , expectedHeader);
}
Outside-In Development
That’s Better!
shouldBeExactlyTwoLineItemsOn(invoice,expectedLineItem( invoice, product1, QUANTITY,
product1.getPrice() * QUANTITY1)expectedLineItem( invoice, product2, QUANTITY2,
product2.getPrice() * QUANTITY2)
);
Now, All I have to do is implementthese test utility methods(test-driven, of course!)
38
75 Copyright 2014 Gerard Meszaroshttp://craft2014.testAutomationPatterns.com
Closing Thoughts• Are you automating to find defects or prevent
them?• Are your automated tests good examples?
– Why not? What would you need to change?• Are your tests low maintenance?
– Why not? What causes them to break?– What could you change to make them break less often?– .... to reduce the impact of breakage?
76 Copyright 2014 Gerard Meszaroshttp://craft2014.testAutomationPatterns.com
Thank You!Gerard Meszaros
[email protected]://www.xunitpatterns.com
Slides: http://craft2014.testAutomationPatterns.com
Call me when you:• Want to transition to Agile or Lean• Want to do Agile or Lean better• Want to teach developers how to test• Need help with test automation strategy• Want to improve your test automation
Jolt Productivity Awardwinner - Technical Books
Available on MSDN: