Salesforce的TestClass

虽然Salesforce从业人员应该尽量使用标准功能来实现需求,
但仍不可避免的要用到代码。

标准功能的话,Salesforce会自己负责质量(虽然Bug频发。。。),
对于自定义功能,Salesforce则制定了质量标准,
比如,如果总体代码覆盖率不到75%,无法Deploy。

一般的Apex Code,就像那些出现在Trigger里,出现在Controller里的代码,
测试类都很好写,就按照
1. 准备测试数据
2. 执行业务逻辑
3. 断言执行结果
按套路打就行了。

除此之外,另一些apex code,需要特别的测法。
Salesforce作为CRM系统,无法避免的要与其他系统进行数据交互。
大部分情况,我们只需要把salesforce的标准集成文档和权限已经配置妥当的账号,提供给对方就好,
但是,当我们需要将业务封装起来的时候,就需要自己建WebService。
况且,Salesforce主动调用外部系统接口的时候,又要区分是Rest还是SOAP。

对于Apex Rest Callout可以使用下面三种方式(代码示例点击链接)
1. HttpCalloutMock
比较常见的写法,有极大的可拓展性(毕竟连响应结果都要自己Code进去)。
2. StaticResourceCalloutMock
LoadData是一对好兄弟,都是在Test Class中利用StaticResource,可以很好的做到测试数据与测试代码分离。
3. MultiStaticResourceCalloutMock
StaticResourceCalloutMock的复数URL版。

对于Apex SOAP Callout,Salesforce也提供了一种方式。
1. WebServiceMock

以上是Callout的测试类写法,原理均是构造一个Mock类,然后提供一个假的response。
你总不能期待每次跑测试类的时候,让人家集成方的服务器真的去响应你吧?

那么Callin呢。
首先,处理Callin的Apex Class一定是WebService。
WebService也分为Rest Webservice和SOAP WebService。
对于Rest WebService,Salesforce并没有提供类似与Mock类的工具类,所以我们直接去构造上下文环境。
在实际情况种,上下文环境是由系统进行构造的,这里我们代替系统。

        .................
        RestRequest request = new RestRequest();
        request.requestURI = 'https://[InstanceName].salesforce.com/services/apexrest/xxxxxx/';
        request.httpMethod = 'GET';
        RestContext.request = request;
        /** Call your WebService like below **/
        MyWebService.MyFoo();
        .................

对于SOAP Webservice就简单多了。因为不需要处理上下文环境的参数等内容,直接处理方法中的参数就好,
所以就当作普通的Apex Code处理就可以,不需要特殊的操作。

除此之外。

虽然目前我还没见有人用过,但是Salesforce还是提供了一个Stub接口,用来做Mock框架。
它的好处是可以实现单元测试的完全解耦,对各个小模块进行单独的测试。
就目前市面上各个大公司的状况来看。。。能好好写个断言就不错了。(捂脸)(尴尬)

Salesforce的Rollback与SavePoint

之前对于Salesforce的RollBack机制没有进行深入的思考。
那天,闲来无事,有人问了我一个问题。

问说,下面的代码在匿名快执行完之后插进去了几条数据。

ObjectA__c objA = new ObjectA__c();
objA.Name = "TestA";
insert objA;

List<ObjectB__c> objBList = new List<ObjectB__c>
for(Integer i = 0; i < 200; i++) {
    ObjectB__c objB = new ObjectB__c();
    objB.Name = "TestB" + i;
    insert ojbB;
}

我大概扫了一眼,问的是Governor Limit嘛,答案是150,送分题,不谢。
但又隐约的觉得不大对劲,既然拿来问我,肯定有坑。
立马打开Developer Console试了一下。
看到了结果倒吸了一口冷气。。。。。。。。0,一条都没插进去。全都RollBack了?

咦?不对啊,那一定是匿名块做了特殊的设置。试试Controller。
要不然我以前在Controller里写个什么劲儿的SavePoint。
VF:

<apex:page controller="TestRollbackClass">
    <apex:form>
    	<apex:commandButton action="{!save}" value="Button"/>  
    </apex:form>
</apex:page>

Apex Class:

public class TestRollbackClass {

    public void save() {     
        ObjectA__c oa = new ObjectA__c();
        oa.Name = 'TestA';
        insert oa;

	ObjectB__c ob = new ObjectB__c();
        ob.Name = 'testB';
        insert ob;
    }
}

我在ObjectB上加了一个Validation Rule,保证新建ObjectB记录的一定会报错。
这次总该如预期一般只有ObjectA被插进去了吧。

结果一跑,唉? 还是都RollBack了?
瞬间怀疑人生。既然都自动RollBack了,那我以前为什么必须写SavePoint?
去翻了下Release Note,也没找到相关的变动。

没办法,一定是写法问题,我试着按照Coding Standard写一次

public class TestRollbackClass {

    public void save() {
	ObjectA__c oa = new ObjectA__c();
	oa.Name = 'Test2';
        insert oa;
        try {
            ObjectB__c ob = new ObjectB__c();
        	ob.Name = 'test1';
        	insert ob;
        } catch(Exception e) {
            ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.Error, e.getMessage()));
        }
    }
}

再次执行,第一条数据ObjectA居然插进去了。
我瞬间明白了点什么。

Salesforce的机制应该是这样的,
如果Transaction被异常中断,则Rollback全部的数据变动。
如果异常被捕获,则已经正常commit的数据变动会落实。出错的部分无效。

按照开发潜规则,应该展现给User更Friendly的错误信息,而不是可怕的Trace Log信息。
所以我以前的公司要求捕获所有可能的异常并替换成友好的错误信息。
这样的话,Transaction一定不会被异常中断,所以自动全部Rollback的机制从来没有启用过。
那么为了防止在出错时插入脏数据,Salesforce提供了SavePoint。
用法如下:

public class TestRollbackClass {

    public void save() {
        SavePoint sp = Database.setSavepoint();
		ObjectA__c oa = new ObjectA__c();
		oa.Name = 'Test2';
        insert oa;
        try {
            ObjectB__c ob = new ObjectB__c();
        	ob.Name = 'test1';
        	insert ob;
        } catch(Exception e) {
            Database.rollback(sp);
            ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.Error, e.getMessage()));
        }
    }
}

如果ObjectB插入出错,已insert的ObjectA也会回滚。达到目的。

Salesforce的字段类型转换

如果维护/接手别人的烂摊子,
有很大几率碰到字段类型转换问题。

Salesforce的字段类型转换并不是100%安全。
如果符合下列规则,则会丢失所有数据。

Date -> Datetime
DateTime -> Date
Any -> Percent
Any -> Number
Any -> Currency
Checkbox -> Any
MultiPicklist -> Any
Any -> MultiPicklist
PickList -> multiPickList value retains if exsited in multipicklist
AutoNumber -> Any(Exclude Text)
Any (exclude Text) -> AutoNumber
Text -> PickList
TextArea(Long) -> Any(exclude Email, phone, Text, Text Area, URL)

常规操作顺序,
应该先在sandbox上演练一下,
确认结果与预期一致。
然后,
先将生产环境的数据备份,
再在生产环境上进行实际操作。

官方文档
https://help.salesforce.com/articleView?id=notes_on_changing_custom_field_types.htm&type=5

Salesforce关于Debug

做Salesforce开发,最让人头疼的事情,莫过于Debug。

不同于Java开发,已经有了一系列成熟的Debug工具,
Salesforce开发者最常用的手段只有输出log,就如同Javascript开发一样。
不过随着现代浏览器的兴起,就算是Javascript开发也可以debug了。

对于一个老手来说,添加System.debug()然后查log,虽然已经足够。
但是,面对巨型transaction的时候,就要面临Debug Log的限制
1. log总体大小不能超过3MB,否则随机截断(不管官网怎么说,我觉得是随机的,囧)
2. log只能存留七天
3. 15分钟内产生的log不能超过250MB

为了回避此问题,Salesforce提供了Class和Trigger的Debug Log级别自定义功能。官网点此
启用这个功能,可以在Class和Trigger的详细页面,找到TraceFlag,在此设定需要的Debug log级别,(一般全开最详细,或者全关)
自定义完成后,transaction中class和trigger产生log时的级别设定,就会覆盖默认的debug log级别设定。
使用此功能可以极大的减少不需要的log输出,从而避免被随机截断。

但是,System.debug必然不是万能的。

所以在DeveloperConsole中,Salesforce给开发者提供了CheckPoint功能,注意,不是BreakPoint,是CheckPoint。
Checkpoint不同于BreakPoint那样可以互动。只提供了信息查看功能。
就是说,在代码中打上Checkpoint之后,程序执行并不会暂停,而是执行到结束,但Salesforce会将Checkpoint标记位置的内存状态保留下来供开发者查看。
需要值得注意的是,Checkpoint全局只能打5个。

如果代码真的复杂到checkpoint和Debug log都无法搞定,
Salesforce提供了一个真正的Debug工具——Apex Debugger
有了Apex Debugger,Salesforce开发者也可以像Java程序员一样debug了。
不过越强大的武器,限制越多。
Apex Debugger只提供了一个免费的License,并且是全Org通用,就说Production和旗下所有sandbox合起来同时只能有一个人用。

不管使用哪种Debug方式,都应该从设计伊始就避免设计出如此巨大的自定义功能。

Salesforce的Sharing Rule中Criteria Rule如何判断空值

某日,客户提了一个需求,
说想把某个字段是非空的数据Share给另一个部门。

看起来是个很简单的需求,
就是一个Sharing Rule的事儿。

然后,立马新建了一个Sharing criteria rule,
设定条件为[ 字段 not equals to 空值 ],保存。唉?报错了。
那么空格呢?也不行。

Error: Empty value

难道,Sharing Rule不能判断空值?
正在百思不得其解之时,经高人指点,说空值的位置写成[“”]就可以了。

赶紧试了一下,果然成功了。

保存后的效果是这样的。

Salesforce如何用Import Wizard将空值插入PicklistValue字段

如果Picklist Value没有定义为必填字段。
则多出一个–None–选项可以选,
在DB里存为空。

我们知道Dataloader默认会无视所有值为空的字段,
除非我们在设置里将[Insert Null Values]选中。

但是在Import Wizard中并没有提供类似的选项。
如果尝试将字段的值留空,一样会被无视,空格等一切空白字符也会被无视。

解决方法是,将想设置成空的字段的值改为[#N/A]。
之后再次导入就可以了。

Salesforce如何查看RecordType的Assignment

我发现Salesforce只提供了Profile-RecordType-Layout三角关系(Assignment)的查看页面。
并没有提供查看某个RecordType都有哪些Profile可以创建的视角。
只能一个一个Profile去点开。

思来想去,如果不用第三方工具的话,只能直接去搜Metadata了。

首先用ForceIDE或者其他方式获取Profile的完整MetaData。

然后,打开全局检索,勾选启用正则表达式。
然后用下面的字符串搜索全部的Profile
<recordType>[ObjectName].[RecordType Name]</recordType>\n.*true

搜出的结果就是能创建这个RecordType的Profile了。

也许将来能够像FLS一样,以recordType的视角直接查看Profile的Assignment情况。

Salesforce的Tag(标签)-一天一个标准功能系列

这两天有小盆有提到了一个需求,
说客户一直在用MacBook,
MacOS提供了一个标签功能,客户可以将一组散落在不同位置的,不同类型的文件,打上同一个标签,
之后可以很方便的管理同一标签的文件。
所以客户也想在Salesforce里面使用这种功能。
问我怎么办才好。

我问他,“你想怎么实现呢”
“我想,首先要确认都有哪些obj需要被打标签”
“很好,然后呢”
“然后,我可以在这些obj上都加上一个自定义字段,叫Tag,然后在所有的Layout上把这个Tag字段放出来。”
“唔。。。可以实现,但是,用户怎么查看都有哪些数据被打了相同的Tag呢?而且,如果多个用户都需要打Tag,怎么保证互不干扰,并且互相保密呢?”
“啊。。。。我想想,查看的话,我可以写一个VF,通过查询把所有的表都查一遍,多个用户就麻烦了。。。我想想。。。”
(过了半个小时)
我问道,“有想法了没?”
“有了!我不加字段了,那样不科学!”
“很好,那你想怎么做呢?”
“首先,我建一张表叫Tag__c。然后关联所有需要打Tag的obj。之后Sharing Rule设置成private。查看Tag的时候,用ListView进行过滤,把所有相同Tag名字的数据都显示出来。(得意)”
“那打标签的动作怎么解决。就是打算如何在数据上点一下就加上一个标签。”
“我打算用Quick Action或者自定义Button,将它们添加到所有的Layout上,这样在Detail画面直接点一下就能打上标签了。”
“那如果有个标签我想给所有人看呢?(坏笑)”
“那样的话我得用Sharing Rule,将Share to All Flag为True的数据Share给所有人。”
“累不?”
“累。。。。”
“你听说过标准Tag功能吗?”
“我就知道!!!”

标签,无论是在现实世界还是计算机世界,都是管理资源的良好手段。
所以Salesforce也原生提供了Tag功能,我们要做的,就是将其开启就好。
官方文档,点击这里

开启之后,查看标签的效果是这样的

点击[Personal Tags]之后

开启之后,在数据的详细画面上,可以点击右上的[Edit Tag]添加标签。

不过,Tag也不是无限打的,是有个数限制的

开启的方法为,
Setup -〉Build -〉Customize -〉 Tags -〉Tag Setting
开启Personal Tag之后,可以选择是否在Report等之上使用Tag,与显示Tag功能的PageLayout。

需要留意的是,Salesforce官方文档提到了两种Tag,分别是Personal Tag和Public Tag,
但是DE只能开启一种Personal Tag。
只有真正的Production才能开启Public Tag。

有了这个功能,用户可以跨越表关联,方便的管理一组数据了。

有同学质疑我的“一天一个”,我的意思是。。。。我花了一天时间才能写一个的。。。。

关于Salesforce的组成

小时候看CCTV播放的纪录片,里面讲抗日战争日期中国空军的事情。
到现在都能很清晰的记起其中一段。
当时是采访一位老兵,说当时根本没有时间进行系统训练,
所有的飞行员都是简简单单练练起飞降落开机枪就上天作战了,
根本没机会像现代空军一样,要系统的学习机械原理,飞机构造之类的才能上天。
简单的比喻就像开车一样,能把车开走不一定需要知道车为什么能开走,引擎是什么烧的,动力是怎么传导的。

做开发也一样。
特别是Salesforce。
作为Salesforce从业者,也许精通admin,玩转401,熟练掌握501。
但不一定需要了解Salesforce平台是由什么构成的。

对于大部分从业人员来讲,了解了这些也不一定对日常工作有什么帮助。更何况还有可能占用宝贵的娱乐时间。
而且虽然作为Salesforce从业者,仍然有大量的英语困难症患者(日语也一样),更成为了止步不前的借口。

其实还是建议所有想在Salesforce平台好好做下去的从业者,认认真真的读一读官方提供的Salesforce基本原理
毕竟想跳的更高,就要蹲的更深。

干了这些年,根据经验总结。
我把Salesforce里面所有的东西分成三类。
第一类是云计算的核心,Metadata元数据
第二类是数据
第三类是设定

Metadata是其中最好理解的,也是最难深入理解的。
有经验的人知道我们对系统做出的大部分(注意,并不是所有)改动都会在Metadata上有所体现。
我们在Deploy的时候,Deloy的具体source也是MetaData文件。
MetaData可以视为是定义了Org中的一切。
老手甚至可以通过手动编辑MetaData让系统变成自己想要的样子。

如果MetaData使用来定义这个系统是什么样子。
那么数据就是这个系统的血肉,真正让系统的存在有了意义。
做Java系统时间长了的人,总觉得系统=代码。
而Salesforce将数据的重要性提到了一个新的高度。
几乎所有的改动都在围绕着数据。
收集数据,处理数据,显示数据。
数据还要分为业务数据,功能数据,系统数据。
业务数据就是系统用户日常业务开展需要数据,客户信息啊,商品列表啊,这些也是开发人员轻易不能动的内容之一。
功能数据,就是为了实现一些功能,需要储存参数的临时表, Custom Setting,或者记录功能执行记录的数据。
这部分数据用户是看不到也不需要看到的,但是又是系统正常运转的必须部分。
最后还有大量的系统数据,这些数据默默的维持着系统的基本运转,其中有一些表甚至不对开发者开放。

在这个世界上, 事情从来不会非黑即白。如果MetaData和数据泾渭分明,世界还是很美好的。
但现实是残酷的。并不是如理想一般,这个东西不是属于MetaData,就是属于数据。
在Salesforce里面,存在这大量双轨制的东西。
比如Profile,Role,Apex,VF等等,
这些既存在于MetaData,又存在于数据中。这样就等于赋予了这些东西两种截然不同的属性。
MetaData和数据的根本区别在于什么呢?
MetaData是定义,可以通过工具部署。是骨架。
数据不需要部署,可以直接导入,直接修改。是血肉。
这些东西就如粒子一般,拥有波粒二象性。
这里跑题太严重,就不展开了。

除了MetaData和数据以外,还有一些看得见摸得着的东西,不属于任何一种。
我称呼它们为设定,它们拥有这最顽固的性质,
除了手改,无法部署,不能导入。而且存在于各个角落。
最复杂的情况比如,在Profile本身已经是双规制。
结果Session Timeout设置和Password Policies这个两个俨然应该隶属于Profile的设定,
却不存在于MetaData和数据中任何一方。
甚至Profile的Copy功能也会将这两个设定无视掉。

具体分类的名单,等整理完之后再发吧。

// Upadate1

我还是想简单了,其实Metadata和数据的界限要更模糊一点。
有些我原来以为只能通过Metadata修改的东西,最后在sfdc后台其实还是一张表。

古德尔的哥德堡变奏曲

人生中第一次听说哥德堡变奏曲,是通过汉尼拔医生。

在《汉尼拔医生》那本小说里,变态吃人心理医生汉尼拔在杀人之后,
在教堂里优雅地弹奏了一曲哥德堡变奏曲,并在专注之处,情不自禁的哼唱。
那种大师沉醉与演奏的画面感,使我迫不及待的找来听听看。

当时互联网还不是很发达,很辛苦才找到一个低音质的不完整版本。
而且演奏者也不知道是哪个路人。
所以并没有太多感觉。

多年之后,再次想起了巴赫,再次想起了哥德堡变奏曲。
相比于贝多芬,斯特劳斯,莫扎特和肖邦等,我更喜欢虔诚的巴赫。

可惜,出生的年代太晚,没有机会亲耳听大师们演奏自己的作品。
所以对于后世的我们,只能不断的挑剔着演奏者及乐队指挥。

年少时,曾疯狂的收集维也纳爱乐乐团演奏的贝多芬。
指挥也必须是卡拉扬。
我觉得,只有这个组合,才能真正的表达出贝多芬。
那个与命运抗争的贝多芬。

同样,每个演奏巴赫的钢琴家都有着自己对巴赫的理解,
每个钢琴家对巴赫的每一部作品都有自己的演绎。
在演奏技巧上无懈可击的,只能叫琴师,
真正的大师才能赋予灵魂。

历史上演奏过哥德堡变奏曲的钢琴家有很多,
我却唯独喜欢古德尔1981年录制版本。

古德尔是年少成名的天才演奏家。喜欢巴赫,不喜欢肖邦。
录制的第一张唱片是哥德堡变奏曲,录制的最后一张唱片也是哥德堡变奏曲。
在录制最后一次哥德堡变奏曲之后,第二年便与世长辞。

他在22岁时第一次录制哥德堡变奏曲,节奏欢快,清朗。
81年最后一次录制哥德堡变奏曲,更加沉稳,内省。
不变的,是古德尔在弹奏到忘情时一起被录进唱片的哼哼声和椅子的嘎吱声。
那就是他与巴赫进行交流的方式。

哥德堡变奏曲,巴赫疯狂的进行了30次精彩绝伦的变奏。
所以汉尼拔医生等一众变态都喜欢它。

古德尔和巴赫一样生性孤独,这两个相差两个多世纪的灵魂在哥德堡变奏曲中相遇。
最后也如同巴赫一样,刚步入中年就因病离世。
只留下了对巴赫伟大的诠释。