Salesforce 開發整理(一)測試類最佳實踐

在Sales force開發中完善測試類是開發者必經的一個環節,代碼的部署須要保證至少75%的覆蓋率,那麼該如何寫好測試類呢。程序員

測試類定義格式以下:單元測試

 1 @isTest
 2 private class MyTestClass {
 3     @isTest static void myTest() {
 4         // code_block
 5     }     
 6     static testMethod void testName() {
 7     // code_block
 8     }
 9 }

測試類容許聲明爲private或者public,通常來講,單元測試的測試類都聲明爲private,若是是DateFactory的話則聲明爲public,其中測試類不佔用計算在APEX代碼的限制中。測試

測試類的命名規範通常有兩種:MyClassTest 或者 TestMyClassNameui

兩種規範各有優劣,其中 MyClassTest緊隨Apex類以後,便於查詢與修改,TestMyClass則將全部測試類都集中在一塊兒,便於打包。能夠根據本身的習慣自行決定。spa

在測試類的寫法上有兩種選擇code

  • 用少許測試類覆蓋遠大於測試類的Apex類
  • 一個測試類對應一個Apex類

從項目規範上來講,應該一個測試類對應一個Apex類,從而保證不管是後期維護,仍是功能變動又或者代碼修改,都能保證代碼的質量而且在部署時更加快捷;可是少許測試類覆蓋多個Apex類一樣也是一種選擇,由於存在Apex類由於功能簡單,專門寫個測試類顯得很「多餘」,並且在測試類的交叉覆蓋下,不少Apex類覆蓋率天然就上去了,項目上線迭代的速度能獲得很大程度的提升。blog

兩種寫測試類的優缺點很明顯,少許測試類覆蓋遠大於測試類的Apex類,上線速度快,減小開發量,可是在後期維護乃至二期項目中須要花費更大的時間去處理測試類的問題,若是後來的開發者不熟悉以前的邏輯那須要的時間將更加漫長;一個測試類對應一個Apex類的寫法在項目進行中的時候會佔據很多的時間,可是好處就是代碼結構清晰,後期維護的開發者無論熟不熟悉都將能遊刃有餘的處理以前的內容。ci

從長遠的角度來講,我更推薦使用後一種,做爲程序員,應該花60%的時間去思考邏輯以及最優的實現方式,而後花20%的時間去實現,最後花費20%的時間進行包括業務場景在內的測試。開發

寫好的測試類能儘量的驗證你寫的代碼邏輯的正確性,提高你的代碼質量,即便修改了以前的邏輯,執行你的測試方法也能知道有沒有影響你以前的邏輯,方便進行迴歸測試。部署

說了這麼多,來看一個示例吧。

 1 /*********
 2     * 
 3     *  @Author:ricardo
 4     *  @Time:2018-05-09
 5     *  @Function 華氏溫度溫度轉換攝氏溫度
 6     */
 7 public class TemperatureConverter {
 8     // Takes a Fahrenheit temperature and returns the Celsius equivalent.
 9     public static Decimal FahrenheitToCelsius(Decimal fh) {
10         Decimal cs = (fh - 32) * 5/9;
11         return cs.setScale(2);
12     }
13 }

測試類以下

 1 @isTest
 2 private class TestTemperatureConverter {
 3     //正常輸入
 4     @isTest static void testWarmTemp() {
 5         Decimal celsius = TemperatureConverter.FahrenheitToCelsius(70);
 6         System.assertEquals(21.11,celsius);
 7     }
 8     //臨界值
 9     @isTest static void testFreezingPoint() {
10         Decimal celsius = TemperatureConverter.FahrenheitToCelsius(32);
11         System.assertEquals(0,celsius);
12     }
13     //異常值
14     @isTest static void testBoilingPoint() {
15         Decimal celsius = TemperatureConverter.FahrenheitToCelsius(212);        
16         System.assertEquals(100,celsius,'這溫度超限啊');
17     } 
18     //反向測試
19     @isTest static void testNegativeTemp() {
20         Decimal celsius = TemperatureConverter.FahrenheitToCelsius(-10);
21         System.assertEquals(-23.33,celsius);
22     }
23       
24 }

能夠看到,測試類其實至少應該測試正常的業務流程,同時還包括異常/臨界/反向三種狀況。

默認狀況下,Sales force不運行測試類訪問正式的ORG數據,除了某些極端狀況,好比你要測試報表相關的Apex類,可使用@isTest(SeeAllData=true)的註解來訪問org中的數據,不過通常咱們是不建議這樣操做的。那麼針對這樣的狀況,也就是須要重複建立一些測試數據的咱們怎麼作呢,答案是使用Test Utility Classes建立測試數據,也就是建立一個DateFactory類專門提供測試數據,從而避免重複建立數據的問題,也能幫助快速修復一些由於數據不全引發的測試代碼報錯的問題。

下面看一個Account的trigger示例

 1 /***********
 2     *
 3     *  測試類最佳實踐-示例二
 4     *
 5     */
 6 trigger AccountDeletion on Account (before delete) {
 7     // 若是關聯業務機會則禁止刪除Account
 8     for (Account a : [SELECT Id FROM Account
 9                      WHERE Id IN (SELECT AccountId FROM Opportunity) AND
10                      Id IN :Trigger.old]) {
11         Trigger.oldMap.get(a.Id).addError(
12             'Cannot delete account with related opportunities.');
13     }
14 }

準備DataFactory

 1 @isTest
 2 public class TestDataFactory {
 3     public static List<Account> createAccountsWithOpps(Integer numAccts, Integer numOppsPerAcct) {
 4         List<Account> accts = new List<Account>();        
 5         for(Integer i=0;i<numAccts;i++) {//numAccts:建立的Account數量
 6             Account a = new Account(Name='TestAccount' + i);
 7             accts.add(a);
 8         }
 9         insert accts;
10         
11         List<Opportunity> opps = new List<Opportunity>();
12         for (Integer j=0;j<numAccts;j++) {
13             Account acct = accts[j];
14             //  遍歷每個Account,都關聯建立一個對應的業務機會
15             for (Integer k=0;k<numOppsPerAcct;k++) {
16                 opps.add(new Opportunity(Name=acct.Name + ' Opportunity ' + k,
17                                        StageName='Prospecting',
18                                        CloseDate=System.today().addMonths(1),
19                                        AccountId=acct.Id));
20             }
21         }
22         //  插入關聯了Accout的業務機會
23         insert opps;        
24         return accts;
25     }
26 }

建立測試類

 1 @isTest
 2 private class TestAccountDeletion {
 3     @isTest static void TestDeleteAccountWithOneOpportunity() {
 4         // 測試類初始準備測試數據
 5         // 建立一個關聯業務機會的Account,並準備刪除掉
 6         //Account acct = new Account(Name='Test Account');
 7         //insert acct;
 8         //
 9         // 測試類初始準備測試數據使用DataFactory
10         // 建立一個Account,關聯一個業務機會
11         Account[] accts = TestDataFactory.createAccountsWithOpps(1,1);
12                 
13         // 測試開始
14         Test.startTest();
15         Database.DeleteResult result = Database.delete(accts[0], false);
16         Test.stopTest();
17         // 校驗 
18         // 應該被禁止刪除(關聯了業務機會)
19         // 返回的應該是禁止刪除的錯誤提示
20         System.assert(!result.isSuccess());
21         System.assert(result.getErrors().size() > 0);
22         System.assertEquals('不能刪除關聯業務機會的Account',
23                              result.getErrors()[0].getMessage());
24     }
25     
26      @isTest static void TestDeleteAccountWithNoOpportunities() {
27         // 測試初始化
28         // 建立一個Account,不關聯業務機會的
29         Account[] accts = TestDataFactory.createAccountsWithOpps(1,0);
30         
31         // 測試開始
32         Test.startTest();
33         Database.DeleteResult result = Database.delete(accts[0], false);
34         Test.stopTest();
35         // 順利刪除
36         System.assert(result.isSuccess());
37     }
38     
39     @isTest static void TestDeleteBulkAccountsWithOneOpportunity() {
40         // 初始化測試數據
41         // 建立200個Account,建立一個業務機會
42         Account[] accts = TestDataFactory.createAccountsWithOpps(200,1);
43         
44         // 測試開始
45         Test.startTest();
46         Database.DeleteResult[] results = Database.delete(accts, false);
47         Test.stopTest();
48         // 校驗結果
49         // 批量刪除下,有Account是不能被刪除的,數據須要回滾
50         // 因此咱們會獲得一個錯誤的返回值
51         for(Database.DeleteResult dr : results) {
52             System.assert(!dr.isSuccess());
53             System.assert(dr.getErrors().size() > 0);
54             System.assertEquals('不能刪除關聯業務機會的Account',
55                                  dr.getErrors()[0].getMessage());
56         }
57     }
58     
59     @isTest static void TestDeleteBulkAccountsWithNoOpportunities() {
60         // 初始化
61         // 建立批量不關聯業務機會的數據
62         Account[] accts = TestDataFactory.createAccountsWithOpps(200,0);
63         
64         // 測試開始
65         Test.startTest();
66         Database.DeleteResult[] results = Database.delete(accts, false);
67         Test.stopTest();
68         // 順利批量刪除
69         for(Database.DeleteResult dr : results) {
70             System.assert(dr.isSuccess());
71         }
72     }
73     
74 }

能夠看到,咱們在完善本身的測試類時,不該該僅僅關注代碼的覆蓋率,同時也應該注意到對各類正常,異常,臨界諸多可能的測試,好的測試能保證代碼的質量,尤爲須要注意的是常常會被忽略的批量數據處理的問題,在寫測試類的時候通常追求覆蓋率寫出來的測試方法都是模擬用戶在界面上的單記錄操做,不多會考慮批量數據的測試狀況,而在實際的系統使用中是極可能遇到並且容易引發問題的地方。

測試類不該該是Salesforce強制要求的內容,而應該是屬於咱們寫的代碼中不可或缺的一部分,但願能從新讓你認識到測試類的重要性

若是文中有錯誤歡迎指正,有問題歡迎留言。

相關文章
相關標籤/搜索