mirror of
https://github.com/forcedotcom/afv-library.git
synced 2026-07-30 11:43:26 +08:00
* Migrating Core Salesforce Skills * Updating pr comments * updat reference * Updating a skill * Migrating Datacloud skills * Migrating Industries cloud skills * Validating - skills fixing --------- Co-authored-by: Sandip Kumar Yadav <sandipkumar.yadav+sfemu@salesforce.com>
284 lines
7.5 KiB
Markdown
284 lines
7.5 KiB
Markdown
<!-- Parent: running-apex-tests/SKILL.md -->
|
|
# Performance Optimization for Apex Tests
|
|
|
|
Fast tests enable faster development. When test suites run quickly, developers refactor confidently. This guide covers techniques to dramatically reduce test execution time.
|
|
|
|
---
|
|
|
|
## Why Test Speed Matters
|
|
|
|
| Test Suite Duration | Impact |
|
|
|---------------------|--------|
|
|
| **< 5 minutes** | Developers run frequently, catch issues early |
|
|
| **5-30 minutes** | Developers run occasionally, issues slip through |
|
|
| **30+ minutes** | Developers avoid running, tests become stale |
|
|
| **Hours** | CI/CD bottleneck, blocked deployments |
|
|
|
|
The goal: **Sub-second unit tests**, with integration tests taking seconds, not minutes.
|
|
|
|
---
|
|
|
|
## Technique 1: Mock DML Operations
|
|
|
|
Database operations are the #1 cause of slow tests.
|
|
|
|
### The Numbers
|
|
|
|
| Operation | 10,000 Records | Notes |
|
|
|-----------|----------------|-------|
|
|
| Actual insert | ~50 seconds | Database round-trips |
|
|
| DML mocking | <1 second | In-memory only |
|
|
| **Improvement** | **~35x faster** | |
|
|
|
|
### Implementation
|
|
|
|
See `assets/dml-mock.cls` and `references/mocking-patterns.md` for complete implementation.
|
|
|
|
```apex
|
|
// ❌ SLOW: Actual database insert
|
|
List<Account> accounts = TestDataFactory.createAccounts(1000);
|
|
insert accounts; // ~5 seconds
|
|
|
|
// ✅ FAST: Mock DML
|
|
DMLMock.reset();
|
|
AccountService service = new AccountService(new DMLMock());
|
|
service.createAccounts(accounts); // <0.1 seconds
|
|
Assert.areEqual(1000, DMLMock.InsertedRecords.size());
|
|
```
|
|
|
|
---
|
|
|
|
## Technique 2: Mock SOQL Queries
|
|
|
|
Query execution adds overhead, especially with large result sets.
|
|
|
|
```apex
|
|
// ❌ SLOW: Actual query requiring test data setup
|
|
@TestSetup
|
|
static void setup() {
|
|
List<Account> accounts = new List<Account>();
|
|
for (Integer i = 0; i < 1000; i++) {
|
|
accounts.add(new Account(Name = 'Test ' + i));
|
|
}
|
|
insert accounts; // Slow
|
|
}
|
|
|
|
// ✅ FAST: Mock query results
|
|
AccountSelector.setMockResults(new List<Account>{
|
|
new Account(Name = 'Mock 1'),
|
|
new Account(Name = 'Mock 2')
|
|
});
|
|
List<Account> results = AccountSelector.getActiveAccounts(); // Instant
|
|
```
|
|
|
|
---
|
|
|
|
## Technique 3: Minimize @TestSetup
|
|
|
|
`@TestSetup` runs before every test method. Large setups compound execution time.
|
|
|
|
```apex
|
|
// ❌ SLOW: Heavy @TestSetup
|
|
@TestSetup
|
|
static void setup() {
|
|
List<Account> accounts = TestDataFactory.createAccounts(100);
|
|
insert accounts;
|
|
List<Contact> contacts = TestDataFactory.createContacts(500, accounts);
|
|
insert contacts;
|
|
List<Opportunity> opps = TestDataFactory.createOpportunities(200, accounts);
|
|
insert opps;
|
|
// Total: 800 DML operations, runs before EACH test method
|
|
}
|
|
|
|
// ✅ FAST: Minimal @TestSetup, mock what you can
|
|
@TestSetup
|
|
static void setup() {
|
|
// Only create what MUST exist in database
|
|
Account parentAccount = new Account(Name = 'Required Parent');
|
|
insert parentAccount;
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Technique 4: Choose Efficient Loop Constructs
|
|
|
|
Loop performance varies significantly with large iterations.
|
|
|
|
### Benchmark Results (10,000 iterations)
|
|
|
|
| Loop Type | Duration | Notes |
|
|
|-----------|----------|-------|
|
|
| While loop | ~0.4s | Fastest |
|
|
| Cached iterator | ~0.8s | Good alternative |
|
|
| For loop (index) | ~1.4s | Acceptable |
|
|
| Enhanced for loop | ~2.4s | Convenient but slower |
|
|
| Uncached iterator | CPU limit | Avoid |
|
|
|
|
### Recommendation
|
|
|
|
```apex
|
|
// ✅ PREFERRED: While loop for large iterations
|
|
Iterator<Account> iter = accounts.iterator();
|
|
while (iter.hasNext()) {
|
|
Account acc = iter.next();
|
|
// process
|
|
}
|
|
|
|
// ✅ ACCEPTABLE: Standard for loop
|
|
for (Integer i = 0; i < accounts.size(); i++) {
|
|
Account acc = accounts[i];
|
|
// process
|
|
}
|
|
|
|
// ⚠️ CONVENIENT BUT SLOWER: Enhanced for
|
|
for (Account acc : accounts) {
|
|
// process
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Technique 5: Batch Test Data Creation
|
|
|
|
Creating records one-by-one is slow. Batch operations are faster.
|
|
|
|
```apex
|
|
// ❌ SLOW: One-by-one creation
|
|
for (Integer i = 0; i < 200; i++) {
|
|
Account acc = new Account(Name = 'Test ' + i);
|
|
insert acc; // 200 DML statements!
|
|
}
|
|
|
|
// ✅ FAST: Batch creation
|
|
List<Account> accounts = new List<Account>();
|
|
for (Integer i = 0; i < 200; i++) {
|
|
accounts.add(new Account(Name = 'Test ' + i));
|
|
}
|
|
insert accounts; // 1 DML statement
|
|
```
|
|
|
|
---
|
|
|
|
## Technique 6: Use Assert Instead of System.assert
|
|
|
|
The modern `Assert` class is cleaner and provides better error messages.
|
|
|
|
```apex
|
|
// ❌ OLD: System.assert (still works but verbose)
|
|
System.assert(result != null, 'Result should not be null');
|
|
System.assertEquals(expected, actual, 'Values should match');
|
|
|
|
// ✅ MODERN: Assert class (Apex 56.0+)
|
|
Assert.isNotNull(result, 'Result should not be null');
|
|
Assert.areEqual(expected, actual, 'Values should match');
|
|
Assert.isTrue(condition, 'Condition should be true');
|
|
Assert.fail('Should not reach here');
|
|
```
|
|
|
|
---
|
|
|
|
## Technique 7: Avoid SOSL in Tests
|
|
|
|
SOSL searches return empty results in tests unless configured.
|
|
|
|
```apex
|
|
// ❌ PROBLEM: SOSL returns nothing in tests by default
|
|
List<List<SObject>> results = [FIND 'test' IN ALL FIELDS RETURNING Account];
|
|
// results[0] is EMPTY even with matching records!
|
|
|
|
// ✅ SOLUTION: Use Test.setFixedSearchResults()
|
|
@IsTest
|
|
static void testSearch() {
|
|
Account acc = new Account(Name = 'Searchable');
|
|
insert acc;
|
|
|
|
// Configure what SOSL will return
|
|
Test.setFixedSearchResults(new List<Id>{ acc.Id });
|
|
|
|
Test.startTest();
|
|
List<List<SObject>> results = [FIND 'test' IN ALL FIELDS RETURNING Account];
|
|
Test.stopTest();
|
|
|
|
Assert.areEqual(1, results[0].size(), 'Should find configured record');
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Technique 8: Strategic Test Method Scoping
|
|
|
|
Run only the tests you need during development.
|
|
|
|
```bash
|
|
# ❌ SLOW: Run all tests (minutes to hours)
|
|
sf apex run test --test-level RunLocalTests --target-org sandbox
|
|
|
|
# ✅ FAST: Run single test class
|
|
sf apex run test --class-names MyClassTest --target-org sandbox
|
|
|
|
# ✅ FASTER: Run single test method
|
|
sf apex run test --tests MyClassTest.testSpecificMethod --target-org sandbox
|
|
```
|
|
|
|
---
|
|
|
|
## Technique 9: Async Test Execution
|
|
|
|
Use async mode for large test suites to avoid blocking.
|
|
|
|
```bash
|
|
# Start tests asynchronously
|
|
sf apex run test --class-names MyClassTest --wait 0 --target-org sandbox
|
|
# Returns test run ID: 707xx0000000000
|
|
|
|
# Check status later
|
|
sf apex get test --test-run-id 707xx0000000000 --target-org sandbox
|
|
```
|
|
|
|
---
|
|
|
|
## Anti-Patterns to Avoid
|
|
|
|
| Anti-Pattern | Problem | Solution |
|
|
|--------------|---------|----------|
|
|
| DML in loops | N operations instead of 1 | Bulk DML outside loops |
|
|
| Large @TestSetup | Runs before every test | Minimize or mock |
|
|
| No mocking | Full database round-trips | Mock DML, queries, callouts |
|
|
| SeeAllData=true | Depends on org data | Create test data |
|
|
| Deep nested loops | O(n²) or worse | Flatten with Maps |
|
|
| String concatenation in loops | New string objects each iteration | Use List and join |
|
|
|
|
---
|
|
|
|
## Optimization Checklist
|
|
|
|
Before committing tests, verify:
|
|
|
|
- [ ] DML operations are mocked where possible
|
|
- [ ] @TestSetup is minimal
|
|
- [ ] No SOQL/DML inside loops
|
|
- [ ] Uses bulk patterns (200+ records)
|
|
- [ ] Individual test methods run in <1 second
|
|
- [ ] Full test class runs in <10 seconds
|
|
- [ ] Uses Assert class (not System.assert)
|
|
|
|
---
|
|
|
|
## Performance Testing Your Tests
|
|
|
|
```apex
|
|
@IsTest
|
|
static void testPerformance() {
|
|
Long startTime = System.currentTimeMillis();
|
|
|
|
// Your test code here
|
|
|
|
Long duration = System.currentTimeMillis() - startTime;
|
|
System.debug('Test duration: ' + duration + 'ms');
|
|
|
|
// Assert performance constraint
|
|
Assert.isTrue(duration < 1000, 'Test should complete in <1 second, took: ' + duration + 'ms');
|
|
}
|
|
```
|