afv-library/skills/running-apex-tests/references/performance-optimization.md
sandipkumar-yadav 37aa84df42
feat: @W-22444026@ Introducing Core Skills, Datacloud Skills, Industries and Utility Skills. (#268)
* 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>
2026-05-14 19:32:15 +05:30

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');
}
```