标签:用户输入 last 抽象 通过 覆盖 名称 factory top nts
Apex 企业设计模式将应用分为服务层、模型层、选择逻辑层、工作单元几个部分。FFLIB 是一个开源的 Apex 框架,可以帮助开发者快速建立相关的功能。
FFLIB 可以直接部署到需要使用的 Salesforce 系统中。在其 GitHub 主页上可以点击 “Deploy to Salesforce” 按钮直接进行部署。
在 FFLIB 中,有一些关键的类可以帮助开发者实现 Apex 的企业设计模式。
下面我们通过一个简单的例子阐述如何实现设计模式的各个部分。
功能包括:
包含以下几个类:
App_Application 类:
public without sharing class App_Application {
public static final fflib_Application.UnitOfWorkFactory unitOfWork = new fflib_Application.UnitOfWorkFactory(
new List<SObjectType> {
Account.SObjectType,
Contact.SObjectType
}
);
public static final fflib_Application.ServiceFactory service = new fflib_Application.ServiceFactory(
new Map<Type, Type> {
AccountService.IService.class => AccountService.class
}
);
public static final fflib_Application.SelectorFactory selector = new fflib_Application.SelectorFactory(
new Map<SObjectType, Type> {
Account.SObjectType => AccountSelector.class
}
);
public static final fflib_Application.DomainFactory domain = new fflib_Application.DomainFactory(
App_Application.selector,
new Map<SObjectType, Type> {
Account.SObjectType => Accounts.class
}
);
}
App_Application 类中使用工厂方法给出了各个部分的初始化逻辑。注意每个工厂方法的参数,它们包含了对象类型和相应的类。在下面的各个部分中我们会直接调用 App_Application 类的成员。
AccountSelector 类:
public with sharing class AccountSelector extends fflib_SObjectSelector {
public static AccountSelector newInstance() {
return (AccountSelector) App_Application.selector.newInstance(Account.SObjectType);
}
/*
* 实现了 fflib_SObjectSelector 中的抽象函数,用于返回当前类所关联的 SObject 对象类型
*/
public Schema.SObjectType getSObjectType() {
return Account.SObjectType;
}
/*
* 实现了 fflib_SObjectSelector 中的抽象函数,用于提供默认搜索时得到的字段
*/
public List<Schema.SObjectField> getSObjectFieldList() {
return new List<Schema.SObjectField> {
Account.Name,
Account.Id
};
}
/*
* 实现了 fflib_SObjectSelector 中的抽象函数,通过一组 ID 的值来查询一组 Account 对象
*/
public List<Account> selectById(Set<Id> idSet) {
return (List<Account>) selectSObjectsById(idSet);
}
/*
* 自定义函数,用于查找 Name 字段和给定的值相同的 Account 对象
*/
public List<Account> selectByName(String name) {
return [SELECT Name FROM Account WHERE Name = :name];
}
}
AccountSelector 类相对简单。除了实现 fflib_SObjectSelector 类中的抽象函数,我们自己定义了一个按名称查找的函数,可供服务层调用。
Accounts 类:
public with sharing class Accounts extends fflib_SObjectDomain {
public Accounts(List<SObject> SObjectList) {
super(SObjectList);
}
/*
* 对于 fflib_SObjectDomain 类中的钩子函数的重写,在插入记录之后自动建立一个 Contact 对象
*/
public override void onAfterInsert() {
fflib_ISObjectUnitOfWork uow = App_Application.unitOfWork.newInstance();
createContact(uow);
uow.commitWork();
}
/*
* 自定义函数,对于每个 Account 记录,建立一个同名的 Contact 记录
*/
public void createContact(fflib_ISObjectUnitOfWork uow) {
for (Account acc : (List<Account>) Records) // Records 变量是 fflib_SObjectDomain 中定义的 List<SObject> 类型的成员,表示此类包含的记录
{
Contact c = new Contact(LastName = acc.Name);
// 将新建的 Contact 对象关联到 Account 对象中
// 这个函数的第二个参数表明了 Contact 对象中和 Account 对象关联的字段
uow.registerNew(c, Contact.AccountId, acc);
}
}
// 每个继承了 fflib_SObjectDomain 类都必须有的内部类
public class Constructor implements fflib_SObjectDomain.IConstructable {
public fflib_SObjectDomain construct(List<SObject> SObjectList) {
return new Accounts(SObjectList);
}
}
}
Accounts 类是模型层,里面定义了一个函数,用来在 Account 对象下面创建一个 Contact 对象。
对于内部类 Constructor,这是 FFLIB 中的一个约定,每一个继承了 fflib_SObjectDomain 类都必须有这一个内部类。
fflib_SObjectDomain 类实现了一个 TriggerHandler 函数,并提供了若干钩子函数,可以和触发器类结合使用,从而使得对象在被增删修改之后可以自动执行相应的逻辑。由于 Apex 缺乏完整的反射机制,在进行触发器操作时,模型层无法直接得到需要处理的记录。这个内部类实现了 fflib_SObjectDomain.IConstructable 接口的 construct 函数,从而可以将需要处理的数据传递给模型层。
AccountTrigger 类:
trigger AccountTrigger on Account (before insert, after insert) {
fflib_SObjectDomain.triggerHandler(Accounts.class);
}
触发器类很简单,直接调用了 fflib_SObjectDomain 中的 triggerHandler 函数,将模型层的类作为参数传进去。
AccountService 类:
public class AccountService implements IService {
public static IService newInstance() {
return (IService) App_Application.service.newInstance(IService.class);
}
public interface IService {
void createAccount(String name);
void createAccount(fflib_ISObjectUnitOfWork uow, String name);
}
/*
* 新建 Account 记录
*/
public void createAccount(String name) {
fflib_ISObjectUnitOfWork uow = App_Application.unitOfWork.newInstance();
createAccount(uow, name);
uow.commitWork(); // 将数据存入数据库
}
/*
* 核心逻辑,重载 createAccount 函数,查找相应的 Account 记录,如果找不到则新建
*/
public void createAccount(fflib_ISObjectUnitOfWork uow, String name) {
AccountSelector selector = (AccountSelector) AccountSelector.newInstance();
List<Account> accList = selector.selectByName(name);
// 如果不存在相应的 Account 记录,才创建
if (accList.isEmpty()) {
Account newAcc = new Account(Name = name);
uow.registerNew(newAcc); // 将 newAcc 记录标记为新记录,等待使用 commitWork 函数来存入数据库
}
}
}
AccountService 类的逻辑很简单,提供了 createAccount 函数,从而让外部代码可以调用并创建 Account 对象。
注意,我们重载了 createAccount 函数。
第一个函数只包含一个参数,即“名字”,让外部代码直接调用即可创建 Account 对象。
第二个函数包含了“名字”和“工作单元”两个参数,并且不包含 commitWork 函数,从而可以被外部代码单独调用,只实现创建记录的逻辑。外部代码可以调用其他的各种逻辑,也可以定义将数据写入数据库的时间。
Visualforce 页面:
<apex:page controller="AccountTestFflibController">
<apex:form>
<apex:inputText label="输入客户名字" value="{!name}"/>
<apex:commandbutton value="创建" action="{!create}" />
</apex:form>
</apex:page>
Visualforce 控制器:
public class AccountTestFflibController {
public String name {get; set;}
public PageReference create(){
AccountService service = (AccountService) AccountService.newInstance();
service.createAccount(name);
return null;
}
}
可以看到,在 Visualforce 控制器中,我们只调用了一次服务层的 createAccount 函数,就完成了所有相关的逻辑。
上述示例只是使用了 FFLIB 中的一些基本功能,实现了 Apex 企业设计模式的基本结构。
FFLIB 还提供了其他的功能和辅助函数,在实际应用中,可以和其他的框架或现有的代码结合,提高代码的维护和更新效率。
对于 FFLIB 的单元测试,可以使用 ApexMocks 框架。
ApexMocks 框架是为 Apex 的单元测试开发,主要提供了模拟数据的功能。
在 Apex 开发中,我们始终要对开发的类做出单元测试,并且代码覆盖率要不小于75%。为了对功能进行全面的测试,我们往往需要准备很多数据,并将它们插入数据库(当然,在测试结束后 Salesforce 会自动将这些数据删除)。随之而来的问题就是单元测试的效率会随着准备数据的复杂度而降低。
ApexMocks 中提供了多种方法让我们来创建模拟数据,并可以和 Apex 企业设计模式结合,使得我们的单元测试不用真正的对数据库进行操作,从而提高测试效率。
ApexMocks 可以直接部署到需要使用的 Salesforce 系统中。在其 GitHub 主页上可以点击 “Deploy to Salesforce” 按钮直接进行部署。
在 ApexMocks 框架中,最关键的函数就是 setMock 函数。通过它,我们可以对要进行测试的类进行依赖注入,让我们的逻辑使用模拟的数据进行测试。
在使用 ApexMocks 进行模拟数据和测试的时候,一般遵循以下四个步骤:
让我们使用之前建立的 AccountService 类来进行单元测试。
测试类中包含两个函数,一个测试在没有任何 Account 存在时,新的 Account 对象可以被建立,另一个测试当已经存在同名的 Account 对象时,没有新的 Account 对象被创建。
@isTest(isParallel=true)
public class AccountServiceTest {
@IsTest
private static void shouldCreateAccount()
{
// Create mocks
fflib_ApexMocks mocks = new fflib_ApexMocks();
fflib_ISObjectUnitOfWork uowMock = new fflib_SObjectMocks.SObjectUnitOfWork(mocks); // 建立工作单元的模拟
AccountSelector selectorMock = (AccountSelector) mocks.Mock(AccountSelector.class); // 建立选择逻辑层的模拟
// Given
String testAccountName = 'Test Existing Account';
App_Application.unitOfWork.setMock(uowMock); // 使用 setMock 设置选择逻辑层的模拟
App_Application.selector.setMock(selectorMock); // 使用 setMock 设置工作单元的模拟
// When
AccountService service = (AccountService) AccountService.newInstance();
service.createAccount(testAccountName);
// Then
fflib_ArgumentCaptor argument = fflib_ArgumentCaptor.forClass(fflib_ISObjectUnitOfWork.class);
((fflib_ISObjectUnitOfWork) mocks.verify(uowMock, 1)).registerNew((Account) argument.capture()); // 验证 registerNew 函数被执行了一次,并且其中的参数是 Account 类型的
((fflib_ISObjectUnitOfWork) mocks.verify(uowMock, 1)).registerNew((Account) fflib_Match.anyObject()); // 另一种验证,registerNew 函数被执行了一次,并且其中的参数是任意 Account 类型的任何对象
}
@IsTest
private static void shouldNotCreateAccount()
{
// Create mocks
fflib_ApexMocks mocks = new fflib_ApexMocks();
fflib_ISObjectUnitOfWork uowMock = new fflib_SObjectMocks.SObjectUnitOfWork(mocks);
AccountSelector selectorMock = (AccountSelector) mocks.Mock(AccountSelector.class);
// Given
String testAccountName = 'Test Existing Account';
/*
* 下面这段代码使用 stub API 来模拟选择逻辑层的函数 selectByName 的执行结果:
* 当其参数是变量 testAccountName 的值的时候,返回一个 Account 对象
*/
mocks.startStubbing();
List<Account> existingAccounts = new List<Account> {
new Account(
Id = fflib_IDGenerator.generate(Account.SObjectType), // 建立随机的一个 ID 值
Name = testAccountName)
};
mocks.when(selectorMock.sObjectType()).thenReturn(Account.SObjectType);
mocks.when(selectorMock.selectByName(testAccountName)).thenReturn(existingAccounts);
mocks.stopStubbing();
App_Application.unitOfWork.setMock(uowMock);
App_Application.selector.setMock(selectorMock);
// When
AccountService service = (AccountService) AccountService.newInstance();
service.createAccount(testAccountName);
// Then
fflib_ArgumentCaptor argument = fflib_ArgumentCaptor.forClass(fflib_ISObjectUnitOfWork.class);
((fflib_ISObjectUnitOfWork) mocks.verify(uowMock, 0)).registerNew((Account) argument.capture()); // 验证 registerNew 函数没有被执行
}
}
代码解释:
ApexMocks 框架提供了非常强大的模拟对象功能,我们在上文中只给出了很简单的示例。
将 ApexMocks 和 FFLIB 结合使用可以显著地提高单元测试的运行效率。
标签:用户输入 last 抽象 通过 覆盖 名称 factory top nts
原文地址:https://www.cnblogs.com/chengcheng0148/p/apex_in_fflib_basic.html