Code coverage is a measurement of how many unique lines of your code are executed while the automated tests are running. Code coverage percentage is the number of covered lines divided by the sum of the number of covered lines and uncovered lines.
For purposes of calculating code coverage, only executable lines of code are counted, whether covered or uncovered. Comments and blank lines are not counted, and System.debug()
statements and curly braces that appear alone on one line are also not counted.
While writing unit tests, the main emphasis should be given on actually testing the logic and behavior of the block by designing input data to ensure the code path executes and writing assertions to make sure the code's behavior is as expected. Code coverage is a side effect of this testing process.
Fundamentally, to increase your code coverage, you must write functional unit tests for code paths that are not currently covered. For more community resources on writing high quality unit tests, please see this canonical question; read the rest of this question to learn about common coverage scenarios.
Commonly Encountered Scenarios
Control Structures
You may find that your unit tests only cover one branch of a control statement, like an if
/else
or switch on
construct. Here's an example of a unit test that causes this issue.
Main Class
public class SFSEExample {
public static void updateAccountDetails(Account a) {
if (a.Industry == 'Finance') {
a.Description = 'NOTE: Finance Industry. ' + a.Description;
} else {
a.Description = 'Non-finance industry. ' + a.Description;
}
}
}
Test Class
@isTest
public class SFSEExample_TEST {
@isTest
public static void runTest() {
Account a = new Account(Name = 'Test', Industry = 'Finance', Description = 'Desc');
Test.startTest();
SFSEExample.updateAccountDetails(a);
Test.stopTest();
System.assertEquals('NOTE: Finance Industry. Desc', a.Description);
}
}
Code Coverage
Note that only one code path is covered by this unit test.
In branching code, you won't be able to ensure that diverging logical paths all execute in a single unit test. Trying to test too much in a single test method tends to make unit tests more difficult to understand and more fragile.
In most cases, you should expect to cover different branches of your logic using multiple unit tests, each of which performs different setup and data creation steps to execute and validate a different code pathway.
Loops
Code coverage that appears to "stop" and not enter the body of a loop, such as a for
or while
, stems from the same cause as with control structures: the test environment doesn't meet the preconditions for the loop body to execute. Here's an example:
for (Account a : [SELECT Id, Name FROM Account WHERE Name LIKE 'ACME%']) {
a.Description = 'Here are some more details.';
}
After running unit tests, if the line starting with a.Description
is not covered, it's an indication that the loop never begins iterating because the query returns no records. This is a failure of the test data setup: records weren't created that would exercise the functionality of this specific code path.
The same interpretation applies to while
and do/while
loops.
Batch Apex
If a Batch Apex class is run correctly in test context by using a pattern like
Test.startTest();
Database.executeBatch(myBatch);
Test.stopTest();
and the execute()
method is not covered, this almost always indicates that the query run in the start()
method returns no records, just as with loops over query results (above).
Exception Handlers
Code within the catch
block of an exception handler is executable and must be covered. In order to effectively cover and validate the behavior of your exception handlers, you must design unit test cases that will cause your code to fail and enter error-handling pathways.
Some common strategies to cover exception handlers include:
- Passing known bad parameters to methods, or inserting bad data in the unit test context, to cause a failure.
- Using the
@TestVisible
annotation to expose state and allow it to be manipulated in ways that would not be possible in production code.
- Applying dependency injection to force database actions, queries, or calls to other code to fail in specific ways.
- Removing exception handlers that cannot catch any real exception.
Because covering exception handlers is quite case-specific, consider asking a detailed question that focuses closely on your exception handler and attempted test code to get assistance designing a strategy.
Rollbacks & .addError()
Code that purposely adds an error to prevent a database operation or leverages Savepoint/rollback will need to be covered. This would typically entail:
- Passing the data needed to reach the scenario in the code where the error is added or the rollback occurs.
- Assert that your test data is as expected (no changes) or an error was thrown when you attempt to perform the relevant DML operation.
- For asserting errors thrown, you may use:
allOrNone
with Database.insert(yourTestRecord, false)
which would provide you with the errors to verify (Database.Error
).
- Try/Catch blocks surrounding your DML in your test class to catch your expected error thrown and assert its details. Likewise, you may assert no error was thrown in the opposite scenario.
Properties
Apex properties with defined getter and setter methods, such as
public Integer index { get; set; }
are considered executable lines of code. This is true even if, as depicted here, the getter and setter are synthesized by the compiler. You must cover these lines by "calling" the getter and setter method - accessing and setting the property's value.
Resources
For general resources on how to write high quality unit tests that generate code coverage, see this canonical question.