force.com unit testing · “integration test’.” as roy osherove (2009) defines it,...

13
Force.com Unit Testing Alex Berg Software Engineer marketing + technology 701.235.5525 | 888.9.sundog | fax: 701.235.8941 2000 44th st s | floor 6 | fargo, nd 58103 www.sundoginteractive.com

Upload: others

Post on 12-Mar-2020

3 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Force.com Unit Testing · “integration test’.” As Roy Osherove (2009) defines it, “Integration testing means testing two or more dependent software modules as a group.”

Force.com Unit Testing

Alex Berg Software Engineer

marketing + technology

701.235.5525 | 888.9.sundog | fax: 701.235.89412000 44th st s | floor 6 | fargo, nd 58103www.sundoginteractive.com

Page 2: Force.com Unit Testing · “integration test’.” As Roy Osherove (2009) defines it, “Integration testing means testing two or more dependent software modules as a group.”

Force.com Unit Testing | Alex Berg

2

“Your tests are ... able to represent you and to validate that your code is continuing to operate as it should.” Your tests will help bear your responsibility of detecting broken code in your org. Debugging a broken feature is quite difficult, and having unit tests in your org will both prevent it from happening as well as help you to narrow down the problem area and quickly fix it.

Software Tests Tell You When You’re DoneA new developer implementing solutions in Apex might want to research other options. If only there was something that could clearly tell you when your code is working following each coding attempt. I can imagine two possible ways to do that: hire an intern to manually run through a script you give him, or you can write an automated script that the system could read and execute. No, please do not hire an intern just to test your programming attempts, just take a bit of time to learn how to write simple software tests. The time invested will easily be worth the effort. If you ever feel unsure about your code, you can quickly run your tests again to ease your mind.

Write Tests First to Center Your MindTests can be pretty simple to write when you are in the right frame of mind. Coincidentally, once in this state of mind, you’ll be looking at the broad overview of the entire system, from which the solution and its complexities become easier to see. It makes you think, “Gee, how would I interact with Salesforce objects to create this functionality?” This will prevent you from re-writing the solution while designing the solution in your mind. Also, because you are thinking of the whole picture, the design will be more holistic and logical. Well-designed software inherently has fewer bugs. Plus, when you show your code to a hardcore software developer, they will probably be pretty impressed, as you are using their favorite practice, called “Test-Driven Development.”

Tests Prevent Recreating BugsBugs aren’t fun. They are difficult to find and take time to fix. When you finally fix the bug, you will not want it to appear again in the future. More often than not, a problem with your Force.com code will be caused by unexpected user input. After you find the bug, write a

Force.com - The wonderful addition to Salesforce that allows you to customize your Salesforce org to the way your business works. After all, no two businesses are exactly alike. Some are in the non-profit industry, and others are in the medical industry; some are larger than 10,000 employees, and others are smaller than 10. For many of these businesses, it is not realistic to hire a full time, experienced software developer. Therefore, the Salesforce administrator needs to learn about development platforms like Apex to fulfill new requirements. What is often not realized when starting to use Apex, however, is Salesforce’s requirement to accompany the code with unit tests.

A beginner may find it easy enough to write a test that obtains high code coverage, but code coverage is only one way of measuring unit tests. It is quite difficult to measure the real effectiveness of unit tests and their benefits are usually not felt until much later in time. In this paper, I’d like to share with new developers the purpose of software testing, how it can be useful, and go a bit deeper into the theory behind it.

The Basics of Software Testing

Business Contracts as a MetaphorLet’s start with the basics of software testing. It may seem so extraneous, so why does anyone care about tests? Let’s use a metaphor to explain it. We can think of a business like a software project, they both provide you with a service. You can use a business for its services without taking the time to write up a contract for them and you would probably be fine. But, suppose you start to depend on the service of this business when you start running your own business. When the time comes that the business fails to deliver the expected service, your own business will suffer as a result. This is not a risk that one is able to take when building a business.

Software developers should be even more concerned, as they have many more dependencies, most internal to their software system. The developer creates the risk, and it is the developer’s responsibility to minimize them; properly written tests can help you. As Chris Barry (2011) said,

Page 3: Force.com Unit Testing · “integration test’.” As Roy Osherove (2009) defines it, “Integration testing means testing two or more dependent software modules as a group.”

Force.com Unit Testing | Alex Berg

3

test that will feed the unexpected input into your code. Don’t spend time fixing the wrong thing, make certain that your hypothesized problem is, in fact, the same problem experienced in reality. Once the test is in place and the problem solved, you can be assured that someone else does not remove your fix and recreate the bug.

Common Types of Unit Tests on the Force.com Platform

Functional Tests as the Common Entrypoint to Proper Force.com TestsHere’s a situation that describes a common entrypoint into writing tests for a new Force.com developer. Not able to use a validation rule or workflow, a non-developer will have to climb a potentially steep learning curve to use Apex. When they finish the code, and the functionality finally works, they discover that they are then forced to return to coding land to attain minimum line coverage of their code before Salesforce will allow them to deploy it.

The problem then moves from Apex logic to Apex tests. Unfamiliar with them and not cognizant of the value of unit tests, they are hastily created to quickly attain the required amount of line coverage. When a developer intends to check whether their code has successfully added the desired functionality, what they are really testing is whether their code works within the context of many other features in the Salesforce org. They want to test whether the feature is fully-functional, so this type of test is called a “functional test.” Functional tests are great for preventing feature regression, a common problem over time, but depending on how layered the Apex solution was, it does not verify the integrity of each layer used.

Integration TestsIt is probably not something the developer often thinks about, but by using a DML operation in this test, the first domino in a chain of a defined series of DML steps is knocked down, and one of these steps holds our newly written code. Our code may be correct, but the unit test may still fail when any one of these other steps fails, which will cause the entire DML operation to fail. It is because of this dependency that we call this example of a test an “integration test’.” As Roy Osherove (2009) defines it, “Integration testing means testing two or more dependent

software modules as a group.” It informs us not of the correctness of its logic, but of how correctly our code functionally integrates with the rest of the system.

Why Functional Tests are Common on the PlatformForce.com developers will find themselves writing more functional tests than will developers on other platforms. A Windows application, for example, offers you many tools for building a window-based application, but the entire application will be written from a zero-functionality state. Force.com developers write code for a program that is already fully-functional. Most functional requests are just slight additions to the data model and its presentation. Because additions are usually small, the code solution is usually simple, which means there are just a few layers of code in the solution. At this level of simplicity in Salesforce, the simplest test is that functional and integration tests are one and the same. Because of this, most tests will be functional tests, as they are the most economical for simple development.

Inevitable Database Dependencies

Functional tests may be the most common type of test to use to cover Force.com code, but this does not mean they are the only type of test to be aware of. The other important type of software test is the “unit test.” As its name suggests, the scope of a unit test should be just a small unit of functionality. Writing tests for such small pieces of functionality may be challenging for many Force.com developers. Why so challenging? Let’s walk through an average task for an Apex developer.

As noted earlier, only a few lines of code are normally required to complete a feature request. For example, it is probably pretty common to do something like insert a record when a record in a different object is updated. To implement this simple request, we add some code to a trigger that will loop through each updated record and conditionally insert the new record. It’s just a few lines of code, and it may be fine to place them directly on the trigger handler.

With code directly on a trigger handler, the only way to cover the logic from a unit test is to use a DML operation to indirectly fire the trigger. This increases the scope of

Page 4: Force.com Unit Testing · “integration test’.” As Roy Osherove (2009) defines it, “Integration testing means testing two or more dependent software modules as a group.”

Force.com Unit Testing | Alex Berg

4

the test to include all the code that is executed by that DML operation, which is an integration test instead of a unit test.

Consider all the areas of Salesforce functionality that are touched when an integration test uses a DML statement. Salesforce developer documentation has an article titled, “Triggers and Order of Execution,” that informs us of each area. System field validation, all Apex code in ‘before’ triggers, system validation again, all Apex code in ‘after’ triggers, assignment rules, auto-response rules, workflow rules, escalation rules, and criteria based sharing, not including the smaller steps. This is a long list. Each of these steps is a potential failure point for your tests! If an integration test is failing, it is very possible that the logic being tested is perfectly valid, and the failure lies in one of these many DML operation steps. Integration tests are important, but they don’t paint a complete picture of the failure for us.

Changing an Integration Test Into a Unit TestIn our previous example, firing the DML operation invokes every trigger on the way that contains the logic we want to test. Wouldn’t it be nice if we could bypass that other functionality and test just the logic we added? If the test for this small unit of code is successful, then we know to look elsewhere for the problem when it arises! The code sits on the trigger, and the only way to get to it is through a DML operation. So, as it is, we cannot test that unit of logic.

Does this mean that Force.com code cannot be unit tested? Absolutely not. It just means that the path of least resistance for a simply coded trigger code does not lend itself conveniently to unit testing. It is quite simple, however, with enough foresight to pull the logic off of the trigger and into a helper class that the trigger calls. Once in its own method, we can write a unit test that directly calls this method. This avoids the chain of dependencies that appears when reaching the code through a DML operation, and now that these dependencies are removed, we can call it a unit test.

Five Ways to Test Functionality

The Five Goals of TestsTo comprehensively test new functionality on the Force.com platform, here is a checklist to review before calling your unit tests complete. Salesforce recommends that you test the following five areas of new functionality:

1. positive path 2. end state 3. negative path 4. governor limits 5. user permissions

We’ll go over each of these in detail and provide an example. I’ll write unit tests for the following piece of code, which performs the common task of updating another record if a field on the record in the trigger is changed.

Page 5: Force.com Unit Testing · “integration test’.” As Roy Osherove (2009) defines it, “Integration testing means testing two or more dependent software modules as a group.”

Force.com Unit Testing | Alex Berg

5

Here is the function for which we will write unit tests:

trigger ArtistHandleVenueChange on Artist__c (after update) { List<Venue__c> venuesToUpdateList = new List<Venue__c>(); for (Artist__c newArtist : trigger.newMap.keySet()) { Artist__c oldArtist = trigger.oldMap.get(newArtist.Id); if (newArtist.Venue__c != oldArtist.Venue__c) { if (newArtist.Is_Touring__c != false) { venuesToUpdateList.add(new Venue__c( Id = newArtist.Venue__c)); venuesToUpdateList.add(new Venue__c( Id = oldArtist.Venue__c)); } else { throw new MyCustomException( ‘An Artist must be touring to change Venues!’); } } } Database.update(venuesToUpdateList);}

Page 6: Force.com Unit Testing · “integration test’.” As Roy Osherove (2009) defines it, “Integration testing means testing two or more dependent software modules as a group.”

Force.com Unit Testing | Alex Berg

6

1) Positive Path

static testMethod void testVenueChange_ShouldThrowNoExceptions() { //Create test data Venue__c venue1 = new Venue__c(Name = ‘Test venue’, Location = ‘Test Location’); Database.insert(venue1);

Venue__c venue2 = new Venue__c(Name = ‘Test venue2’, Location = ‘Test Location2’); Database.insert(venue2);

Artist__c artist1 = new Artist__c(Name = ‘Test Artist’, Venue__c = venue1.Id); Database.insert(artist1);

//Invoke functionality Test.startTest(); artist1.Venue__c = venue2.Id; String errorMessages = ‘’; try { Database.update(artist1); } catch (Exception e) { String errorMessages = e.getMessage(); } Test.stopTest();

//Check results System.assertEquals(‘’, errorMessages);}

As Chris Barry (2011) candidly presents it, “The first goal of Apex tests is very simply to make sure that our code does not blow up.” To use a metaphor, if I plug an iPod via USB into my computer, I want to make sure the computer does not crash and reboot. This first goal is pretty simple: when given sensible input, it should not throw uncaught exceptions. Any code you write should expect to be supplied with bad data, and it should produce sensible output when it receives it.

Here is an example of a unit test that ensures a function can complete without throwing any exceptions. Exceptions can be created and thrown by your code to indicate unexpected failure, or they can be thrown by the Force.com platform. We should test to make sure our code is not causing the system to throw any unexpected exceptions:

Page 7: Force.com Unit Testing · “integration test’.” As Roy Osherove (2009) defines it, “Integration testing means testing two or more dependent software modules as a group.”

Force.com Unit Testing | Alex Berg

7

2) End State

static testMethod void testVenueChange_ShouldUpdateVenue() { //Create test data Venue__c venue1 = new Venue__c(Name = ‘Test venue’, Location = ‘Test Location’); Database.insert(venue1);

Venue__c venue2 = new Venue__c(Name = ‘Test venue2’, Location = ‘Test Location2’); Database.insert(venue2);

Artist__c artist1 = new Artist__c(Name = ‘Test Artist’, Venue__c = venue1.Id); Database.insert(artist1);

//Invoke functionality Test.startTest(); artist1.Venue__c = venue2.Id; Database.update(artist1); Test.stopTest();

//Check results Venue__c venue2After = [SELECT Was_Updated__c FROM Venue__c WHERE Id = :venue2.Id LIMIT 1]; //We assume there is logic to set Was_Updated__c field to true on Venue__c trigger. System.assertEquals(true, venue2After.Was_Updated__c);}

The next simple aspect of your code to test is its end state. Our code is not valuable if it doesn’t do what it was designed to do. If a piece of code does nothing, then it should be removed from the solution.

Here is an example of a unit test that tests a function’s end state:

Page 8: Force.com Unit Testing · “integration test’.” As Roy Osherove (2009) defines it, “Integration testing means testing two or more dependent software modules as a group.”

Force.com Unit Testing | Alex Berg

8

3) Negative Path

static testMethod void testVenueChange_AndIsNotTouring_ShouldThrowCustomException() { //Create test data Venue__c venue1 = new Venue__c(Name = ‘Test venue’, Location = ‘Test Location’); Database.insert(venue1);

Venue__c venue2 = new Venue__c(Name = ‘Test venue2’, Location = ‘Test Location2’); Database.insert(venue2);

Artist__c artist1 = new Artist__c(Name = ‘Test Artist’, Venue__c = venue1.Id); Database.insert(artist1);

//Invoke functionality Test.startTest(); Boolean caughtExpectedException = false; artist1.Venue__c = venue2.Id; artist1.Is_Touring__c = false; try { Database.update(artist1); } catch (MyCustomException e) { if (e.getMessage().contains(‘An Artist must be touring to change Venues!’)) { caughtExpectedException = true; } } Test.stopTest();

//Check results System.assertEquals(true, caughtExpectedException);}

The third on our list tests any negative cases that your code has special handling for. In our example, I set up an arbitrary exception case that protects against an artist changing venues when the artist is not actually on tour. We need to assert that this custom exception case performs as we expect. So in our test, we need to violate it by changing the Is_Touring__c field to false while changing the its Venue.

Here is an example of a unit test that tests a function’s negative path:

Page 9: Force.com Unit Testing · “integration test’.” As Roy Osherove (2009) defines it, “Integration testing means testing two or more dependent software modules as a group.”

Force.com Unit Testing | Alex Berg

9

4) Governor Limits

static testMethod void testVenueChange_UpdateBulkRecords_ShouldNotFail() { //Create test data Venue__c venue1 = new Venue__c(Name = ‘Test venue’, Location = ‘Test Location’); Database.insert(venue1);

Venue__c venue2 = new Venue__c(Name = ‘Test venue2’, Location = ‘Test Location2’); Database.insert(venue2); List<Artist__c> artistsToInsertList = new List<Artist__c>(); for (int i = 0; i < 200; i++) { Artist__c artist = new Artist__c(Name = ‘Test Artist ‘ + i, Venue__c = venue1.Id); artistsToInsertList.add(artist); } Database.insert(artistsToInsertList);

//Invoke functionality Test.startTest(); Boolean caughtException = false;

List<Artist__c> artistsToUpdateList = new List<Artist__c>(); for (Artist__c a : artistsToInsertList) {

Salesforce uses an architecture called “multi-tenancy,” the details of which won’t be discussed here, but means is that many organizations are sharing the same limited computing resources. To ensure that each organization’s custom code does not spiral out of control and negatively affect other organizations, Salesforce imposes governor limits.

Because these limits can affect how your code functions and can cause it to fail, Salesforce recommends you to test how close your code is to these governor limits. You want to make sure that your unit of code does not cause too many database queries or DML operations, because if your code violates these limits in production, the functionality will not operate, and it will also probably cause other functionality to fail. This is a pretty important aspect to test, especially when your org has much custom code already in it. These governor limits will be tested when performing a data migration which uses the Data Loader tool to insert many records at a time. If your triggers aren’t designed to handle operations on many records at once, data migration may fail.

Here is an example of a unit test for a function’s governor limits:

Page 10: Force.com Unit Testing · “integration test’.” As Roy Osherove (2009) defines it, “Integration testing means testing two or more dependent software modules as a group.”

Force.com Unit Testing | Alex Berg

10

a.Venue__c = venue2.Id; artistsToUpdateList.add(a); }

try { Database.update(artistsToUpdateList); } catch (Exception e) { caughtException = true; } Test.stopTest();

//Check results System.assertEquals(false, caughtException); List<Artist__c> artistListAfter = [SELECT Venue__c FROM Artist__c WHERE Id IN :artistsToUpdateList]; for (Artist__c a: artistListAfter) { System.assertEquals(venue2.Id, a.Venue__c); }}

Page 11: Force.com Unit Testing · “integration test’.” As Roy Osherove (2009) defines it, “Integration testing means testing two or more dependent software modules as a group.”

Force.com Unit Testing | Alex Berg

11

5) User Restrictions

static testMethod void testVenueChange_UpdateBulkRecords_ShouldNotFail() { //Create test data Venue__c venue1 = new Venue__c(Name = ‘Test venue’, Location = ‘Test Location’); Database.insert(venue1);

Venue__c venue2 = new Venue__c(Name = ‘Test venue2’, Location = ‘Test Location2’); Database.insert(venue2); Artist__c artist1 = new Artist__c(Name = ‘Test Artist’, Venue__c = venue1.Id); Database.insert(artist1); Profile normalProfile = [SELECT Id FROM Profile WHERE Name = ‘Normal Profile’]; User someUser = new User(Name = ‘Test User’, ProfileId = normalProfile.Id); Database.insert(someUser);

//Invoke functionality Test.startTest(); Boolean caughtException = false; System.runAs(someUser) {

try { artist1.Venue__c = venue2.Id; Database.update(artist1); } catch (Exception e) { caughtException = true; } } Test.stopTest();

//Check results System.assertEquals(true, caughtException);}

In our example, there is no special code to differentiate behavior depending on the currently running user. Many features want one behavior for a normal user and another behavior for more privileged users. This will change with each feature request, so take notes when the requirements include changing functionality based on the running user. The Salesforce unit testing framework provides facilities for testing this functionality differentiation by simulating a certain user.

Here’s an example of how to use use it:

Page 12: Force.com Unit Testing · “integration test’.” As Roy Osherove (2009) defines it, “Integration testing means testing two or more dependent software modules as a group.”

Force.com Unit Testing | Alex Berg

12

Conclusion

In this paper, we’ve discussed the purpose of software testing, its great benefits, and a few things to think about when writing tests on the Force.com platform. You should know the steps to take to produce a comprehensive test suite that will ward off regression errors with future changes. When you next write tests, take the time to follow them, and see what bugs they catch. You can be thankful that you don’t have to track them down and fix them later.

Page 13: Force.com Unit Testing · “integration test’.” As Roy Osherove (2009) defines it, “Integration testing means testing two or more dependent software modules as a group.”

Force.com Unit Testing | Alex Berg

13

Sources

Barry, C. & Henning, J. (2011) Hands-On: Testing in Force.com Code (Apex) for Developers [Conference] San Francisco, CA: Dreamforce 2011

Osherov, R. (2009). The art of unit testing with examples in .net. Greenwich, CT: Manning Publications Co.

Force.com Apex Code Developer’s Guide (2011) http://www.salesforce.com/us/developer/docs/api/index.htm