tests and testability: apex structure and strategy

86
Tests and Testability Apex Structure and Strategy Stephen Willcock, FinancialForce.com, Director of Product Innovation @stephenwillcock

Upload: salesforce-developers

Post on 11-May-2015

700 views

Category:

Technology


4 download

DESCRIPTION

Join us as we look at unit tests in Apex - what they are and where they fit within an efficient and effective testing strategy. We'll also consider the demands that implementing such a strategy makes on how Apex code is structured in a Force.com application. You'll leave with an appreciation of the test pyramid, and some specific examples of mocking techniques.

TRANSCRIPT

Page 1: Tests and Testability: Apex Structure and Strategy

Tests and TestabilityApex Structure and Strategy

Stephen Willcock, FinancialForce.com, Director of Product Innovation@stephenwillcock

Page 2: Tests and Testability: Apex Structure and Strategy

All about FinancialForce.comRevolutionizing the Back Office#1 Accounting, Billing and PSA Apps on the Salesforce platform

▪ Native apps

▪ San Francisco HQ, 595 Market St

▪ R&D in San Francisco, Harrogate UK, and Granada ES

▪ We are hiring! Meet us at Rehab!

Page 3: Tests and Testability: Apex Structure and Strategy

Tests and Testability - overviewTesting strategy• Test pyramidUnit test principles• What is a unit?• Isolation• Saturation• ExpectationUnit test techniques• Testability• Fabrication• Substitution

• Loose type coupling• Dependency Injection• Decoupler• Adapter

Page 4: Tests and Testability: Apex Structure and Strategy

Tests and TestabilityTesting strategy• Test pyramidUnit test principles• What is a unit?• Isolation• Saturation• ExpectationUnit test techniques• Testability• Fabrication• Substitution

• Loose type coupling• Dependency Injection• Decoupler• Adapter

Page 5: Tests and Testability: Apex Structure and Strategy

Testing strategyIn an ideal world we would test…An entire system “end-to-end”Using different types of user With production data volumesWith complex / varied data profilesAll possible code paths

SIMULTANEOUSLY!

Page 6: Tests and Testability: Apex Structure and Strategy

Test pyramid

More numerous

tests

Fewer tests More complex

tests

Less complex

tests

Page 7: Tests and Testability: Apex Structure and Strategy

Test pyramid

Page 8: Tests and Testability: Apex Structure and Strategy

Test pyramidThe test pyramid is a concept developed by Mike Cohn.... [the] essential point is that you should have many more low-level unit tests than high level end-to-end tests running through a GUI.

http://martinfowler.com/bliki/TestPyramid.html

Page 9: Tests and Testability: Apex Structure and Strategy

Test pyramidEven with good practices on writing them, end-to-end tests are more prone to non-determinism problems, which can undermine trust in them. In short, tests that run end-to-end through the UI are: brittle, expensive to write, and time consuming to run. So the pyramid argues that you should do much more automated testing through unit tests than you should through traditional GUI based testing.

http://martinfowler.com/bliki/TestPyramid.html

Page 10: Tests and Testability: Apex Structure and Strategy

Tests and TestabilityTesting strategy• Test pyramidUnit test principles• What is a unit?• Isolation• Saturation• ExpectationUnit test techniques• Testability• Fabrication• Substitution

• Loose type coupling• Dependency Injection• Decoupler• Adapter

Page 11: Tests and Testability: Apex Structure and Strategy

Tests and TestabilityTesting strategy• Test pyramidUnit test principles• What is a unit?• Isolation• Saturation• ExpectationUnit test techniques• Testability• Fabrication• Substitution

• Loose type coupling• Dependency Injection• Decoupler• Adapter

Page 12: Tests and Testability: Apex Structure and Strategy

Unit test principles - what is a unit?The smallest testable chunk of codeIndependent from other units and systems

Uno

Page 13: Tests and Testability: Apex Structure and Strategy

Unit test principles - what is a unit?

Page 14: Tests and Testability: Apex Structure and Strategy

Unit test principles - what is a unit?

Page 15: Tests and Testability: Apex Structure and Strategy

Tests and TestabilityTesting strategy• Test pyramidUnit test principles• What is a unit?• Isolation• Saturation• ExpectationUnit test techniques• Testability• Fabrication• Substitution

• Loose type coupling• Dependency Injection• Decoupler• Adapter

Page 16: Tests and Testability: Apex Structure and Strategy

Unit test principles - isolation

Uno Uno

Database

https://

@Uno

UnoTriggerUno

Related Data

Uno

…further dependencies

Managed Apex

Workflow Rule

Validation Rule

Page 17: Tests and Testability: Apex Structure and Strategy

Unit test principles - isolation

Uno Uno

Database

Mocked resources

Page 18: Tests and Testability: Apex Structure and Strategy

Tests and TestabilityTesting strategy• Test pyramidUnit test principles• What is a unit?• Isolation• Saturation• ExpectationUnit test techniques• Testability• Fabrication• Substitution

• Loose type coupling• Dependency Injection• Decoupler• Adapter

Page 19: Tests and Testability: Apex Structure and Strategy

Unit test principles - saturationThe Force.com platform requires that at least 75% of the Apex Code in an org be executed via unit tests in order to deploy the code to production. You shouldn’t consider 75% code coverage to be an end-goal thoughInstead, you should strive to increase the state coverage of your unit testsCode has many more possible states than it has lines of code

http://wiki.developerforce.com/page/How_to_Write_Good_Unit_Tests

Page 20: Tests and Testability: Apex Structure and Strategy

Unit test principles - saturation

Page 21: Tests and Testability: Apex Structure and Strategy

Tests and TestabilityTesting strategy• Test pyramidUnit test principles• What is a unit?• Isolation• Saturation• ExpectationUnit test techniques• Testability• Fabrication• Substitution

• Loose type coupling• Dependency Injection• Decoupler• Adapter

Page 22: Tests and Testability: Apex Structure and Strategy

Unit test principles - expectationUnit test•Stakeholder: developers•Asks: does this code do what it says it will?System test•Stakeholder: Business Analyst•Asks: does this system fulfil my functional requirements?

Page 23: Tests and Testability: Apex Structure and Strategy

Tests and TestabilityTesting strategy• Test pyramidUnit test principles• What is a unit?• Isolation• Saturation• ExpectationUnit test techniques• Testability• Fabrication• Substitution

• Loose type coupling• Dependency Injection• Decoupler• Adapter

Page 24: Tests and Testability: Apex Structure and Strategy

Tests and TestabilityTesting strategy• Test pyramidUnit test principles• What is a unit?• Isolation• Saturation• ExpectationUnit test techniques• Testability• Fabrication• Substitution

• Loose type coupling• Dependency Injection• Decoupler• Adapter

Page 25: Tests and Testability: Apex Structure and Strategy

Unit test principles - testability

Page 26: Tests and Testability: Apex Structure and Strategy

Unit test principles - testability Well structured, Object Oriented code is likely to be testable:•Encapsulation - well defined inputs and outputs•Limited class scope •Limited class size•Limited method sizeTDD

Page 27: Tests and Testability: Apex Structure and Strategy

Tests and TestabilityTesting strategy• Test pyramidUnit test principles• What is a unit?• Isolation• Saturation• ExpectationUnit test techniques• Testability• Fabrication• Substitution

• Loose type coupling• Dependency Injection• Decoupler• Adapter

Page 28: Tests and Testability: Apex Structure and Strategy

Evaluate and assert Read data Database commit

Triggered record update

Unit test

Trigger firesInsert test data

SObject fabrication

Insert supporting data

Page 29: Tests and Testability: Apex Structure and Strategy

SObject fabrication - the unittrigger OpportunityLineItems on OpportunityLineItem

(before insert) {

for(OpportunityLineItem item : Trigger.new) {

if(item.Description==null)

item.Description = 'foo';

}

}

Page 30: Tests and Testability: Apex Structure and Strategy

SObject fabrication - the testOpportunityLineItem oli = new OpportunityLineItem (

Description=null );

insert oli;

oli = [select Description from OpportunityLineItem where

id=:oli.Id];

system.assertEquals('foo',oli.Description);

OpportunityId

UnitPrice

Quantity

PricebookEntryId

insert new

PricebookEntry

insert new

Opportunity

insert new

Product2

Select Id from

Pricebook2

AccountId

StageName

CloseDate

insert new

Account

Page 31: Tests and Testability: Apex Structure and Strategy

SObject fabrication - the testa = new Account(…); insert a;

o = new Opportunity(…); insert o;

pb = [select Id from Pricebook2 … ];

p = new Product2(…); insert p;

pbe = new PricebookEntry(…); insert pbe;

oli = new OpportunityLineItem(…); insert oli;

oli = [select … from OpportunityLineItem …];

system.assertEquals('foo',oli.Description);

Page 32: Tests and Testability: Apex Structure and Strategy

SObject fabrication - the revised unittrigger OpportunityLineItems on OpportunityLineItem

(before insert) {

new OpportunityLineItemsTriggerHandler().beforeInsert(

Trigger.new );

}

Testable code: break up the Trigger

Page 33: Tests and Testability: Apex Structure and Strategy

SObject fabrication - the revised unitpublic class OpportunityLineItemsTriggerHandler {

public void beforeInsert(List<OpportunityLineItem>

items) {

for(OpportunityLineItem item : items) {

if(item.Description==null)

item.Description = 'foo';

}

}

} Testable code: break up the Trigger

Avoid referring to Trigger variables in the

handler

Page 34: Tests and Testability: Apex Structure and Strategy

SObject fabrication - the revised testOpportunityLineItem oli = new OpportunityLineItem(

Description=null );

new OpportunityLineItemsTriggerHandler().beforeInsert(

new List<OpportunityLineItem>{oli});

system.assertEquals('foo',oli.Description);

Page 35: Tests and Testability: Apex Structure and Strategy

SObject fabrication #2 - the unitpublic class OpportunityService {

public void adjust(OpportunityLineItem oli) {

oli.UnitPrice += (oli.UnitPrice *

oli.Opportunity.Account.Factor__c);

}

}

Page 36: Tests and Testability: Apex Structure and Strategy

SObject fabrication #2 - the testAccount a = new Account(Factor__c=0.1);

Opportunity o = new Opportunity(Account=a);

OpportunityLineItem oli = new OpportunityLineItem(

Opportunity=o, UnitPrice=100);

OpportunityService svc = new OpportunityService();

svc.adjust(oli);

system.assertEquals(110,oli.UnitPrice);

Page 37: Tests and Testability: Apex Structure and Strategy

SObject fabrication - what did we do?Structured the code to make it easier to test•Trigger handler / TriggerFabricated SObjects (including relationships)•In-memory•No database interaction

Page 38: Tests and Testability: Apex Structure and Strategy

Tests and TestabilityTesting strategy• Test pyramidUnit test principles• What is a unit?• Isolation• Saturation• ExpectationUnit test techniques• Testability• Fabrication• Substitution

• Loose type coupling• Dependency Injection• Decoupler• Adapter

Page 39: Tests and Testability: Apex Structure and Strategy

Tests and TestabilityTesting strategy• Test pyramidUnit test principles• What is a unit?• Isolation• Saturation• ExpectationUnit test techniques• Testability• Fabrication• Substitution

• Loose type coupling• Dependency Injection• Decoupler• Adapter

Page 40: Tests and Testability: Apex Structure and Strategy

Loose type couplingUse inheritance to “loosen” a relationship•Interface•SuperclassSubstitute a mock “sibling implementation” during unit tests

Page 41: Tests and Testability: Apex Structure and Strategy

Loose type coupling

OrderController.Item

Aggregator

OrderController(Custom

Controller)

ProviderConsumer

Unit test

Page 42: Tests and Testability: Apex Structure and Strategy

Loose type coupling - the provider (test subject)public class Aggregator {

List<OrderController.Item> items;

public void setItems(List<OrderController.Item> items) {

this.items = items; }

public Decimal getSum() {

Decimal result = 0;

for(OrderController.Item item : this.items)

result += item.getValue();

return result;

}

}

Page 43: Tests and Testability: Apex Structure and Strategy

Loose type coupling - the provider testList<OrderController.Item> testItems = new

List<OrderController.Item>{ new OrderController.Item(…), … };

Aggregator testAggregator = new Aggregator();

testAggregator.setItems(testItems);

system.assertEquals(123.456,testAggregator.getSum());

Page 44: Tests and Testability: Apex Structure and Strategy

Loose type coupling - the revised providerpublic class Aggregator {

public interface IItem {

Decimal getValue();

}

public void setItems(List<IItem> items) {…}

Page 45: Tests and Testability: Apex Structure and Strategy

Loose type coupling - the revised provider public Decimal getSum() {

Decimal result = 0;

for(IItem item : items)

result += item.getValue();

return result;

}

}

Page 46: Tests and Testability: Apex Structure and Strategy

Loose type coupling - the revised consumerpublic controller OrderController {

public class Item implements Aggregator.IItem {…}

Aggregator a…

List<Item> items…

a.setItems(items);

Decimal s = a.getSum();

Page 47: Tests and Testability: Apex Structure and Strategy

Loose type coupling - the revised provider testclass TItem implements Aggregator.IItem {

Decimal value;

TItem(Decimal d) {

value = d;

}

public getValue() {

return value;

}

}

Page 48: Tests and Testability: Apex Structure and Strategy

Loose type coupling - the revised provider testList<TItem> testItems = new List<TItem>{ new TItem(100),

new TItem(20.006), new TItem(3.45) };

Aggregator testAggregator = new Aggregator();

testAggregator.setItems(testItems);

system.assertEquals(123.456,testAggregator.getSum());

Page 49: Tests and Testability: Apex Structure and Strategy

Loose type coupling - what did we do?

Production Unit Test

Aggregator.IItemOrderController.Item

Aggregator

TItem

Page 50: Tests and Testability: Apex Structure and Strategy

Tests and TestabilityTesting strategy• Test pyramidUnit test principles• What is a unit?• Isolation• Saturation• ExpectationUnit test techniques• Testability• Fabrication• Substitution

• Loose type coupling• Dependency Injection• Decoupler• Adapter

Page 51: Tests and Testability: Apex Structure and Strategy

Dependency InjectionDependency Injection is all about injecting dependencies, or simply said, setting relations between instancesSome people refer to it as being the Hollywood principle "Don't call me, we'll call you”I prefer calling it the "bugger" principle: "I don't care who you are, just do what I ask”http://www.javaranch.com/journal/200709/dependency-injection-unit-testing.html

Page 52: Tests and Testability: Apex Structure and Strategy

Dependency injection

OpportunityAdjuster

OpportunityController

Opportunity o;

OpportunityAdjuster a;

a = new OpportunityAdjuster();

a.adjust(o);

ProviderConsumer

Unit test

Page 53: Tests and Testability: Apex Structure and Strategy

Dependency injection

OpportunityController

TOpportunityAdjuster

OpportunityController

OpportunityAdjuster

Inject dependency

Production usage

Unit test usage

Page 54: Tests and Testability: Apex Structure and Strategy

Dependency injection - interfacepublic interface IAdjustOpportunities {

void adjust(Opportunity o);

}

Page 55: Tests and Testability: Apex Structure and Strategy

Dependency injection - providerpublic with sharing class OpportunityAdjuster implements

IAdjustOpportunities {

public void adjust(Opportunity o) {

// the actual implementation

// do some stuff to the opp

}

}

Page 56: Tests and Testability: Apex Structure and Strategy

Dependency injection - mock providerpublic with sharing class TOpportunityAdjuster

implements IAdjustOpportunities {

@testVisible Opportunity opp;

@testVisible Boolean calledAdjust;

public void adjust(Opportunity o) {

opp = o;

calledAdjust = true;

}

}

@testVisible

Page 57: Tests and Testability: Apex Structure and Strategy

Dependency injection - consumerpublic class OpportunityController {

IAdjustOpportunities adjuster;

@testVisible OpportunityController(

IAdjustOpportunities a,

ApexPages.StandardController c ) {

this.adjuster = a;

}

Page 58: Tests and Testability: Apex Structure and Strategy

Dependency injection - consumer public OpportunityController(

ApexPages.StandardController c) {

this(new OpportunityAdjuster(), c);

}

public void makeAdjustment() {

adjuster.adjust(opp);

}

Page 59: Tests and Testability: Apex Structure and Strategy

Dependency injection - consumer testOpportunity opp = new Opportunity(…);

ApexPages.StandardController sc = new

ApexPages.StandardController(opp);

TOpportunityAdjuster adjuster = new TOpportunityAdjuster();

OpportunityController oc = new

OpportunityController(adjuster, sc);

oc.makeAdjustment();

system.assert(adjuster.calledAdjust);

system.assertEquals(opp,adjuster.opp);

Constructor injection

Page 60: Tests and Testability: Apex Structure and Strategy

Dependency injection - what did we do?• Loosen the coupling to a provider class in a consumer class • Mock the provider class• Inject the mock provider implementation into the consumer via

a new @testVisible constructor on the consumer class to test the consumer class

Page 61: Tests and Testability: Apex Structure and Strategy

Tests and TestabilityTesting strategy• Test pyramidUnit test principles• What is a unit?• Isolation• Saturation• ExpectationUnit test techniques• Testability• Fabrication• Substitution

• Loose type coupling• Dependency Injection• Decoupler• Adapter

Page 62: Tests and Testability: Apex Structure and Strategy

SObject Decoupler - the unitpublic class OpportunitiesTriggerHandler {

public static void afterUpdate(List<Opportunity> items) {

for(Opportunity item : items) {

if(item.IsClosed){

// do something

}

}

} …

}

Page 63: Tests and Testability: Apex Structure and Strategy

SObject Decoupler - the test@isTest private class OpportunitiesTriggerHandlerTest {

@isTest static void myTest() {

Opportunity o = new Opportunity(IsClosed=true);

OpportunitiesTriggerHandler handler = new

OpportunitiesTriggerHandler();

handler.afterUpdate(new List<Opportunity>{o});

// test something

}

Field is not writeable:

Opportunity.IsClosed

SObject fabrication limitations: formula fields, rollup summaries,

system fields, subselects

Page 64: Tests and Testability: Apex Structure and Strategy

SObject Decoupler - the decouplerpublic virtual class OpportunityDecoupler {

public virtual Boolean getIsClosed( Opportunity o ) {

return o.IsClosed;

}

}

Page 65: Tests and Testability: Apex Structure and Strategy

SObject Decoupler - the test decouplerpublic virtual class TOpportunityDecoupler extends

OpportunityDecoupler {

@testVisible Map<Id,Boolean> IsClosedMap =

new Map<Id,Boolean>();

public override Boolean getIsClosed( Opportunity o ) {

return IsClosedMap.get(o.Id);

}

}

Page 66: Tests and Testability: Apex Structure and Strategy

SObject Decoupler - the revised unitpublic class OpportunitiesTriggerHandler {

OpportunityDecoupler decoupler;

@testVisible OpportunitiesTriggerHandler(

OpportunityDecoupler od ) {

this.decoupler = od;

}

public OpportunitiesTriggerHandler() {

this(new OpportunityDecoupler());

}

Constructor injection

Page 67: Tests and Testability: Apex Structure and Strategy

SObject Decoupler - the revised unit public void afterUpdate(List<Opportunity> items) {

for(Opportunity item : items) {

if(decoupler.getIsClosed(item)) {

// do something

}

}

}

}

Page 68: Tests and Testability: Apex Structure and Strategy

SObject Decoupler - the revised test@isTest private class OpportunitiesTriggerHandlerTest {

@isTest static void myTest() {

TOpportunityDecoupler decoupler = new

TOpportunityDecoupler();

Opportunity o = new Opportunity(Id =

TestUtility.getFakeId(Opportunity.SObjectType));

decoupler.IsClosedMap.put(o.Id,true);

Fabrication of SObject IDs

Page 69: Tests and Testability: Apex Structure and Strategy

SObject Decoupler - the revised test

public with sharing class TestUtility {

static Integer s_num = 1;

public static String getFakeId(Schema.SObjectType sot) {

String result = String.valueOf(s_num++);

return sot.getDescribe().getKeyPrefix() +

'0'.repeat(12-result.length()) + result;

}

}

Fabrication of SObject IDs

Page 70: Tests and Testability: Apex Structure and Strategy

SObject Decoupler - the revised test OpportunitiesTriggerHandler handler = new

OpportunitiesTriggerHandler(decoupler);

handler.afterUpdate(new List<Opportunity>{o});

// test something

}

Page 71: Tests and Testability: Apex Structure and Strategy

SObject Decoupler - what did we do?Mechanism for mocking non-writable SObject properties•Access the SObject properties via a separate virtual class - the decoupler•Decoupler subclass mocks access to non-writable SObject properties•Inject the decoupler subclass in the test subject constructor

Page 72: Tests and Testability: Apex Structure and Strategy

SObject Decoupler - useful for…Mocking:•Formula fields•System fields•Rollup summary fields•Subselects

• Select (Select ... From OpportunityLineItems) From Opportunity

Page 73: Tests and Testability: Apex Structure and Strategy

Tests and TestabilityTesting strategy• Test pyramidUnit test principles• What is a unit?• Isolation• Saturation• ExpectationUnit test techniques• Testability• Fabrication• Substitution

• Loose type coupling• Dependency Injection• Decoupler• Adapter

Page 74: Tests and Testability: Apex Structure and Strategy

Adapter - for managed classes

LockingRules.LockingRule

Handler(managed class)

IHandleLockingRules

OpportunitiesTrigger Handler

LockingRuleHandler

(wrapper)

Test LockingRuleHandler

implements IHandleLockingRules

Page 75: Tests and Testability: Apex Structure and Strategy

Adapter - managed class

global class

LockingRuleHandler

static void handleTrigger()

Page 76: Tests and Testability: Apex Structure and Strategy

Adapter - interfacepublic interface IHandleLockingRules {

void handleAfterUpdate(Map<Id,sObject> oldMap,

Map<Id,sObject> newMap);

}

Page 77: Tests and Testability: Apex Structure and Strategy

Adapter - wrapperpublic class LockingRuleHandler implements

IHandleLockingRules {

public void handleAfterUpdate(Map<Id,sObject> oldMap,

Map<Id,sObject> newMap) {

LockingRules.LockingRuleHandler.handleTrigger();

}

}

Page 78: Tests and Testability: Apex Structure and Strategy

Adapter - mock implementationpublic class TLockingRuleHandler implements

IHandleLockingRules {

@testVisible Boolean calledHandleAfterUpdate;

public void handleAfterUpdate(Map<Id,sObject> oldMap,

Map<Id,sObject> newMap) {

calledHandleAfterUpdate = true;

}

}

Page 79: Tests and Testability: Apex Structure and Strategy

Adapter - the unitpublic class OpportunitiesTriggerHandler {

IHandleLockingRules lockingRules;

@testVisible OpportunitiesTriggerHandler(

IHandleLockingRules lr ) {

this.lockingRules = lr;

}

public OpportunitiesTriggerHandler() {

this(new LockingRuleHandler());

}

Constructor injection

Page 80: Tests and Testability: Apex Structure and Strategy

Adapter - the unitpublic void handleAfterUpdate(Map<Id,sObject> oldMap,

Map<Id,sObject> newMap) {

lockingRules.handleAfterUpdate(oldMap, newMap);

}

Page 81: Tests and Testability: Apex Structure and Strategy

Adapter - the testMap<Id,Opportunity> oldMap …

Map<Id,Opportunity> newMap …

TLockingRuleHandler lockingRules = new

TLockingRuleHandler();

OpportunitiesTriggerHandler trig = new

OpportunitiesTriggerHandler(lockingRules);

trig.afterUpdate(oldMap,newMap);

system.assert(lockingRules.calledHandleAfterUpdate);

Page 82: Tests and Testability: Apex Structure and Strategy

Adapter - what did we do?Mock a managed class•Create an interface defining our expectations of the managed class•Adapt the the managed class by wrapping and implementing the interface•Mock the production class by implementing the same interface•Inject the mock implementation during unit test execution

Page 83: Tests and Testability: Apex Structure and Strategy

In a nutshell…Unit tests are foundational to an effective Apex testing strategyConsider testability in the structure / design of your codeUnits must be independent to be easily testedUnits can be made independent through fabrication and substitution of connected resources

Page 84: Tests and Testability: Apex Structure and Strategy

Going forward…Tests and Testability on foobarforce.comSample code on Github@stephenwillcock

Page 85: Tests and Testability: Apex Structure and Strategy

Stephen Willcock

Director of Product Innovation at FinancialForce.

com@stephenwillcock

Page 86: Tests and Testability: Apex Structure and Strategy