An Apex test class is a regular class annotated @isTest whose methods exercise production code and assert the results. Salesforce requires 75% line coverage across all Apex in an org before deployment to production succeeds — but coverage is the minimum, not the goal.
The skeleton every test class follows
@isTest
private class AccountServiceTest {
@testSetup
static void setupData() {
// Data shared across all test methods, created once per test class
List<Account> accounts = new List<Account>();
for (Integer i = 0; i < 5; i++) {
accounts.add(new Account(Name = 'Test ' + i, Active__c = true));
}
insert accounts;
}
@isTest
static void getActiveAccounts_returnsOnlyActive() {
// ARRANGE — extra setup specific to this test
insert new Account(Name = 'Inactive', Active__c = false);
// ACT
Test.startTest();
List<Account> result = AccountService.getActiveAccounts(10);
Test.stopTest();
// ASSERT
System.assertEquals(5, result.size(), 'Should return only active accounts');
for (Account a : result) {
System.assertEquals(true, a.Active__c);
}
}
}
The pieces, explained
@isTeston the class — tells the platform “this class isn’t production code; don’t count it toward the 6 MB Apex code limit; don’t deploy it unless explicitly requested.”private— test classes don’t need to be public. Convention isprivateso they can’t be called from elsewhere.@testSetup— runs once before all@isTestmethods in the class. Data inserted here is automatically rolled back at the end of each test method, then restored for the next.@isTeston each method — declares the test entry point. Salesforce only runs methods with this annotation.static— test methods must be static.- No return value, no parameters — test methods are
static void someTest(). Period.
Arrange — Act — Assert
The convention every senior developer follows:
- Arrange — create the data the code under test needs
- Act — call the method, wrap the call in
Test.startTest()/Test.stopTest()to reset governor limits - Assert — verify the result with
System.assertEquals,System.assert,System.assertNotEquals
Each test method runs in its own transaction
Salesforce rolls back all DML at the end of every test method. So if Test A inserts 5 Accounts and Test B queries Account, Test B sees zero Accounts unless it inserts its own. This is why test isolation is automatic — you don’t have to clean up.
What “good coverage” looks like
The 75% bar is necessary but not sufficient. A senior reviewer looks for:
- Positive tests — happy path
- Negative tests — bad input, missing required fields, security exceptions
- Bulk tests — call the method with 200 records, prove no governor limit blows up
- Boundary tests — what happens with 0, 1, N+1, max records?
Common anti-patterns
Test.isRunningTest()in production code branching. Tests should exercise the real path, not a fake one.- No assertions — calling the method without verifying anything. The line gets covered, but the test catches nothing.
- Hard-coding IDs — IDs differ across orgs. Query for them.
Verified against: Apex Developer Guide — Testing Apex. Last reviewed 2026-05-17 for Spring ‘26 release.