Salesforce Test Classes, Demystified: Coverage Rules Every Admin-Turned-Dev Should Know

If you learned Salesforce the point-and-click way, your first encounter with Apex can feel like crossing into a different country — and nothing makes that culture shock sharper than test classes. You write some code, it runs fine, and then the platform tells you it won't deploy until you've also written code that tests your code. Here's the plain-English version of why that happens, and how to survive your first deployment.
What a test class actually is
A test class is an Apex class whose only job is to exercise your real code and prove it behaves. You mark it with the @isTest annotation, and Salesforce keeps it out of your org's production code footprint. Inside, you write static, void test methods that build records, run your logic, and then check the results with System.assert statements. Think of it as a scripted dress rehearsal: the test constructs a mini-scenario, executes the trigger or class under test, and verifies the ending matches what you expected.
Why Salesforce forces you to write them
Most platforms treat tests as a nice-to-have. Salesforce can't afford that attitude, because thousands of customers share the same multitenant infrastructure. The platform simply refuses to let unverified code into production. When you deploy, Salesforce runs your tests and demands two things: they must execute at least 75% of your Apex code, and every single test must pass. One failure and the whole deployment is rejected. It's the platform's immune system — broken logic gets caught before it can touch real user data.
The 75% rule and how coverage is calculated
Code coverage is the share of your Apex lines that actually executed while tests ran. Salesforce draws the line at 75% before a production deployment goes through. A few things first-timers get wrong about that number:
- Coverage is measured per class, and every class and trigger needs its own tests and coverage.
- 75% is a floor, not a goal. Experienced teams aim for 90% or more, because a passing percentage says nothing about whether your assertions were meaningful.
- Chasing coverage without assertions is the classic rookie trap. A test that runs code but asserts nothing inflates your percentage while proving absolutely nothing.
Writing assertions that mean something
The assertion is the entire point of the test. System.assert checks that the value your code produced matches the value you expected — and if it doesn't, the test fails loudly. Build these habits early:
- Assert outcomes, not just execution. Confirm the opportunity stage actually changed, the right number of records was created, the error fired when it should have.
- Feed your tests realistic data. Create every record inside the test itself — never depend on whatever data happens to exist in the org. Use a
@TestSetupmethod to share fixture data across test methods without repeating yourself. - Cover both the happy path and the edge cases. If your code has
if/elsebranches or ternary operators, each branch deserves its own test. - Test in bulk. Apex processes records in batches, so run your logic against 200 records rather than one — that's where governor-limit problems hide.
- Wrap async work in
Test.startTest()andTest.stopTest()so future methods and queueables actually execute during the test. - Use
System.runAs()to run code as different users and confirm your sharing rules and permissions hold up.
Deployment-time mistakes that bite first-timers
The errors that ambush new developers at deployment time are almost always avoidable:
- Hard-coding IDs or relying on org data. A test that passes in your sandbox and explodes in production usually assumed certain data would exist. Build every record your test needs.
- Skipping assertions. Tests without asserts pass silently — until a failed deploy or a code reviewer asks what your tests actually proved.
- One giant test method. Run test classes individually while you develop; when something breaks, you want to know exactly which scenario failed.
- Mixed DML errors. Creating a User record and updating a custom object in the same context blows up —
System.runAs()is the standard escape hatch. - Untested exception paths. Wrap expected exceptions in try-catch blocks inside tests so failures you planned for don't masquerade as surprises.
Keeping an eye on coverage
You can watch coverage in the Developer Console's Overall Code Coverage panel (blue lines ran, red lines didn't), pull numbers from the Salesforce CLI with sfdx force:apex:test:run --codecoverage --resultformat human, or use a browser extension like Apex Coverage Inspector that paints coverage right onto your code. The full walkthrough lives in our test classes and code coverage guide.
Test classes stop feeling like bureaucratic overhead the first time one catches a real bug before your users do. Write them early, assert something real, and aim well above the 75% floor — your future self, deploying late on a Friday, will be grateful.
Originally published at Way2Force.



