unit test craftsmanship-finalxunitpatterns.com/~gerard/agiletourvietnam2016.pdf · (pehgghg 7hohfrp...

Post on 13-Oct-2020

3 Views

Category:

Documents

0 Downloads

Preview:

Click to see full reader

TRANSCRIPT

Unit Test CraftsmanshipGerard Meszaros

Independent Consultant

1 Copyright 2016 Gerard Meszaroshttp://vietnam2016.xunitpatterns.com

Independent Consultant

CTO of FeedXL.Com

vietnam2016@gerardm.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 Meszarosxunit@gerardm.com

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

Vietnam2016@gerardm.com 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:

top related