Enums, interfaces, abstract methods, class-level declaration statements, class definitions, and debug statements never count for code coverage. Everything else does, including getters and setters, non-abstract methods (including the method signature), class-level assignments, and every non-debug line of code within a method always count for coverage.
Has this always been the case?
Yes, it's been this way since the inception of Apex, as far as I can remember.
In the enum class no coverage data is calculated and the lines do not count toward to total lines to cover
Yes, it is a "class-level declaration statement," and thus has no coverage.
The first property is not counted nor covered.
It is a class-level declaration statement, and thus has no coverage, either.
The one with the getter / setter is counted and not covered.
This transforms the line from a class-level declaration statement to a method signature statement, thus requiring coverage.
It seems if a property is defaulted to a value it counts as a line of code to cover and gets covered.
Yes, any time you use the =
operator, it is no longer just a declaration, it is an executable statement; you're assigning a value to a variable.
Yes, there are plenty of ways to make more work for oneself, and one of them is to unnecessarily declare default getters and setters. They are strictly only necessary for Visualforce attributes, and should not be used otherwise without a reason.
I think more to the point, it is up to each developer to learn all of the particular tools in which they use (in this case, the various parts of the Apex language) to write the most optimal code possible. Unfortunately, we have a lot of poorly written resources out there that newer developers prefer to read than to check the official documentation, which further perpetuates the cycle.
This problem is further exacerbated by the fact that the documentation team, despite doing a pretty good job at what they do, presume that the readers have a decent history in Java, C#, or another related language. The "obvious parts", like why { get; set; }
causes code coverage, isn't in the documentation. It's presumed that the reader knows that these are really methods, and all methods require code coverage.
This answer is not intended to teach you everything about writing unit tests, nor to specifically answer every question, but to provide a quick summary and links to the resources that will help you move forward and develop more specific questions that SFSE can assist with.
Overview
Unit (and integration) testing is a big topic, but it starts with a small set of principles. If you've never written a unit test before, we strongly encourage you to complete the Unit Testing on the Lightning Platform Trailhead module and read at least the Month of Testing series. These materials and others are linked under Resources, below.
Fundamentally, testing comprises three steps, all of which take place within the context of a unit test:
- Creating test data as input for your code, which is designed to ensure that a specific logical path is executed. This can take the form of values in memory or of creating and inserting sObjects.
- Executing that code, meaning that that specific code path runs within a method annotated with the
@isTest
annotation.
- Writing assertions to demonstrate that the results of the code are correct and as expected for the given inputs.
Then, all code that is executed under step (2) is counted as covered under Salesforce's code coverage metrics. Code coverage is a side effect of high quality unit tests. Salesforce uses code coverage as a proxy to measure the presence of unit tests in your deployments.
Unit testing principles are quite general, and most Apex code is not special in the sense of requiring unique approaches to create a successful test. Techniques for implementing tests that perform all three steps are taught in the resources we include below.
Test Isolation
On Salesforce, all unit tests are executed in an isolated context. In this context, your code cannot see data in your organization, including ordinary records as well as Custom Settings. All data must be created via the unit test or @testSetup
method.
Metadata records, including Users and Custom Metadata, are visible in test context.
An older annotation, seeAllData=true
, allows tests to see all data in the Salesforce org. Use of this annotation is strongly discouraged for new unit tests, and is considered a very bad practice. It's important instead to follow the first step above, by designing test data as input for your code. This practice makes unit tests self-contained and repeatable, and insulates them against fragility stemming from data changes.
Smoke Tests (Tests without Assertions)
Unit tests that don't contain assertions are often called smoke tests. These tests have very limited value, because they show nothing other than that your code does not crash under a specific set of circumstances. They don't prove the code works or does what it's intended to do.
Resources
Trailhead
Apex Developer Guide
- Testing Apex
- The
Test
class reference, which includes a variety of testing-related utility methods, including Test.stopTest()
and Test.startTest()
, as well as methods for setting static SOSL results, controlling audit fields, working with mocks and stubs, and other tools.
Blogs and Articles
- Month of Testing series from the Salesforce Developers blog.
Dreamforce Video Content
Third-Party Testing Frameworks (Advanced Topics)
- ApexMocks, an open-source mocking framework for Apex.
- Force-DI, a framework for pervasive dependency injection.
Best Answer
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
orswitch on
construct. Here's an example of a unit test that causes this issue.Main Class
Test Class
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
orwhile
, 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: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
anddo/while
loops.Batch Apex
If a Batch Apex class is run correctly in test context by using a pattern like
and the
execute()
method is not covered, this almost always indicates that the query run in thestart()
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:
@TestVisible
annotation to expose state and allow it to be manipulated in ways that would not be possible in production code.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:
allOrNone
withDatabase.insert(yourTestRecord, false)
which would provide you with the errors to verify (Database.Error
).Properties
Apex properties with defined getter and setter methods, such as
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.