1.05k likes | 1.72k Views
Testing in Salesforce. Going beyond 75% (or even 99%) statement coverage. Laurent Poulain lpoulain@progress.com. Disclaimer. This is NOT an exhaustive presentation about testing Great FREE online courses on testing Udacity : Software Testing
E N D
Testing in Salesforce Going beyond 75% (or even 99%) statement coverage • Laurent Poulain • lpoulain@progress.com
Disclaimer • This is NOT an exhaustive presentation about testing • Great FREE online courses on testing • Udacity: Software Testing • edX: CS169.1x / CS169.2x: Engineering Software as a Service • Two developers, three opinions
Why testing? • Because Salesforce requires it? • Because it’s a good practice • Useful for tedious manual tests • Useful after refactoring some code • “This code has been working fine for years. No need to retest it.” Yeah right! • Testing is a key component if you want to pass the Advanced Developer Certification
One of the most expensive bugs in history • The Ariane 5 rocket launch (1996) • A software exception occurred in legacy code from Ariane 4 • An integer conversion turned awry in the stabilization subsystem
One of the most expensive bugs in history • The Ariane 5 rocket launch (1996) • A software exception occurred in legacy code from Ariane 4 • An integer conversion turned awry in the stabilization subsystem • Cost (rocket + satellite): $370-$500 million • A simple test of the function would have detected the bug
Morale of the story: even stable code can break • Changes other than data can affect your code and/or your test • Triggers • Validation rules • Workflows • New required fields • …
Searching for a needle in a haystack • A test suite can ONLY prove that your code has a bug • You cannot run all the tests possible, so you have to make some choices
Code coverage • Statement coverage (used by Salesforce)
Other types of code coverage • Method coverage (S0): are all the methods called? • Method coverage (S1): are all the methods called from all the places where they can be called? • Branch coverage (C1): for each branch, are the tests covering both scenarios where the condition is met and is not met?
Code coverage example if (condition) { // block 1 ... } // block 2 ...
Statement coverage if condition == true: 100% if (condition) { // block 1 ... } // block 2 ...
Branch coverage if condition == true: 50% if (condition) { // block 1 ... } // block 2 ...
Branch coverage if condition == true: 50% (in theory) if (isAdmin) { // block 1 ... } // block 2 ...
Branch coverage+ if (isAdmin || isManager || isSuperUser) { // block 1 ... } // block 2 ...
Other types of code coverage (ctn’d) • Path coverage (C2): are all the possible paths through the code executed? • Loop coverage: cover every loop with a scenario with 0, 1 and multiple iterations • MC/DC (modified condition/decision coverage): for each condition, test all possible boolean combinations
Code coverage example try { if (isAdmin) { // block 1 ... } // block 2 ... // bug => crash for non-admin users } catch (Exception e) { // handles the exception }
Is the code doing the right thing? • Use assertions in your test • Add assertions in your code to detect internal inconsistencies • Two approaches • Black box testing: given an input, do we get the expected output? • What if you enter invalid data? • White box testing: given an input, is the program behaving as expected? • On Salesforce, needs to add code for testing purpose only • Sometimes, looking at Limits.get<limit name>() is enough • Too many white box tests it might be an indication you need to refactor your code
The need of oracles • An oracle can tell whether the code is doing the right thing or not • Weak oracle: exception in code • Medium oracle: assertion in code • Strong oracle • Compare results with an alternate implementation • Function inverse pair (e.g. save and load) • Null space transformation (feed two inputs that should be treated exactly the same)
Still not finished… • Start with acceptance tests • Decompose in unit tests • Integration testing: testing with other systems the app is interacting with • Use StaticResourceCalloutMock • Random/fuzz testing: sending random data or random input • You may not want to deploy such tests
How much testing? • Cost/benefit tradeoff: is the testing worth the time? • Critical parts should be well-tested (e.g. triggers) • It is not unusual to have more lines of test than lines of code itself • SQLite 3.8 has ~84 thousand lines of C code
How much testing? • Cost/benefit tradeoff: is the testing worth the time? • Critical parts should be well-tested (e.g. triggers) • It is not unusual to have more lines of test than lines of code itself • SQLite 3.8 has ~84 thousand lines of C code • SQLite tests: 91 million lines of code (x1084 !)
Salesforce Recommendations • Use the default “seeAllData=false” whenever possible • Use a separate class for tests • Create separate factory classes that will generate your test data • e.g. implement a factory method to create n Opportunities with m Contact per Opportunity • Bulkify your tests by feeding your code with 200 records • Use test.startTest() and test.stopTest() to get two sets of governor limits
Test-Driven Development (TDD)Behavior-Driven Design (BDD) • Both methodologies put writing tests first, coding second • Test early, test often • BDD is about the behavior of the application • A black box form of TDD testing • A good way to think of some tests: each feature should be tested • TDD can be very convenient when debugging code which is modifying data
Testing a complex VisualForce page • A Scorecard written in VF+apex • Contains 30+ case metrics • Several filters, breakdowns and ways to display the data • Some data is precomputed, some is computed on the fly • The challenges: • Lots of possible user options increases risk of bugs • Detecting bugs in numbers • Four types of tests • Code coverage • White box testing • Black box testing • Random testing -> Exhaustive testing
White box test • Check that the right function is called and the right dynamic SOQL is generated • Doesn’t care about the results • Use a class called SupportTest that acts as a “debug log” that the test can read • Stores data as a key / value pair • Use only static functions for simplicity • Available on https://github.com/lpoulain/DebugLog
SupportTest class SupportTest.reset(); … SupportTest.addValue(’compute’, ’Precomputed (6 months)’); SupportTest.addValue(’SOQL’, soql_query); SupportTest.addValue(’error’, e.getMessage()+’ - ’+getStackTraceString()); … System.assert(SupportTest.valueEquals(’compute’, expectedValue)); System.assert(SupportTest.getNbValues(’compute’) == 1); error = SupportTest.getOldestValue(’error’); System.assert(error == null, error); soqlError = SupportTest.SOQLWhoseWHEREEquals(’SOQL’, SOQLFilters) System.assert(soqlError == ’’, soqlError);
Black box test • Runs some scenarios and compares the result with hard-coded data • The test class contains all the necessary test data: • The raw data • Scenarios (user selection + expected output) • A VisualForce page to display the test data • Use the test as a form of documentation • When a new metric is added, the test is FIRST updated
Random test turned exhaustive test • Use the drill down capability as an oracle • Compared numbers from the Scorecard with numbers returned by the drilldown • Started as random tests • Decided to transform it to an exhaustive test • It is now a batch job • It is not an official test anymore • I did find a bug this way
BDD on Salesforce: Pickle • A BDD tool inspired from Ruby tool Cucumber • Allows to write some tests in plain English (some apex required behind the scenes) • Available on Github (https://github.com/lpoulain/pickle) • You can use Cucumber + Selenium, but it runs outside the Salesforce testing framework
Pickle Test Example Given the following Users:u1|Joe SmithGiven the following Accounts exist:Account Id|Account Namea2 |Foo Inc.Given the following Cases exist:Case Id|OwnerId|AccountId|Subject |Case Origin|Escalatedc3 |u1 |a2 |This is a test|Web |trueGiven I am on page "My VF Page" (MyClass) with parameters foo=24When I set "My Field" to "Random Value"and I click on "Update"Then "result" should contain "OK"and "numeric Field" should be >= "5"and "foo" should be equal to "24"and the query [SELECT Case Id, Subject FROM Case WHERE "Case Origin" = ’Web’] should return:c3|This is a test