Salesforce 开发整理(一)测试类最佳实践
在Sales force开发中完善测试类是开发者必经的一个环节,代码的部署需要保证至少75%的覆盖率,那么该如何写好测试类呢。
测试类定义格式如下:
@isTest
private class MyTestClass {
@isTest static void myTest() {
// code_block
}
static testMethod void testName() {
// code_block
}
}
测试类允许声明为private或者public,一般来说,单元测试的测试类都声明为private,如果是DateFactory的话则声明为public,其中测试类不占用计算在APEX代码的限制中。
测试类的命名规范一般有两种:MyClassTest 或者 TestMyClassName
两种规范各有优劣,其中 MyClassTest紧随Apex类之后,便于查询与修改,TestMyClass则将所有测试类都集中在一起,便于打包。可以根据自己的习惯自行决定。
在测试类的写法上有两种选择
- 用少量测试类覆盖远大于测试类的Apex类
- 一个测试类对应一个Apex类
从项目规范上来说,应该一个测试类对应一个Apex类,从而保证不论是后期维护,还是功能变更又或者代码修改,都能保证代码的质量并且在部署时更加快捷;但是少量测试类覆盖多个Apex类同样也是一种选择,因为存在Apex类因为功能简单,专门写个测试类显得很“多余”,而且在测试类的交叉覆盖下,很多Apex类覆盖率自然就上去了,项目上线迭代的速度能得到很大程度的提高。
两种写测试类的优缺点很明显,少量测试类覆盖远大于测试类的Apex类,上线速度快,减少开发量,但是在后期维护乃至二期项目中需要花费更大的时间去处理测试类的问题,如果后来的开发者不熟悉之前的逻辑那需要的时间将更加漫长;一个测试类对应一个Apex类的写法在项目进行中的时候会占据不少的时间,但是好处就是代码结构清晰,后期维护的开发者不管熟不熟悉都将能游刃有余的处理之前的内容。
从长远的角度来说,我更推荐使用后一种,作为程序员,应该花60%的时间去思考逻辑以及最优的实现方式,然后花20%的时间去实现,最后花费20%的时间进行包括业务场景在内的测试。
写好的测试类能尽可能的验证你写的代码逻辑的正确性,提升你的代码质量,即使修改了之前的逻辑,执行你的测试方法也能知道有没有影响你之前的逻辑,方便进行回归测试。
说了这么多,来看一个示例吧。
/*********
*
* @Author:ricardo
* @Time:2018-05-09
* @Function 华氏温度温度转换摄氏温度
*/
public class TemperatureConverter {
// Takes a Fahrenheit temperature and returns the Celsius equivalent.
public static Decimal FahrenheitToCelsius(Decimal fh) {
Decimal cs = (fh - 32) * 5/9;
return cs.setScale(2);
}
}
测试类如下
@isTest
private class TestTemperatureConverter {
//正常输入
@isTest static void testWarmTemp() {
Decimal celsius = TemperatureConverter.FahrenheitToCelsius(70);
System.assertEquals(21.11,celsius);
}
//临界值
@isTest static void testFreezingPoint() {
Decimal celsius = TemperatureConverter.FahrenheitToCelsius(32);
System.assertEquals(0,celsius);
}
//异常值
@isTest static void testBoilingPoint() {
Decimal celsius = TemperatureConverter.FahrenheitToCelsius(212);
System.assertEquals(100,celsius,'这温度超限啊');
}
//反向测试
@isTest static void testNegativeTemp() {
Decimal celsius = TemperatureConverter.FahrenheitToCelsius(-10);
System.assertEquals(-23.33,celsius);
} }
可以看到,测试类其实至少应该测试正常的业务流程,同时还包括异常/临界/反向三种情况。
默认情况下,Sales force不运行测试类访问正式的ORG数据,除了某些极端情况,比如你要测试报表相关的Apex类,可以使用@isTest(SeeAllData=true)的注解来访问org中的数据,不过一般我们是不建议这样操作的。那么针对这样的情况,也就是需要重复创建一些测试数据的我们怎么做呢,答案是使用Test Utility Classes创建测试数据,也就是创建一个DateFactory类专门提供测试数据,从而避免重复创建数据的问题,也能帮助快速修复一些因为数据不全引起的测试代码报错的问题。
下面看一个Account的trigger示例
/***********
*
* 测试类最佳实践-示例二
*
*/
trigger AccountDeletion on Account (before delete) {
// 如果关联业务机会则禁止删除Account
for (Account a : [SELECT Id FROM Account
WHERE Id IN (SELECT AccountId FROM Opportunity) AND
Id IN :Trigger.old]) {
Trigger.oldMap.get(a.Id).addError(
'Cannot delete account with related opportunities.');
}
}
准备DataFactory
@isTest
public class TestDataFactory {
public static List<Account> createAccountsWithOpps(Integer numAccts, Integer numOppsPerAcct) {
List<Account> accts = new List<Account>();
for(Integer i=0;i<numAccts;i++) {//numAccts:创建的Account数量
Account a = new Account(Name='TestAccount' + i);
accts.add(a);
}
insert accts; List<Opportunity> opps = new List<Opportunity>();
for (Integer j=0;j<numAccts;j++) {
Account acct = accts[j];
// 遍历每一个Account,都关联创建一个对应的业务机会
for (Integer k=0;k<numOppsPerAcct;k++) {
opps.add(new Opportunity(Name=acct.Name + ' Opportunity ' + k,
StageName='Prospecting',
CloseDate=System.today().addMonths(1),
AccountId=acct.Id));
}
}
// 插入关联了Accout的业务机会
insert opps;
return accts;
}
}
创建测试类
@isTest
private class TestAccountDeletion {
@isTest static void TestDeleteAccountWithOneOpportunity() {
// 测试类初始准备测试数据
// 创建一个关联业务机会的Account,并准备删除掉
//Account acct = new Account(Name='Test Account');
//insert acct;
//
// 测试类初始准备测试数据使用DataFactory
// 创建一个Account,关联一个业务机会
Account[] accts = TestDataFactory.createAccountsWithOpps(1,1); // 测试开始
Test.startTest();
Database.DeleteResult result = Database.delete(accts[0], false);
Test.stopTest();
// 校验
// 应该被禁止删除(关联了业务机会)
// 返回的应该是禁止删除的错误提示
System.assert(!result.isSuccess());
System.assert(result.getErrors().size() > 0);
System.assertEquals('不能删除关联业务机会的Account',
result.getErrors()[0].getMessage());
} @isTest static void TestDeleteAccountWithNoOpportunities() {
// 测试初始化
// 创建一个Account,不关联业务机会的
Account[] accts = TestDataFactory.createAccountsWithOpps(1,0); // 测试开始
Test.startTest();
Database.DeleteResult result = Database.delete(accts[0], false);
Test.stopTest();
// 顺利删除
System.assert(result.isSuccess());
} @isTest static void TestDeleteBulkAccountsWithOneOpportunity() {
// 初始化测试数据
// 创建200个Account,创建一个业务机会
Account[] accts = TestDataFactory.createAccountsWithOpps(200,1); // 测试开始
Test.startTest();
Database.DeleteResult[] results = Database.delete(accts, false);
Test.stopTest();
// 校验结果
// 批量删除下,有Account是不能被删除的,数据需要回滚
// 所以我们会得到一个错误的返回值
for(Database.DeleteResult dr : results) {
System.assert(!dr.isSuccess());
System.assert(dr.getErrors().size() > 0);
System.assertEquals('不能删除关联业务机会的Account',
dr.getErrors()[0].getMessage());
}
} @isTest static void TestDeleteBulkAccountsWithNoOpportunities() {
// 初始化
// 创建批量不关联业务机会的数据
Account[] accts = TestDataFactory.createAccountsWithOpps(200,0); // 测试开始
Test.startTest();
Database.DeleteResult[] results = Database.delete(accts, false);
Test.stopTest();
// 顺利批量删除
for(Database.DeleteResult dr : results) {
System.assert(dr.isSuccess());
}
} }
可以看到,我们在完善自己的测试类时,不应该仅仅关注代码的覆盖率,同时也应该注意到对各种正常,异常,临界诸多可能的测试,好的测试能保证代码的质量,尤其需要注意的是经常会被忽略的批量数据处理的问题,在写测试类的时候一般追求覆盖率写出来的测试方法都是模拟用户在界面上的单记录操作,很少会考虑批量数据的测试情况,而在实际的系统使用中是很可能遇到而且容易引起问题的地方。
测试类不应该是Salesforce强制要求的内容,而应该是属于我们写的代码中不可或缺的一部分,希望能重新让你认识到测试类的重要性
如果文中有错误欢迎指正,有问题欢迎留言。
Salesforce 开发整理(一)测试类最佳实践的更多相关文章
- Salesforce 开发整理(五)代码开发最佳实践
在Salesforce项目实施过程中,对项目代码的维护可以说占据极大的精力,无论是因为项目的迭代,还是需求的变更,甚至是项目组成员的变动,都不可避免的需要维护之前的老代码,而事实上,几乎没有任何一个项 ...
- [翻译] API测试的最佳实践 - 介绍
API测试的最佳实践 - 介绍 在上一篇“是什么让API测试很叼”一文中,我们讨论API与其他形式的软件测试的差异.部分是因为API之间的通信压根就没考虑让你能读懂,纯粹是为了方便计算机之间的交互而设 ...
- Salesforce 开发整理(八)PDF打印相关
一:基础设置 Salesforce中的PDF页面本质上还是Visualforce[简称VF]页面,所以只需要给VF页面加上一个属性[renderAs="pdf"] 即可生成一个PD ...
- Java Servlet开发的轻量级MVC框架最佳实践
在Servlet开发的工程实践中,为了减少过多的业务Servlet编写,会采用构建公共Servlet的方式,通过反射来搭建轻量级的MVC框架,从而加快应用开发. 关于Servlet开发的基础知识,请看 ...
- 构建高效Presubmit卡点,落地测试左移最佳实践
樊登有一节课讲的挺有意思,说中国有个组织叫绩效改进协会,专门研究用技控代替人控的事情.其用麦当劳来举例子,他说麦当劳其实招人标准很低,高中文凭就可以,但是培养出来的人,三五年之后,每一个都是大家争抢的 ...
- Salesforce 开发整理(十)项目部署总结
项目部署顺序 全局值集 小组 自定义字段-对象-设置(SF1 紧凑布局要和记录类型在这里要一起部署) 邮件模板-静态资源 角色 工作流-流定义(包含进程生成器) 批准过程 开发部署<Apex类, ...
- Salesforce 开发整理(二)报表开发学习
Salesforce提供了强大的报表功能,支持表格.摘要.矩阵以及结合共四种形式,本文探讨在站在开发的角度要如何理解报表. 一:查询报表基本信息报表在Sales force中是Report对象,基本的 ...
- Salesforce 开发整理(九) 开发中使用的一些小技巧汇总[持续更新]
1.查询一个对象下所有字段 当需要查询一个对象所有字段进行复制或其他操作,可以使用一段拼接的语句来查询 String query = 'select '; for(String fieldApi : ...
- Salesforce 开发整理(七)配置审批流
salesforce提供了比较强大的可配置审批流功能,在系统中翻译为“批准过程”.所以需要配置审批时,选择创建 ——> 工作流和批准 ——> 批准过程,然后选择管理批准过程,选择需要配置 ...
随机推荐
- Saltstack_使用指南09_远程执行-编写执行模块
1. 主机规划 salt 版本 [root@salt100 ~]# salt --version salt (Oxygen) [root@salt100 ~]# salt-minion --versi ...
- 将 云数据库MongoDB(阿里云)物理备份文件下载恢复至本地自建数据库 遇到的5个问题
有时候我们可能需要将云上数据库下载到本地,下面是我们在操作MongoDB数据库时遇到的五个小问题. 其实现在RDS的 帮助文档 写的都比较详细了,大家在第一次操作时,可以细读一下,避免一些不必要的问题 ...
- oracle 查询两个字段值相同的记录
select A.* from tb_mend_enrol A, (select A.Typeid, A.address from tb_mend_enrol A group by A.Typeid, ...
- linux bash基础特性
使用history命令,取得命令历史,当bash进程结束后,会把命令历史存放到文件中,下次开机还能看到命令历史. 定制history:通过设置环境变量,来定制history 环境变量$HISTSIZE ...
- windows命令行pip报错解决的方法
今天在新电脑安装python,发现pip无效了,于是乎百度了很多方法,发现原因是pip升级导致的 解决办法,卸载pip重新安装 可以首先执行 python -m ensurepip 然后执行 py ...
- C# 让你解决方案乱七八糟的DLL放入指定文件夹
嗯,大家的解决方案可能会有许多dll,这样不美观,而且也麻烦. 很多小白都不知道如何将这些dll放到如自己程序的bin文件夹下. 本渣今天来试着将dll复制到指定的文件夹下~ 比如我之前做的一个Win ...
- R-6 线性回归模型流程
本节内容: 0:小知识 1:新数据要如何进行分析 2:第二步骤:理解数据 3:第三步骤:相关分析 4:特殊点 0:小知识 0.1:我们说对分析一个数据一般是分步骤的:那么我们可以对其中的步骤进行打标签 ...
- js获取input checkbox的选中值
HTML代码: <form action="/test/action" method="get"> <input type="che ...
- C语言程序设计100例之(15):除法算式
例15 除法算式 问题描述 输入正整数n(2≤n≤68),按从小到大输出所有形如abcde/fghi=n的表达式.其中a~i为1~9的一个排列. 输入格式 每行为一个正整数n (n <= 1 ...
- javascript es6 Promise 异步同步的写法(史上最简单的教程了)
1 来个简单的例子 var p = new Promise(function(resolve, reject){ //做一些异步操作 setTimeout(function(){ console.lo ...