Upload
others
View
0
Download
0
Embed Size (px)
Citation preview
Unit Test CraftsmanshipGerard Meszaros
Independent Consultant
1 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.com
Independent Consultant
CTO of FeedXL.Com
@gerardmes
#xunitpatterns
These Slides: http://vietnam2016.xunitpatterns.com
EmbeddedTelecom
My Background
•Software developer
•Development manager
•Project Manager
•Software architect
2 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.com
Product & I.T.
I.T.
Gerard [email protected]
Software architect
•OOA/OOD Mentor
•Requirements (Use Case) Mentor
•XP/TDD Mentor
•Agile PM Mentor
•Test Automation Consultant & Trainer
•Lean/Agile Coach/Consultant
Audience Survey
3 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.comhttps://lmsa.site-ym.com/news/221726/Sex-and-Gender-Medical-Education-National-Student-Survey.htm
What Does it Take To be Successful?
Programming Experience
+ XUnit Experience
+ Testing experience
-------------------------------
4 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.com
-------------------------------
Robust Automated Tests
A Sobering Thought
Expect to have just as much test code as production code!
5 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.com
The Challenge: How To Prevent Doubling Cost of Software Maintenance?
Coding Objectives Comparison
CrucialImportantMaintainability
SomewhatCrucialExecution Speed
NotImportantReusability
CrucialImportantCorrectness
TestwareProduction
6 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.com
NotImportantReusability
NotImportantFlexibility
CrucialImportant?Simplicity
CrucialImportant?Ease of writing
CrucialNot?Obviousness
Why are They so Crucial?
• Tests need to be maintained along with rest of the software.
• Expect there to be as much testware as software.
• Testware must be much easier to maintain than the software, otherwise:
7 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.com
than the software, otherwise:– It will slow you down
– It will get left behind
– Value drops to zero
– You’ll go back to manual testing
Critical Success Factor:
Writing tests in a maintainable style
TestAutomation
Economics of Maintainability
Automated unit testing/checking is a lot easier to sell on
• Cost reduction than
• Software Quality Improvement or
• Quality of Life Improvement
Cost of Test Automation + Ongoing
Maintenance
8 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.com
AutomationEffort
After Automation
DevelopmentEffort
saved effort
time
Increasedeffort(Hump) Ongoing
effortInitialeffort
TestAutomation
Economics of Maintainability
Test Automation is a lot easier to sell on
• Cost reduction than
• Software Quality Improvement or
• Quality of Life Improvement
Cost of Test Automation + Ongoing
Maintenance
9 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.com
AutomationEffort
saved effort
time
Initialeffort
Ongoingeffort
Increasedeffort(Hump)
Unsustainable Automation
DevelopmentEffort
10 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.com
Example
• Test addItemQuantity and removeLineItem methods of Invoice
Customer Addressshipping
invoicesinvoicedCustomer
shipping
11 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.com
CustomerFirstNameLastNameDiscount
InvoiceaddItemQuantity()
Addressshipping
billing
1
billing
Product
LineItemQuantityUnitPrice
ExtendedPricePercentDiscount
shipping
A Bunch of Tests / Checks:TestInvoiceLineItems {
testAddItemQuantity_singleQuantity()testAddItemQuantity_severalQuantity{..}testAddItemQuantity_duplicateProduct {..}testAddItemQuantity_differentProduct () {..}testAddItemQuantity_zeroQuantity {..}testAddItemQuantity_severalQuantity_... {..}
12 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.com
testAddItemQuantity_severalQuantity_... {..}testAddItemQuantity_discountedPrice_... {..}testRemoveItem_noItemsLeft… {..}testRemoveItem_oneItemLeft… {..}testRemoveItem_ severalItemsLeft… {..}
}
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", new BigDecimal("19.99"));Invoice invoice = new Invoice(customer);// Exercise SUTinvoice.addItemQuantity(product, QUANTITY);// Verify OutcomeList lineItems = invoice.getLineItems();if (lineItems.size() == 1) {
13 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.com
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!
How To Get To This?@Testpublic void addItemQuantity_severalQuantity () {
QUANTITY = 5 ;product = givenAnyProduct();invoice = givenAnEmptyInvoice();
invoice.addItemQuantity( product, QUANTITY);
14 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.com
invoice.addItemQuantity( product, QUANTITY);
assertExactlyOneLineItem(invoice, expectedItem(
invoice, product, QUANTITY, product.getPrice()* QUANTITY) );
}
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 SUT
Given: ???
When we call addItemQuantity
15 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.com
// 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);
} :}
addItemQuantity
Then: ???
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());
16 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.com
Obtuse Assertion
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);
}
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());
Refactoring
17 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.com
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");
}}
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());
Refactoring
18 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.com
Hard-Wired Test Data
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
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(),
Pattern
19 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.com
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");
}}
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(),
Pattern
20 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.com
Verbose TestassertEquals(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");
}}
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);
Refactoring
21 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.com
} else {
fail("invoice should have exactly one line item");
}
Custom Assertion
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
22 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.com
Conditional Test LogicConditional Test Logic
fail("invoice should have exactly one line item");
}
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
23 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.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
24 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.com
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); }
Hard-Coded Test Data
public 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", new
Code Smell
25 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.com
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 SUT
invoice.addItemQuantity(product, QUANTITY);
Hard-coded Test Data
(Obscure Test)
Unrepeatable Tests
Distinct Generated Values
public 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(
Pattern
26 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.com
Customer customer = new Customer(getUniqueInt(), getUniqueString(), getUniqueString(), getUniqueDiscount(), billingAddress, shippingAddress);
Product product = new Product(getUniqueInt(), getUniqueString(), getUniqueNumber());
Invoice invoice = new Invoice(customer);
Distinct Generated Values
public 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( Irrelevant
Pattern
27 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.com
Customer customer1 = new Customer(getUniqueInt(), getUniqueString(),getUniqueString(), getUniqueDiscount(), billingAddress, shippingAddress);
Product product = new Product(getUniqueInt(), getUniqueString(), getUniqueNumber());
Invoice invoice = new Invoice(customer);
Irrelevant Information
(Obscure Test)
Creation Method
public void testAddItemQuantity_severalQuantity () {
final int QUANTITY = 5;Address billingAddress = createAnonymousAddress();
Address shippingAddress = createAnonymousAddress();
Customer customer = createCustomer( billingAddress,
Pattern
28 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.com
Customer customer = createCustomer( billingAddress, shippingAddress);
Product product = createAnonymousProduct();
Invoice invoice = new Invoice(customer);
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); Irrelevant
Information
Code Smell
29 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.com
// Exerciseinvoice.addItemQuantity(product, QUANTITY);// VerifyLineItem 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);
}
Information (Obscure Test)
Remove Irrelevant Information
public void testAddItemQuantity_severalQuantity () {
final int QUANTITY = 5 ;
Customer customer = createAnonymousCustomer();`
Product product = createAnonymousProduct();Invoice invoice = new Invoice(customer); Irrelevant
Refactoring
30 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.com
// Exerciseinvoice.addItemQuantity(product, QUANTITY);// VerifyLineItem 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);
}
Irrelevant Information
(Obscure Test)
Remove Irrelevant Information
public void testAddItemQuantity_severalQuantity () {
final int QUANTITY = 5 ;
Product product = createAnonymousProduct();Invoice invoice = createAnonymousInvoice()
Refactoring
31 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.com
// Exerciseinvoice.addItemQuantity(product, QUANTITY);// VerifyLineItem 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);
}
Introduce Custom Assertion
public void testAddItemQuantity_severalQuantity () {
final int QUANTITY = 5 ;
Product product = createAnonymousProduct();Invoice invoice = createAnonymousInvoice()
Refactoring
32 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.com
Mechanics hides Intent
// Exerciseinvoice.addItemQuantity(product, QUANTITY);// VerifyLineItem 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);
}
Introduce Custom Assertion
public void testAddItemQuantity_severalQuantity () {
final int QUANTITY = 5 ;
Product product = createAnonymousProduct();Invoice invoice = createAnonymousInvoice()
Refactoring
33 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.com
// Exerciseinvoice.addItemQuantity(product, QUANTITY);// VerifyLineItem expectedLineItem = newLineItem(invoice,
product, QUANTITY, product.getPrice()*QUANTITY );assertExactlyOneLineItem(invoice, expectedLineItem );
}
The Whole Test – Done
public void testAddItemQuantity_severalQuantity () {// Setupfinal int QUANTITY = 5 ;Product product = createAnonymousProduct();Invoice invoice = createAnonymousInvoice();// Exerciseinvoice.addItemQuantity(product, QUANTITY);// VerifyLineItem expectedLineItem = newLineItem(invoice,
34 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.com
LineItem expectedLineItem = newLineItem(invoice, product, QUANTITY, product.getPrice()*QUANTITY );
assertExactlyOneLineItem(invoice, expectedLineItem );}
Four-Phase Test
public void testAddItemQuantity_severalQuantity () {// Setupfinal int QUANTITY = 5 ;Product product = createAnonymousProduct();Invoice invoice = createAnonymousInvoice();// Exerciseinvoice.addItemQuantity(product, QUANTITY);// VerifyLineItem expectedLineItem = newLineItem(invoice,
or // Arrange
or // Act
or // Assert
Pattern
35 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.com
LineItem expectedLineItem = newLineItem(invoice, product, QUANTITY, product.getPrice()*QUANTITY );
assertExactlyOneLineItem(invoice, expectedLineItem );}
• Bill Wake http://xp123.com/articles/3a-arrange-act-assert/
This terminology reinforces our focus on mechanics, not intent!
// Teardown// Shouldn’t be needed
Four-Phase Test
public void testAddItemQuantity_severalQuantity () {// Setupfinal int QUANTITY = 5 ;Product product = createAnonymousProduct();Invoice invoice = createAnonymousInvoice();// Exerciseinvoice.addItemQuantity(product, QUANTITY);// VerifyLineItem expectedLineItem = newLineItem(invoice,
Given an empty invoice
when I call addItemQuantity
or // Arrange
or // Act
or // Assert
36 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.com
LineItem expectedLineItem = newLineItem(invoice, product, QUANTITY, product.getPrice()*QUANTITY );
assertExactlyOneLineItem(invoice, expectedLineItem );}
•Use Domain-Specific Language•Say Only What is Relevant
Then the invoice will end up with exactly 1
lineItem on it.
Improving Terminology
public void testAddItemQuantity_severalQuantity () {// Givenfinal int QUANTITY = 5 ;Product product = createAnonymousProduct();Invoice invoice = createAnonymousInvoice();// Wheninvoice.addItemQuantity(product, QUANTITY);// ThenLineItem expectedLineItem = newLineItem(invoice,product,
37 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.com
LineItem expectedLineItem = newLineItem(invoice,product, QUANTITY, product.getPrice()*QUANTITY );
assertExactlyOneLineItem(invoice, expectedLineItem );}
•Use Domain-Specific Language•Say Only What is Relevant
Improving Terminology@Test public void testAddItemQuantity_severalQuantity () {
final int QUANTITY = 5 ;Product product = createAnonymousProduct();Invoice invoice = createAnonymousInvoice();// Wheninvoice.addItemQuantity(product, QUANTITY);// ThenLineItem expectedLineItem = newLineItem(invoice, product,
Upgrade to Junit 4+
38 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.com
LineItem expectedLineItem = newLineItem(invoice, product, QUANTITY, product.getPrice()*QUANTITY );
assertExactlyOneLineItem(invoice, expectedLineItem );}
•Use Domain-Specific Language•Say Only What is Relevant
Improving Terminology@Test public void addItem_severalQuantity_itemValueIsQuantityTimesProductPrice(){
final int QUANTITY = 5 ;Product product = createAnonymousProduct();Invoice invoice = createAnonymousInvoice();// Wheninvoice.addItemQuantity(product, QUANTITY);// ThenLineItem expectedLineItem = newLineItem(invoice, product,
Rename
39 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.com
LineItem expectedLineItem = newLineItem(invoice, product, QUANTITY, product.getPrice()*QUANTITY );
assertExactlyOneLineItem(invoice, expectedLineItem );}
•Use Domain-Specific Language•Say Only What is Relevant
Constantly Strive to Improve Readability
Improving Terminology@Test public void addItem_severalQuantity_itemValueIsQuantityTimesProductPrice(){
final int QUANTITY = 5 ;Product product = createAnonymousProduct();Invoice invoice = createAnonymousInvoice();// Wheninvoice.addItemQuantity(product, QUANTITY);// ThenshouldBeExactlyOneLineItemOn(invoice,
Rename, Inline Local, Rename
40 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.com
shouldBeExactlyOneLineItemOn(invoice, expectedLineItem(invoice, product, QUANTITY,
product.getPrice()*QUANTITY) );}
•Use Domain-Specific Language•Say Only What is Relevant
Constantly Strive to Improve Readability
Improving Terminology@Test public void addItem_severalQuantity_itemValueIsQuantityTimesProductPrice(){
final int QUANTITY = 5 ;Product product = createIrrelevantProduct();Invoice invoice = createIrrelevantInvoice();// Wheninvoice.addItemQuantity(product, QUANTITY);// ThenshouldBeExactlyOneLineItemOn(invoice,
Another Rename
41 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.com
shouldBeExactlyOneLineItemOn(invoice, expectedLineItem(invoice, product, QUANTITY,
product.getPrice()*QUANTITY) );}
•Use Domain-Specific Language•Say Only What is Relevant
Constantly Strive to Improve Readability
Improving Terminology@Test public void addItem_severalQuantity_itemValueIsQuantityTimesProductPrice(){
final int QUANTITY = 5 ;Product product = givenAnyProduct();Invoice invoice = givenAnEmptyInvoice();// Wheninvoice.addItemQuantity(product, QUANTITY);// ThenshouldBeExactlyOneLineItemOn(invoice,
Yet Another Rename
42 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.com
shouldBeExactlyOneLineItemOn(invoice, expectedLineItem(invoice, product, QUANTITY,
product.getPrice()*QUANTITY) );}
•Use Domain-Specific Language•Say Only What is Relevant
Naming as a Process – Arlo Belshee
Test CoverageTestInvoiceLineItems {
testAddItemQuantity_singleQuantity()testAddItemQuantity_severalQuantity{..}testAddItemQuantity_duplicateProduct {..}testAddItemQuantity_differentProduct () {..}testAddItemQuantity_zeroQuantity {..}testAddItemQuantity_severalQuantity_... {..}
43 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.com
testAddItemQuantity_severalQuantity_... {..}testAddItemQuantity_discountedPrice_... {..}testRemoveItem_noItemsLeft… {..}testRemoveItem_oneItemLeft… {..}testRemoveItem_ severalItemsLeft… {..}
}
Test CoverageTestInvoiceLineItems {
addItem_singleQuantity_itemValueIsProductPriceaddItem_severalQuantity_itemValueIsQuantityTimesPr… addItem_duplicateProduct_singleItemHasSumOfQuantityaddItem_differentProduct_oneItemPerProductaddItem_zeroQuantity_noItemAddedaddItem_customerWithDiscount_itemValueIsDiscounted
A whole bunch of Renames
44 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.com
addItem_customerWithDiscount_itemValueIsDiscountedremoveItem_onlyItem_noItemsLeft…removeItem_severalItems_oneLessItemLeftremoveItem_severalItems_severalItemsLeft
}
Test CoverageTestInvoiceLineItems {
addItem_singleQuantity_itemValueIsProductPriceaddItem_severalQuantity_itemValueIsQuantityTimesPr… addItem_duplicateProduct_singleItemHasSumOfQuantityaddItem_differentProduct_oneItemPerProductaddItem_zeroQuantity_noItemAddedaddItem_customerWithDiscount_itemValueIsDiscounted
45 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.com
addItem_customerWithDiscount_itemValueIsDiscountedremoveItem_onlyItem_noItemsLeft…removeItem_severalItems_oneLessItemLeftremoveItem_severalItems_severalItemsLeft
}
Rapid Test Writing
final int QUANTITY2 = 2 ;
Given an empty invoice
when I call addItemQuantitytwice with same
product
@Test public void addItem_duplicateProduct_singleItemHasSumOfQuantities () {
final int QUANTITY = 1 ;
Product product = givenAnyProduct();
Invoice invoice = givenAnEmptyInvoice();
// When
invoice.addItemQuantity(product, QUANTITY);
46 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.com
The invoice will end up with exactly 1 lineItem on it for the sum of the two calls to add..().
invoice.addItemQuantity(product, QUANTITY);
// ThenshouldBeExactlyOneLineItemOn(invoice,
expectedLineItem(invoice, product, QUANTITY, product.getPrice() * QUANTITY) );
}
expectedLineItem(invoice, product, QUANTITY+QUANTITY2, product.getPrice() * (QUANTITY+QUANTITY2) );
invoice.addItemQuantity(product, QUANTITY2);
GGM53
Slide 46
GGM53 Redo using new naming conventionsGerard Meszaros, 12/10/19
Test CoverageTestInvoiceLineItems {
addItem_singleQuantity_itemValueIsProductPrice…{..}addItem_severalQuantity_itemValueIsQuantityTi… {..}addItem_duplicateProduct_singleItemHasSumOfQ...{..}addItem_differentProduct_oneItemPerProduct( ) {..}addItem_zeroQuantity_noItem… {..}addItem_severalQuantity_... {..}
47 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.com
addItem_severalQuantity_... {..}addItem_discountedPrice_... {..}removeItem_noItemsLeft… {..}removeItem_oneItemLeft… {..}removeItem_ severalItemsLeft… {..}
}
Rapid Test Writing@Test public void
addItem_differentProduct_oneItemPerProduct () {
final int QUANTITY = 1;
Product product1 = givenAnyProduct();
Invoice invoice = givenAnEmptyInvoice();
// When
invoice.addItemQuantity(product1, QUANTITY);
48 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.com
invoice.addItemQuantity(product1, QUANTITY);
// ThenshouldBeExactlyOneLineItemOn(invoice,
expectedLineItem(invoice, product1, QUANTITY, product1.getPrice() * QUANTITY1)
);}
shouldBeExactlyTwoLineItems(invoice,
expectedLineItem(invoice, product2, QUANTITY2, product2.getPrice() * QUANTITY2 ) );
}
Rapid Test Writing@Test public void
addItem_differentProduct_oneItemPerProduct () {
final int QUANTITY = 1;
Product product1 = givenAnyProduct();
Invoice invoice = givenAnEmptyInvoice();
// When
invoice.addItemQuantity(product1, QUANTITY);
Product product2 = givenAnyProduct();when I call
addItemQuantitytwice with
different products
final int QUANTITY2 = 2;
Given an empty invoice
49 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.com
invoice.addItemQuantity(product1, QUANTITY);
// ThenshouldBeExactlyOneLineItemOn(invoice,
expectedLineItem(invoice, product1, QUANTITY, product1.getPrice() * QUANTITY1)
);}
shouldBeExactlyTwoLineItems(invoice,
invoice.addItemQuantity(product2, QUANTITY2);
expectedLineItem(invoice, product2, QUANTITY2, product2.getPrice() * QUANTITY2 ) );
}
The invoice will end up with 2 lineItems on it, one for each of the two calls to add..().
Removing Deodorant@Test public void
addItem_differentProduct_oneItemPerProduct () {
final int QUANTITY = 1;
Product product1 = givenAnyProduct();
Invoice invoice = givenAnEmptyInvoice();
invoice.addItemQuantity(product1, QUANTITY);
Product product2 = givenAnyProduct();when I call
addItemQuantitytwice with
different products
final int QUANTITY2 = 2;
Given an empty invoice
50 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.com
invoice.addItemQuantity(product1, QUANTITY);
shouldBeExactlyOneLineItemOn(invoice,expectedLineItem(invoice, product1, QUANTITY,
product1.getPrice() * QUANTITY) );
}
shouldBeExactlyTwoLineItems(invoice,
invoice.addItemQuantity(product2, QUANTITY2);
expectedLineItem(invoice, product2, QUANTITY2, product2.getPrice() * QUANTITY2 ) );
}
The invoice will end up with 2 lineItems on it, one for each of the two calls to add..().
Benefits• Writing tests is faster
– Less code to write
• Reading tests is faster, too.– Less code to read
• Much easier to see what’s different from one test to another.
51 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.com
test to another.– Differences are fairly obvious
• Tests are much less fragile– Most code breakages are in test utility methods, not the
tests themselves.
Nice, But Couldn’t We Avoid the Refactoring?
@Testpublic void generateInvoice_should…() throws Ex… {
// setup and exercise omitted
Reducing the Need to Refactor Tests
// 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
52 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.com
Hmmm, this is getting ugly!Let’s try another way ….
assertEquals(“City”, account. getC
@Testpublic void generateInvoice_should…() throws Ex… {
// setup and exercise omittedassertInvoiceHeaderIs( newInvoice , expectedHeader(account) );
Reducing the Need to Refactor Tests
shouldBeExactlyTwoLineItemsOn(invoice,expectedLineItem( invoice, product1, QUANTITY,
product1.getPrice() * QUANTITY1)
53 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.com
} That’s Better!
product1.getPrice() * QUANTITY1)expectedLineItem( invoice, product2, QUANTITY2,
product2.getPrice() * QUANTITY2) );
Now, All I have to do is implement these test utility methods (test-driven, of course!)
What Does it Take To be Successful?Programming Experience
+ XUnit Experience
+ Testing experience
+ Good naming
+Regular refactoring
54 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.com
+Regular refactoring
+ a bunch of other things …
+ Fanatical Attention to Test Maintainability
-------------------------------
Robust Automated Tests ->
Now, you may be thinking,
“This is fine for unit tests,
55 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.com
“This is fine for unit tests, but how does it help us with
system tests?”
Example: Notifying of Bank Transactions
Configure Notification
Rules
Suspend Notification
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
Not
ific
atio
n
56 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.com
AccountHolder
Notification
Resume Notification
Process Transaction
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
Checking Notifications• Open MegaBank app
• Log in as “BobMa” with password ******
• Click on “Manage Notifications” tab
• Click on “Add New Rule” button
• Select account “10035692877”
• Type “Default Rule” into field “rule name”
57 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.com
• Type “Default Rule” into field “rule name”
• Type “1000” into field “threshold amount”
• Click on “all transaction types” radio button
• Click on “all locations” radio button
• Click on “save changes” button
• …
• …
Checking Notifications – 1/2Given:
User and Accounts
Another attempt:
When: Notification
Rule is Configured
58 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.com
Then:Notification Rule Configured and
Active
Then:Notification Rule
is Active
Checking Notifications – 2/2
Then: Expected
When: The Transactions to be processed
Another attempt:
59 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.com
Then: ExpectedNotifications
Test Automation Pyramid
Exploratory Tests
Component
SystemTests (GUI)
60 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.com
Pyramid originally proposed by Mike Cohn
Unit Tests
ComponentTests (API)
Behavior Specification at Right Level
Component
SystemTests (GUI)
Det
ail
High
Low
61 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.com
Unit Tests
ComponentTests (API)
Broad Narrow
Det
ail
High
Low
Scope
Behavior Specification at Right LevelDet
ail
High
Low
System
62 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.com
Broad Narrow
Det
ail
High
Low
Scope
ComponentTests (API)
SystemTests (GUI)
Behavior Specification at Right Level
System
Multi-Use Case
Workflows
IncompleteSpec
Det
ail
High
Low
Transactions
63 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.com
ComponentTests (API)
SystemTests (GUI)
Rules &Algorithms
Toomuch detailUnmaintainable
Det
ail
High
Low
Broad NarrowScope
Transactions(Use Cases)
Changing Level of Abstraction/Detail
Multi-Use Case
WorkflowsTransactions
IncompleteSpec
Det
ail
High
L
ow
64 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.com
Toomuch detailUnmaintainable
Rules &Algorithms
Transactions(Use Cases)
Broad Narrow
Det
ail
High
L
ow
Scope
Refactoring Workflow ExampleGiven:
User and Accounts
When: Notification
Rule is Configured
65 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.com
Then:Notification Rule Configured and
Active
Then:Notification Rule
is Active
Refactoring Workflow Example
66 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.com
“If it isn’t essential to conveying the essence of the behavior, it is essential to
not include it.”
Then: Expected
When: The Transactions to be processed
Refactoring Workflow Example
67 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.com
Then: ExpectedNotifications
Refactoring Workflow Example
68 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.com
Refactoring Workflow Example
69 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.com
Refactoring Workflow ExampleGiven: User &
Thresholds
When: Transactions
Are Processed
70 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.com
Then: We Expect
Notifications
Given: User &
Thresholds
When: Transactions
Are Processed
Refactoring Workflow Example
71 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.com
9991113333
Then: We Expect
Notifications
credit
credit
Refactoring Workflow ExampleGiven: User &
Thresholds
When: Transactions
Are Processed
72 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.com
9991113333
Broad Scope (Multi-Actor);
Minimum Detail (per Actor/Transaction);
Then: We Expect
Notifications
credit
credit
Filling in the Details
Multi-Use Case
WorkflowsTransactions
IncompleteSpec
Det
ail
High
Low
73 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.com
Rules &Algorithms
Transactions(Use Cases)
Too much detailUnmaintainableBroad Narrow
Det
ail
High
Low
Scope
Business Rule Example
Process Transaction
Threshold per Charge Type
Configuration
74 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.com
Process Transaction
Threshold per Charge Type
Example:
Configuration Given these rules
Business Rule ExampleWhen we ask
NotificationRequired? with this transaction:
75 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.com
Narrow Scope (Single Rule)
High Detail (Everything that matters)Then: The
answer should be
Closing Thoughts• Are your automated checks helping you
deliver value continuously?– Do they help you understand what you need to deliver?
• Are your checks helping Make Safety a Prerequiste?– Are they making it safer to change your code?
76 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.com
– Are they making it safer to change your code?
• Are they helping you Experiment and Learn Continuously?– Fast feedback on impacts of code changes?
• Are you Making People Awesome by
automating the checks?– Happy developers and users?
Thank You!Gerard Meszaros
[email protected] http://www.xunitpatterns.com
Slides: http://vietnam2016.xunitpatterns.com
Jolt Productivity Award winner - Technical Books
77 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.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
winner - Technical Books
Available on MSDN: