在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 或者 TestMyClassName
兩種規范各有優劣,其中 MyClassTest緊隨Apex類之后,便於查詢與修改,TestMyClass則將所有測試類都集中在一起,便於打包。可以根據自己的習慣自行決定。
在測試類的寫法上有兩種選擇
- 用少量測試類覆蓋遠大於測試類的Apex類
- 一個測試類對應一個Apex類
從項目規范上來說,應該一個測試類對應一個Apex類,從而保證不論是后期維護,還是功能變更又或者代碼修改,都能保證代碼的質量並且在部署時更加快捷;但是少量測試類覆蓋多個Apex類同樣也是一種選擇,因為存在Apex類因為功能簡單,專門寫個測試類顯得很“多余”,而且在測試類的交叉覆蓋下,很多Apex類覆蓋率自然就上去了,項目上線迭代的速度能得到很大程度的提高。
兩種寫測試類的優缺點很明顯,少量測試類覆蓋遠大於測試類的Apex類,上線速度快,減少開發量,但是在后期維護乃至二期項目中需要花費更大的時間去處理測試類的問題,如果后來的開發者不熟悉之前的邏輯那需要的時間將更加漫長;一個測試類對應一個Apex類的寫法在項目進行中的時候會占據不少的時間,但是好處就是代碼結構清晰,后期維護的開發者不管熟不熟悉都將能游刃有余的處理之前的內容。
從長遠的角度來說,我更推薦使用后一種,作為程序員,應該花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強制要求的內容,而應該是屬於我們寫的代碼中不可或缺的一部分,希望能重新讓你認識到測試類的重要性
如果文中有錯誤歡迎指正,有問題歡迎留言。