The Deployment That Passed in the Sandbox
A 42-component change set (6 Apex classes, 2 record-triggered flows, 1 custom object, 9 fields, 4 permission sets) validates clean in a Full Copy sandbox at 78% org-wide coverage. The production deployment is rejected at 71%. Production also contains three Apex classes and one trigger from a departed consultant; none are in the change set, none are exercised by the new test classes, and the worst sits at 9% coverage. The Full Copy sandbox was last refreshed five months ago. At the same time, a new required Region__c field on Account and a new validation rule on Case are scheduled to ship in the same change set, and a nightly inbound integration writes 30,000 records across both objects.
1. Why the gap? What exactly does the 75% requirement measure, and why was the sandbox number not predictive?
Read the worked answer
The denominator is the whole org, not the change set. The threshold is org-wide coverage across every Apex class and trigger that will exist after the deployment - including code nobody in this release touched. Your six new classes raise the average; the consultant's four components drag it back down. Each trigger also needs its own coverage, so one well-covered class will not rescue an untested trigger.
The sandbox was stale. A Full Copy five months old does not contain the consultant's code if it landed in production after the refresh. UAT measured a different org than the one being deployed to, so 78% was a number about a system that no longer exists.
Coverage is computed from the tests that actually ran in that deployment, and it is reported as a whole number against a hard threshold - there is no rounding up from 71.x.
The operational lesson: treat any coverage figure from a sandbox older than the current production baseline as fiction, and re-baseline before you build the change set.
2. You cannot modify the consultant's classes - they are under a support contract freeze. What are your options, and which do you choose?
Read the worked answer
You do not need to touch a class to cover it. Write a standalone @IsTest class that exercises its public and global methods inside Test.startTest()/stopTest() with a shared TestDataFactory. Test classes deploy on their own and their coverage counts toward org-wide coverage. For callouts in the frozen code, Test.setMock works from the outside; for async work, use Test.getEventBus() / scheduled-test patterns as applicable.
If the class is genuinely untestable (private entry points, no seams, hard dependencies), your only lever is the numerator: push your own new classes to 90%+ so the org average clears 75%. That is a real option but a fragile one, because every future deployment inherits the same drag.
Choose: deploy a test-only change set first. Ship the legacy coverage test class on its own, confirm org-wide coverage is comfortably above the line, then deploy the real change set against a target org that already includes it. It needs no contract change, it is reversible, and it makes the next four deployments easier instead of harder.
Do not attempt to delete or @isTest-exclude your way out - there is no supported exclusion mechanism for production Apex.
3. Sequence the deployment given the data problems. What order, and what would you validate first?
Read the worked answer
- Test-only change set first. Legacy coverage class in, coverage cleared with margin.
- Deploy Region__c as an optional field, plus the Case validation rule left inactive. Nothing breaks a 30,000-record nightly write yet.
- Backfill and align the integration. Populate Region__c by batch or default, and get the integration owner to send valid values for both objects. Replay a 30,000-record sample against a staging org.
- Activate. Turn on the validation rule, then set Region__c to required in a separate small deployment inside a window where the integration is paused. If the integration cannot be changed in time, put a bypass in the validation rule keyed to a custom permission for the integration user - explicitly, and with a removal date.
Validate the whole sequence with a dry-run validation deployment and RunLocalTests before each step, and prefer a source-based deployment (SFDX / unlocked package) over a change set: change sets hide dependency failures until the deploy, and with 42 components you want the failure list up front, not one at a time.