Pages

Friday, April 8, 2016

Test-Driven Development Example

The following post is based on an email I sent a student worker who, bless his heart, is doing terribly complicated and important, but also terribly boring, grunt work. As a thank-you, I'm giving him Apex coding projects to do in a Developer Org and granting him a block of time per day to work on them.


This morning, I'm working on writing a new "public" Apex method that, as a parameter, takes a set of Contact IDs. If a Contact's "Account.Name" is meaningless (e.g. "No Company Assigned,"), this method then checks to see if there is a non-null string in that Contact's "Company Holding Spot" custom field. If any such strings happen to match the Name of a record in our Accounts table, my new method populates the Contact's AccountId with the appropriate value. Hopefully, I can use this to clean up a bunch of old data in one fell swoop.

I'm writing the new method in test-driven-development style, which means I have my test fully ready to go before I've put any "action" code into my new method. Here's what that looks like:

@isTest
public class ScratchPadClassTest {
    
    // Class-level static variables
    private static Boolean setupAlreadyRan = FALSE;
    private static UtilityDefaultInfoOftenNeeded useful;
    
    // The test method    
    static testMethod void testCompanyHoldingSpotToAcctId() {
        runSetup();
        
        // Set up a couple of test accounts (also remember that a 3rd "No Company Assigned" one exists thanks to "runSetup()")
        Account aMcD = new Account(Name='McDonalds');
        Account aTgt = new Account(Name='Target');
        insert new List<Account>{aMcD, aTgt};
        
        // Set up a couple of test contacts who newly claim they work at McDonald's (but one of whom already worked somewhere else)
        Contact c1 = new Contact(LastName='McTestNullAcctNowMcD', AccountId=useful.getDefaultAccountId(), Company_Holding_Spot__c = 'mcdonalds');
        Contact c2 = new Contact(LastName='McTestTgtAcctNowMcD', AccountId=aTgt.Id, Company_Holding_Spot__c = 'Mc. Donalds');
        insert new List<Contact>{c1, c2};
        
        Test.startTest();
        // Call our data-transformation method on the IDs of our two contacts
        ScratchPadClass.copyCHSToAcct(new Set<Id>{c1.Id, c2.Id});
        Test.stopTest();
        
        // Pull a fresh copy of our contacts out of the database
        Map<Id, Contact> csAfter = new Map<Id, Contact>([SELECT Id, AccountId, Account.Name FROM Contact WHERE Id IN (:c1.Id, :c2.Id)]);
        
        // PERFORM TESTS CHECKING FOR DATA QUALITY
        // Since Contact #1 had a generic "No Company Assigned" account to start with, McD's should have propagated into AccountId
        System.assertEquals(aMcD.Id,csAfter.get(c1.Id).AccountId, 'c1 Account Name is ' + csAfter.get(c1.Id).Account.Name);
        // Contact #2 was already working at a real company (Target), don't overwrite w/ McD's.  Needs human review.
        System.assertEquals(aTgt.Id,csAfter.get(c2.Id).AccountId, 'c2 Account Name is ' + csAfter.get(c2.Id).Account.Name);
    }
    
    // A private helper method
    private static void runSetup() {
        if (setupAlreadyRan == FALSE) {
            DefaultTestDataFactory.setUpCustomSettings();
            if (useful==null) { useful = UtilityDefaultInfoOftenNeeded.getInstance(); }
            setupAlreadyRan = TRUE;
        }
        return;
    }
       
}

NOTE: You might notice that I have classes called "DefaultTestDataFactory" and "UtilityDefaultInfoOftenNeeded" whose code isn't shown here. Don't worry about them. All they do that matters is to this code is:

  1. Insert a "No Company Assigned" account into the database (DefaultTestDataFactory) and
  2. Provide an easy way to retrieve the ID of that account (UtilityDefaultInfoOftenNeeded.getInstance().getDefaultAccountId())

The body of ScratchPadClass looks like this:

public class ScratchPadClass {

    public static void copyCHSToAcct(Set<Id> cIds) {
        return;
    }
    
}

When I "Run Test" on "ScratchPadClassTest," the first System.AssertEquals fails with the following error message:

  • Assertion Failed: c1 Account Name is No Company Assigned: Expected: 00738000006UTiPEDC, Actual: 00738000006UTiOEDC

In other words, I expected c1’s Account.Name to change from "No Company Assigned" to "McDonalds," but it didn’t happen.

Well, of course it didn’t happen. ScratchPadClass.copyCHSToAcct() doesn’t even do anything yet!

Now I will go write ScratchPadClass.copyCHSToAcct(). And I’ll know I wrote it correctly when my test passes.

And THAT’s test-driven development!

No comments:

Post a Comment