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提供了比较强大的可配置审批流功能,在系统中翻译为“批准过程”.所以需要配置审批时,选择创建 ——> 工作流和批准 ——> 批准过程,然后选择管理批准过程,选择需要配置 ...
随机推荐
- QT--电子时钟
QT--电子时钟 简介: 使用了绘图事件.定时事件,有两个窗口,一个窗口用于设置时间(主窗口),一个是时钟窗口 1.主窗口 menu.h文件: #ifndef MENU_H #define MENU_ ...
- 8 个 Tips 让你更好的进行 Code Review
摘要: Code Review 可以提高代码质量. 原文:Elevenbeans 作者:前端小智 Fundebug经授权转载,版权归原作者所有. 原文地址:https://kellysutton.co ...
- sql server使用sp_executesql返回拼接字符串里面的输出参数
问题: 今天一同事请教博主,他拼接了一个语句,select表格形式数据,然后使用@@rowcount获取到行数. 但他又有这样特别的需求:想只获取行数而不返回表格数据结果,因为是while循环,不想返 ...
- crontab -e 报错(E518: Unknown option: foldenable)
crontab 默认编辑器为vi,不支持foldenable #crontab -e Error detected while processing /root/.vimrc: line : E518 ...
- Eureka集群
Eureka集群搭建 高可用集群配置 当注册中心扛不住高并发的时候,这时候 要用集群来扛: 普通操作 我们再新建两个module microservice-eureka-server-2002 m ...
- CF#603 Div2
差不多半年没打cf,还是一样的菜:不过也没什么,当时是激情,现在已是兴趣了,开心就好. A Sweet Problem 思维,公式推一下过了 B PIN Codes 队友字符串取余过了,结果今天早上一 ...
- luoguP1871 对撞机【赛后第一题
题面 题目描述 在2312年,宇宙中发现了n台巨型对撞机,这些对撞机分别用1-n的自然数标识.科学家们不知道启动这些对撞机会发生什么危险事故,所以这些机器,刚开始都是出于关闭的状态. 随着科学家们的研 ...
- js全局属性/函数
全局函数 eval () isFinite () 检查某个值是否为有穷大的数 isNaN () 检查某个值是否是数字 返回true或fa ...
- java之匿名内部类
Person.java package insof; public class Person extends Object{ String name; static int age; public P ...
- Exe4j 打包: this executable was created with an evaluation version of exe4j
异常 this executable was created with an evaluation version of exe4j 异常.png 问题原因 当前打包使用exe4j未授权 解决方法 ...