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 或者 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强制要求的内容,而应该是属于我们写的代码中不可或缺的一部分,希望能重新让你认识到测试类的重要性

如果文中有错误欢迎指正,有问题欢迎留言。

 

posted @ 2018-05-09 16:48  Ricardo.M.Lu  阅读(2954)  评论(2编辑  收藏  举报