40
10 Principles of Apex Testing Kevin Poorman. Sr. Customer Success Architect, Marketing Cloud. Dreamforce 2015

10 Principles of Apex Testing

Embed Size (px)

Citation preview

10 Principles of Apex Testing

Kevin Poorman. Sr. Customer Success Architect, Marketing Cloud.

Dreamforce 2015

Kevin Poorman

Kevin PoormanSr. Customer Success Architect

Salesforce Marketing Cloud

Twitter: @Codefriar

Safe HarborSafe harbor statement under the Private Securities Litigation Reform Act of 1995:

This presentation may contain forward-looking statements that involve risks, uncertainties, and assumptions. If any such uncertainties materialize or if any of the assumptions proves incorrect, the results of salesforce.com, inc. could differ materially from the results expressed or implied by the forward-looking statements we make. All statements other than statements of historical fact could be deemed forward-looking, including any projections of product or service availability, subscriber growth, earnings, revenues, or other financial items and any statements regarding strategies or plans of management for future operations, statements of belief, any statements concerning new, planned, or upgraded services or technology developments and customer contracts or use of our services.

The risks and uncertainties referred to above include – but are not limited to – risks associated with developing and delivering new functionality for our service, new products and services, our new business model, our past operating losses, possible fluctuations in our operating results and rate of growth, interruptions or delays in our Web hosting, breach of our security measures, the outcome of any litigation, risks associated with completed and any possible mergers and acquisitions, the immature market in which we operate, our relatively limited operating history, our ability to expand, retain, and motivate our employees and manage our growth, new releases of our service and successful customer deployment, our limited history reselling non-salesforce.com products, and utilization and selling to larger enterprise customers. Further information on potential factors that could affect the financial results of salesforce.com, inc. is included in our annual report on Form 10-K for the most recent fiscal year and in our quarterly report on Form 10-Q for the most recent fiscal quarter. These documents and others containing important disclosures are available on the SEC Filings section of the Investor Information section of our Web site.

Any unreleased services or features referenced in this or other presentations, press releases or public statements are not currently available and may not be delivered on time or at all. Customers who purchase our services should make the purchase decisions based upon features that are currently available. Salesforce.com, inc.assumes no obligation and does not intend to update these forward-looking statements.

AgendaWhy we’re here

Lets talk about why we write tests

10 principles of testing Apex Code

A brief note on Mocking (Not the making fun of things kind)

Questions and Answers

• Note: If you don’t ask questions, I’ll have to ask you some.

I found this comment in an org once.

In a “test” class full of carefully crafted additions.

i++;i++;i++;…System.assert(i = 1000);

Lets talk about why we test.The real reasons

Tests provide assurance of functionality

Tests reduce cost of changeTests encourage modular, reusable code

Tests help identify engineering and architectural bugsTests help document expected behavior

Tests + Code = less likely to produce bugs

Principle #0 also known as: the general ideaA recipe for successful unit tests in Apex code

Assertions

StopTest()

Execute Your Unit of Code

StartTest()

Create Test Data

Test Method

Principle #1, Use AssertsA test without Assert methods isn’t a test, it’s a liability.

Three Assert methods built-in

• System.Assert(boolean-expression, ‘friendly message’)

• System.AssertEquals(expect, actual, ‘friendly message’)

• System.AssertNotEquals(expected, actual, ‘friendly message)

Every test method should include at least one assertion.

Good test methods include more than one.

The most robust, and helpful test methods include two sets of asserts.

• Assert that you properly setup all your test data

• Assert that the given test data was properly mutated by your code.

Principle #1, Use AssertsTop Tester Top Tip: You can write your own assert methods!

@isTestpublic class customAssertions{

public class customAssertionException extends exception{} public Boolean AssertDuplicateAccount(Account a, Account b){

// … compare accounts if(duplicate != true) {

Throw new customAssertionException(‘whoa, it’s not the

same’); }

return true; // Return true, without exception when assertion is true. }

}

Principle #2, use startTest and stopTestStart (then) Stop, Collaborate and Listen

Test.startTest() and Test.stopTest() help facilitate testing.

Isolates your code from the proper setup of the test(s)

startTest() resets DML, CPU Time and other governor limits, ensuring any limits you hit come from your tested code!

stopTest() forces asynchronous code to complete.

Principle #3 How to test: Positive TestsI believe you can do it!

You should write ‘Positive Tests’.

Positive tests prove the expected behavior, which is to say they prove it does what you think it does

Lets talk about multiple expected behaviors, (what’s that smell?)

Not just happy path testing

Given  Valid  Input

Executed  Code

Should  Return

Expected  Output

Use  Assertions  to  prove

Principle #3, Positive TestsPublic class exampleCode { Public Integer add(Integer one, Integer two){ return one + two; }}

Principle #3, Positive Testsprivate class exampleCode_Tests { @isTest static void test_Add_Postive() { exampleCode drWho = new exampleCode(); Test.startTest(); Integer testValue = drWho.add(5,7); Test.stopTest(); System.assertEquals(12, testValue, ‘Expected 5+7 to equal 12’);}

Principle #4, Negative TestsIt’s not you, it’s the *other drivers out there* that I don’t trust.

Negative tests prove that your code properly handles exceptions and errors.

The pattern works by calling method within a try/catch block in the test. Catch only the expected type of exception in the catch block

Less intuitive but more powerful!

At the very least, negatively test methods that utilize user data

Try {

Execute Method w/ Bad Input

} Catch () {

didCatch= true;

Boolean didCatch = false;

System.assert(didCatch)

Principle #4, Negative TestsPublic class exampleCode { Public class exampleCodeException{}

Public Static Integer division(Integer one, Integer two){ if(two == 0) { Throw new exampleCodeException(‘Dividing by zero makes

kittens cry’); } return one / two; }}

Principle #4, Negative Testsprivate class exampleCode_Tests { @isTest static void test_Divide_Negative() { Boolean didCatchProperException = false; Test.startTest(); Try { exampleCode.divide(1, 0); } catch (exampleCodeException AwesomeException){ didCatchProperException = true; } Test.stopTest(); System.assert(didCatchProperException, ‘Properly caught custom Exception’);}

Kittens are crying.Bad Data. No

donut.

Principle #5, User TestsBecause really, do we want to give the HR team access to Products and Opportunities?

User based tests prove your security model works like you think it does.

Test with Users of different Roles / Profiles and Permission sets!

The pattern works like this: Create a user with a given profile. As needed assign permission sets. Test both positive and negative users.

User  Role  1

Should  have  access

User  Role  2NO  access

User  w/  Perm  Set

No  Access  before  PermSet  applied

User  w/o  Perm  Set

No  Acces

Principle #5, User TestsPublic class exampleCode {

Public class exampleCodeException{}

Public Integer getBankAccount(Account a){

return a.SuperSekr3tBankAccountNum__c;

}

}

Principle #5, Positive User Testsprivate class exampleCode_Tests {

@isTest static void test_getBankAccount_Positive() {

exampleCode drWho = new exampleCode();

User u = AwesomeTestLib.getUserWithProfile(‘JediProfile’);

Account a = (Account)TestFactory.createSObject(new Account());

Integer result;

System.runAs(u){

Test.startTest();

result = drWho.getBankAccount(a);

Test.stopTest();

}

System.assertNotEquals(result, null,

‘Expected The Doctor to have access to bank #’);

}

Principle #5, Negative User Testsprivate class exampleCode_Tests {

@isTest static void test_getBankAccount_UberForNope() {

exampleCode Dalek = new exampleCode();

User u = AwesomeTestLib.getUserWithProfile(‘DalekProfile’);

Account a = (Account)TestFactory.createSObject(new Account());

Integer result;

System.runAs(u){

Test.startTest();

result = Dalek.getBankAccount(a);

Test.stopTest();

}

System.assertEquals(result, null, ‘Expected Daleks to be blocked’);

}

Principle #5, Testing with Perm Setsprivate class exampleCode_Tests { @isTest static void test_getBankAccount_W_PermSet() { exampleCode ClaraOswald= new exampleCode(); User u = AwesomeTestLib.getUserWithProfile(‘Standard User’); UtilityClass.AssignUserToPermissionSet(u, ‘Companion’); Account a = (Account)TestFactory.createSObject(new Account()); Boolean result; System.runAs(u){ Test.startTest(); result = ClaraOswald.canAccessTardis(a); Test.stopTest(); } System.assertNotEquals(result, null, ‘Expected ClaraOswald who has Companion Permissions to have access to the

Tardis’);}

Principle #6, Use your own dataBecause, someone will delete the account named “do not delete, used for testing”

Always build your own test data.

If you created the data, you can precisely assert against it.

Unless you have to, never use @isTest(seeAllData=true)

List of Acceptable Reasons to use @seeAllData = true.

Reason CommentsYou  don’t  know any  better. Now  you  know  better.You’re  unit testing  Approval  Processes Yeah.  Sometimes  you’re  the  statue,  sometimes  you’re  the  

pigeon.  ??? ‘been  spending most  our  lives  living  in  a  coder’s  paradise  

Used  ‘seeAllData’  once  or  twice,  living  in  a  coder’s  paradise.  Wrote  a  test  on  Monday,  soon  I’ll  write  a  ‘nother

Principle #6, Use your own dataWork smarter, not harder.

• TestFactory, An open source test data factory from Daniel Hoechst. The coolest thing since the iPad. Found at

http://bit.ly/1c5exnV • With it you can say:

• Account a = (Account)TestFactory.createSObject(new Account());

• Opportunity o = (Opportunity)TestFactory.createSObject(new Opportunity(AccountId = a.Id));

• Account[] aList = (Account[])TestFactory.createSObjectList(new Account(), 200);

Principle #7, Use a domain specific helper libYou know, so you’re not constantly reinventing the wheel.

Use a sane naming convention. Ie: Get* returns generated test data matching the method name!

Mark your test helper class as @isTest to ensure it’s never called by live code.

Amazing  Candidates  for test  helper  libs CommentsgetStandardUserWithPermissionSet() Extend  TestFactory.getOpportunityWithAccountAndProducts() Generating complex  object  chains!Any  big  of  code  you  reuse in  more  than  one  test Because. Because.  DRY.  DRY.HTTPMockGenerator Because  having  to  include  all  those  mock  methods  

gets  boring.

Principle #8, MockingGo ahead, mock me.

Integration v. Unit tests

• Integration tests test code in an execution context –i.e. setup some data and execute the code within the context.

• Unit tests focus on a single unit of code, not necessarily a complete function.

Mocks allow us to write true unit tests by ‘mock’ objects.

• You get to setup the mock, and it’s responses.

Principle #8, MockingThe irritation of integration “unit” tests

Ordinary Unit Test

Calls a service

Returns ???

Principle #8, MockingIn Soviet Russia Apex unit tests Mock you.

Unit Test w/ Mocks

Calls a mocked service

Returns exactly what you asked it to.

Principle #8, Mocking, how it worksMarvelous Mock Objects Mocking Service Layer Objects

Data  Access  LayerService  LayerClasses

Unit  Tests

Mock  Service  Layer  Objects Bypassed

Standard  Service  Objects

Functions  Normally

Principle #8, Do’s, Don’ts and Dang-its of MockingIt’s not just for Teenagers anymore.

Do mock objects or method whenever it may fail and throw off the test.

Do mock all your HTTP(s) callouts. Even those soapy things.

Do mock expensive service layer calls with abandon.

Do not mock the unit of code you’re testing at the moment. (Yes, I’ve seen this.)

You have to:

Use the excellent and amazing Fflib_ApexMocks.

Code to interfaces so you can Mock it good.

Ask yourself this: Does the code your testing depend on this object or method having a specified value? If so, you must mock it. Mock it good.

Principle #8, Mocking for Mortals.

Public Boolean MockExample(Account a, Account b){ fflib_apexMocks m = new ffLib_apexMocks(); myInterface mock = new MockMyService(m); m.startStubbing(); m.when(mock.add(5,7)).thenReturn(12); m.stopStubbing(); Test.startTest(); ExampleCode mockObj = new exampleCode(myAwesomeService); Integer result = mockObj.add(5,7); Test.stopTest(); System.assertEquals(12, result, ‘friendly message’);}

Keep calm and mock

Principle #8, Mocking Part 2 – those pesky http requests

Public class HTTPMockFactory implements HttpCalloutMock {

HTTPMockFactory (Integer code, String status, String body, Map<String, String> responseHeaders) { //set class variables here.

} public HTTPResponse respond(HTTPRequest req) {

HttpResponse res = new HttpResponse();

res.setStatusCode(this.code); res.setStatus(this.status);

res.setBody(this.bodyAsString); return res;

}}

Your HTTP request is so fat, even Google couldn’t store it all.

Principle #9, Write for testingSmaller methods are cute like kittens.

Write small, tightly focused methods that play well with others.

Compose advanced functionality by using your small methods together

Write more-or-less unit tests with these focused methods

Write true unit tests with mocks for testing custom, complex composed from your small, tightly focused methods.

Principle #9, Write for testing: guidelinesYou can thank me later.

Keep your methods to no more than 20 lines.

Keep method arguments to a minimum – no more than four.

Write your Visualforce controllers to use a single wrapper object.

• Either mock this wrapper object, or write a Test Helper method to construct it.

Use descriptive method names. Because the person who maintains this code after you’re long gone may just be that psychopathic developer who knows where you live.

Principle #10, Use Continuous Integration!I can haz Ci?

Continuous Integration is the process that enforces a full and complete test run is triggered every time someone commit to your source repository. You are using Git right?

A number of tools are available for CI on the Salesforce1 platform.

Travis.ci, Drone.io, Jenkins, Bamboo and Codeship are some examples

Principle #10, Use Continuous Integration!We need a process for this process.

CI gives you insight to failing tests as soon as they’re committed not when you try to deploy

Facilitates multiple developers working on the same project in their own sandboxes / dev orgs

Keeps you aware of code coverage as development occurs.

Groot's  devsandbox Groot  

commits,  CI  runs  all  the  tests Te

sts  Pass! Groot

makes  a  pull  request

Rocket  reviews Merges

code  into  the  mainline

CI  runs  tests Testing  the  

integration  of  these  changes  with  others'  work

Notifications Everyone  

knows  how  awesome  Groot  is.Deployment  issue  is  created.

Principle #10, Use Continuous Integration!Dude, you broke the build.

When tests fail… Sad Manager Panda is sad.

Groot's  devsandbox Groot  

commits,  CI  runs  all  the  tests Te

sts  FAIL! Groot fixes  

his  bug  hoping  no  one  sees  

Starlord  reviews Merges

code  into  the  mainlineShame  on  him

CI  runs  tests Groot’s  

Change  breaks  Rocket’s  new  feature.

Notifications Everyone  

knows  how  #FAIL  Groot  isManager  Pandaeats  Groot

Thank you

Useful tips

Don’t insert or query unless you have to. You can often test with objects in memory!

STOP, COLLABORATE AND LISTEN, Jesse’s back with a brand new mission. Learn more about Mocking by watching Jesse Altman’s (@JesseAltman) excellent intro to mocking with Apex mocks here:

http://bit.ly/1HtXk2B

Write and use a standardized Logging library that wraps log data in a highly visible header/footer

GO HERE NOW GO HERE NOW

Share Your Feedback, and Win a GoPro!

3 Earn  a  GoPro  prize  entry  for  each  completed  surveyTap  the  bell  to  take  a  survey2Enroll in  a  session1