Upload
salesforce-developers
View
358
Download
0
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
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