Salesforce-Apex基础

Apex基础

Apex 语言亮点

像其他面向对象的编程语言一样,这些是Apex支持的一些语言结构:

  • 类,接口,属性和集合(包括数组)。
  • 对象和数组表示法。
  • 表达式,变量和常量。
  • 条件语句(if-then-else)和控制流语句(for循环和while循环)。

与其他面向对象的编程语言不同,Apex支持:

  • 作为Apex的云开发是在云中存储,编译和执行的。
  • 触发器,类似于数据库系统中的触发器。
  • 数据库语句,允许您直接进行数据库调用和查询语言来查询和搜索数据。
  • 事务和回滚。
  • 全局访问修饰符,它比public修饰符更宽松,并允许跨命名空间和应用程序访问。
  • 自定义代码的版本。

另外,Apex是一个不区分大小写的语言。

Apex语法

  • Switch

    String waterLevel = 'empty';
    //option 1 using a single value
    switch on waterLevel{
        when 'empty'{
            System.debug('Fill the tea kettle');
        }
        when 'half'{
            System.debug('Fill the tea kettle');
        }
        when 'full'{
            System.debug('The tea kettle is full');
        }
        when else{
            System.debug('Error!');
        }
    }
    //option 2 using multiple values
    switch on waterLevel{
        when 'empty', 'half'{ //when waterLevel is either empty or half
            System.debug('Fill the tea kettle');
        }
        when 'full'{
            System.debug('The tea kettle is full');
        }
        when else{
            System.debug('Error!');
        }
    
    

    输出结果为:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4V3CrfjK-1572587509250)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1571644156217.png)]

APEX动态运行代码行 (调试|测试)

  1. 打开开发人员控制台( Developer console )

  2. 点击Debug | Open Execute Anonymous Window. [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ovCiC68A-1572587509251)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1571104642554.png)]

  3. 运行代码:

    ①点击Execute(运行全部)

    ②点击**Execute Highlighted(运行选中部分代码) **

DML

使用数据操作语言(缩写为DML)在Salesforce中创建和修改记录。使用数据操作语言(缩写为DML)在Salesforce中创建和修改记录。

DML语句
语句作用语法
insert新增insert user;
update修改
upsert更新插入
delete删除
undelete回复删除
merge合并
  • insert

    新增单条记录

    // 创建一个客户对象
    Account acct = new Account(Name='Acme', Phone='(415)555-1212', NumberOfEmployees=100);
    // 使用DML插入客户信息
    insert acct;
    //插入记录时,系统会为每个记录分配一个ID。
    //ID值还会自动填充到在DML调用中用作参数的sObject变量上。(也就是说将分配的ID赋值给对象)
    ID acctID = acct.Id;
    System.debug('ID = ' + acctID);
    

    批量新增

    List<Contact> conList = new List<Contact> {
        new Contact(FirstName='Joe',LastName='Smith',Department='Finance'),
            new Contact(FirstName='Kathy',LastName='Smith',Department='Technology'),
            new Contact(FirstName='Caroline',LastName='Roth',Department='Finance'),
            new Contact(FirstName='Kim',LastName='Shain',Department='Education')};
                
    // Bulk insert all contacts with one DML call
    insert conList;
    
  • update

    批量修改

    List<Contact> conList = new List<Contact> {
        new Contact(FirstName='Joe',LastName='Smith',Department='Finance'),
            new Contact(FirstName='Kathy',LastName='Smith',Department='Technology'),
            new Contact(FirstName='Caroline',LastName='Roth',Department='Finance'),
            new Contact(FirstName='Kim',LastName='Shain',Department='Education')};
    insert conList;
    
    List<Contact> listToUpdate = new List<Contact>();
    for(Contact con : conList) {
        if (con.Department == 'Finance') {
            con.Title = 'Financial analyst';
            // Add updated contact sObject to the list.
            listToUpdate.add(con);
        }
    }
    update listToUpdate
    
  • Upsert

什么时候用Upsert?

​ 如果你有一个包含新纪录和现有记录的集合,则可以使用Upsert来对所有记录进行插入和更新。

Upsert的优势?

​ 如果集合的记录在数据库中存在,则会更新记录。如果集合的记录在数据库中不存在,则会新增记录。Upsert有助于避免重复记录的创建,并可以节省您的时间,因为您不必先确定哪些记录。

注意:Upsert语句通过比较一个字段的值将”操作的数据“和”数据库中的数据“作对比。

如果你没指定字段,Upsert使用当前对象的ID做对比。

①指定字段

upsert sObjectList Account.Fields.MyExternalId;//指定字段作为对比值

②不指定字段

Contact josh = new Contact(FirstName='Josh',LastName='Kaplan',Department='Finance');       
insert josh;
josh.Description = 'Josh\'s record has been updated by the upsert operation.';
Contact kathy = new Contact(FirstName='Kathy',LastName='Brown',Department='Technology');
List<Contact> contacts = new List<Contact> { josh, kathy };
upsert contacts;

Upsert对比结果:

  • 如果字段值不匹配,则会创建一个新的对象记录。

  • 如果字段值匹配一次,则现有对象记录将更新。

  • 如果字段值多次匹配,则会生成错误,并且对象记录不会插入或更新。

  • delete

    删除的记录不会从 Lightning Platform (数据库)上永久删除,但是会将它们放置在回收站中15天内,从那里可以恢复它们。

    语法:

    Contact[] contactsDel = [SELECT Id FROM Contact WHERE LastName='Smith']; 
    delete contactsDel;
    
DML语句异常

如果DML操作失败,则返回类型为的异常 DmlException。您可以在代码中捕获异常以处理错误情况。

这个例子产生了一个 DmlException因为它尝试插入没有必填名称字段的帐户。异常捕获在catch块中。

try {
    Account acct = new Account();
    insert acct;
} catch (DmlException e) {
    System.debug('发生了DML异常: ' +
                e.getMessage());
}

数据库方法

Apex包含内置的Database类,该类提供执行DML操作并镜像DML语句副本的方法。

这些数据库方法是静态的,并在类名上调用。

  • Database.insert()
  • Database.update()
  • Database.upsert()
  • Database.delete()
  • Database.undelete()
  • Database.merge()

与DML语句不同,数据库方法具有可选的allOrNone 参数,该参数使您可以指定操作是否允许部分成功。当此参数设置为false时,如果部分记录操作发生错误,则将提交成功的记录,并为失败的记录返回错误。此外,部分操作成功的记录不会引发任何异常。

Database.insert(recordList, false);

数据库方法返回结果对象,其中包含每个记录的成功或失败信息。

Database.SaveResult[] results = Database.insert(recordList, false);

默认情况下,allOrNone参数是true,这意味着Database方法的行为如果遇到失败,将抛出异常,回滚所有操作。

以下语句的效果一致

Database.insert(recordList);
Database.insert(recordList, true);
insert recordList;

关联操作(主从)

insert关联记录(需要多次DML)

Account acct = new Account(Name='SFDC Account');
insert acct;
ID acctID = acct.ID;
//将客户关联到对应的联系人(需要执行俩次DML)
Contact mario = new Contact(
    FirstName='Mario',
    LastName='Ruiz',
    Phone='415.555.1212',
    AccountId=acctID);
insert mario;

update关联记录(需要多次DML)

Contact queriedContact = [SELECT Account.Name 
                          FROM Contact 
                          WHERE FirstName = 'Mario' AND LastName='Ruiz'
                          LIMIT 1];
queriedContact.Phone = '(415)555-1213';
queriedContact.Account.Industry = 'Technology';
update queriedContact;//更新联系人信息
update queriedContact.Account; //更新联系人的客户信息

删除关联记录

delete操作支持级联删除, 如果删除父对象,就会自动删除其子对象。

例如,删除帐户也将删除其相关联系人。

Account[] queriedAccounts = [SELECT Id FROM Account WHERE Name='SFDC Account'];
delete queriedAccounts;

SOQL(类似SQL)

SOQL对象查询语言

SOQL每次查询单个表的数据上限是200,如果超过200条数据,则会再发另外一个请求去查询剩余的数据。

您无需在查询中指定Id字段,因为它始终在Apex查询中返回,除非你只查询ID字段,这个时候则需要指定ID字段。

在查询编辑器中运行查询时,您可能还需要指定ID字段,因为除非指定,否则不会显示ID字段。

当SOQL嵌入Apex中时,称为内联SOQL

Account[] accts = [SELECT Name,Phone FROM Account];
使用查询编辑器
  1. 在开发人员控制台中,单击Query Editor[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Y6ehhmiZ-1572587509252)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1571127124119.png)]
  2. 输入你要测试的sql,然后点击Execute运行
使用条件过滤查询结果
SELECT Name,Phone FROM Account WHERE (Name='SFDC Computing' OR (NumberOfEmployees>25 AND BillingCity='Los Angeles'))
排序

您可以对大多数字段进行排序,包括数字和文本字段。您无法对富文本格式和多选选择列表之类的字段进行排序。

SELECT Name,Phone FROM Account ORDER BY Name ASC
SELECT Name,Phone FROM Account ORDER BY Name DESC
限制返回的记录数

例如,此查询检索返回的第一个帐户。请注意,当limit 1的时候使用时返回的值是一个帐户而不是数组。

Account oneAccountOnly = [SELECT Name,Phone FROM Account LIMIT 1];
在SOQL查询中调用Apex中的变量

使用:引用变量

String targetDepartment = 'Wingo';
Contact[] techContacts = [SELECT FirstName,LastName 
                          FROM Contact WHERE Department=:targetDepartment];
查询关联记录

当查询名称为"SFDC Computing"的客户时,同时查询出该客户对应的联系人

SELECT Name, (SELECT LastName FROM Contacts) FROM Account WHERE Name = 'SFDC Computing'

使用关联查询出来的结果还能通过.调用相关联的数据

Account[] acctsWithContacts = [SELECT Name, (SELECT FirstName,LastName FROM Contacts)
                               FROM Account 
                               WHERE Name = 'SFDC Computing'];
// 获取从表数据
Contact[] cts = acctsWithContacts[0].Contacts;
System.debug('Name of first associated contact: ' 
             + cts[0].FirstName + ', ' + cts[0].LastName);

也可以从子表数据调用主表数据

Contact[] cts = [SELECT Account.Name FROM Contact];
Contact carol = cts[0];
String acctName = carol.Account.Name;
System.debug('account name is ' + acctName);
使用外部变量+Like语句
[SELECT Name FROM Contact WHERE Name like :firstName+'%' ]

SOSL

Salesforce对象搜索语言(SOSL)是一种Salesforce搜索语言,用于在记录中执行文本搜索。使用SOSL在Salesforce中跨多个标准和自定义对象记录搜索字段。文本搜索不区分大小写 。

语法:

//IN ALL FIELDS 默认查询全部字段所以可有可无
FIND {bo} RETURNING Contact
FIND {bo} IN ALL FIELDS RETURNING Contact(name)

//如果需要指定特定字段查找
FIND {bo} IN Email FIELDS RETURNING Contact(name)

//跟在对象后面的字段是返回的内容
FIND {bo} IN ALL FIELDS RETURNING Contact(id,name)

//Order by 排序某个字段
FIND {Cloud Kicks} RETURNING Account (Name ORDER BY Name)

//limit设置返回的最大记录数
FIND {Cloud Kicks} RETURNING Account (Name ORDER BY Name LIMIT 10)

//offset将起始行偏移量设置为结果
FIND {Cloud Kicks} RETURNING Account (Name ORDER BY Name LIMIT 10 OFFSET 25)

这是一个SOSL查询的示例,该查询搜索具有任何字段带有单词“ SFDC”的字段的客户和联系人。

List<List<SObject>> searchList = [FIND 'SFDC' IN ALL FIELDS 
                                      RETURNING Account(Name), Contact(FirstName,LastName)];

案例:在客户的字段Name,联系人字段FirstName,LastName,Department,中查找值为Wingo的记录

FIND {Wingo} IN ALL FIELDS RETURNING Account(Name), Contact(FirstName,LastName,Department)
SOSL关键字
关键字作用
IN限制要搜索的字段类型,包括电子邮件,姓名或电话
LIMIT指定要返回的最大行数
OFFSET在多页上显示搜索结果
RETURNING限制对象和字段返回
WITH DATA CATEGORY指定要返回的数据类型
WITH DivisionFilter指定要返回的除法字段
WITH NETWORK指定要返回的社区ID
WITH PricebookId指定要返回的price book ID

SOQL与SOSL之间的异同

SOSL与SOQL一样,SOSL允许您在组织的记录中搜索特定信息。与一次只能查询一个标准或自定义对象的SOQL不同,单个SOSL查询可以搜索所有对象。

另一个区别是,SOSL根据单词匹配来匹配字段,而默认情况下(当不使用通配符时)SOQL执行完全匹配。例如,在SOSL中搜索“数字”将返回字段值为“数字”或“数字公司”的记录,但SOQL仅返回字段值为“数字”的记录。

SOQL和SOSL是两种具有不同语法的独立语言。每种语言都有不同的用例:

  • 使用SOQL检索单个对象的记录。
  • 使用SOSL在多个对象之间搜索字段。SOSL查询可以搜索对象上的大多数文本字段。

APEX触发器

Apex触发器使您可以在事件发生之前或之后对Salesforce中的记录(例如插入,更新或删除)执行自定义操作。就像数据库系统支持触发器一样,Apex为管理记录提供触发器支持。

您可以使用触发器来执行在Apex中可以做的任何事情,包括执行SOQL和DML或调用自定义Apex方法。

管理触发器

你可以在此页面上编辑、删除等操作

禁用触发器
  1. 设置中,使用左侧搜索栏搜索Apex 触发器
  2. 点击触发器旁边的编辑按钮
  3. 取消 Is Active的勾选[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0V8XE9GS-1572587509253)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1571218889699.png)]
触发器的语法
trigger ContextExampleTrigger on Account (before insert, after insert, after delete) {
    if (Trigger.isInsert) {
        if (Trigger.isBefore) {
            // Process before insert
        } else if (Trigger.isAfter) {
            // Process after insert
        }        
    }
    else if (Trigger.isDelete) {
        // Process after delete
    }
}
触发上下文变量
变量用法
isExecuting如果Apex代码的当前上下文是触发器,而不是Visualforce页面,Web服务或控件,则返回true。 executeanonymous() API调用。
isInsert如果此触发器是由来自Salesforce用户界面、Apex或API的插入操作触发的,则返回true。
isUpdate如果此触发器是由来自Salesforce用户界面、Apex或API的更新操作触发的,则返回true。
isDelete如果此触发器是由Salesforce用户界面、Apex或API中的删除操作触发的,则返回true。
isBefore如果在保存任何记录之前触发此触发器,则返回true。
isAfter如果在所有记录都是savec之后触发此触发器,则返回true
isUndelete如果在从回收站恢复记录后触发此触发器,则返回true。此恢复可以在从Salesforce用户界面、Apex或API执行取消删除操作后发生。
new返回sObject记录的新版本列表。这个sObject列表只能在insert、update和undelete触发器中使用,记录只能在before触发器中修改。
newMap将IDs映射到sObject记录的新版本。此映射仅在更新前、插入后、更新后和取消删除触发器后可用。
old返回sObject记录的旧版本列表。此sObject列表仅在更新和删除触发器中可用。
old mapIDs到sObject记录的旧版本的映射。此映射仅在更新和删除触发器中可用。
operationType返回一个System类型的枚举。对应于当前操作的TriggerOperation。系统的可能值。TriggerOperation enum是:BEFORE_INSERT、BEFORE_UPDATE、BEFORE_DELETE、AFTER_INSERT、AFTER_UPDATE、AFTER_DELETE和AFTER_UNDELETE。如果您根据不同的触发器类型改变您的编程逻辑,那么可以考虑使用switch语句,使其具有惟一触发器执行枚举状态的不同排列。
size触发器调用中的记录总数,包括旧的和新的。
触发器的实际操作

在触发器内部调用类方法,触发器除了可以在内部改变当前记录的值,还能调用外部的方法

//内部修改值
trigger HelloWorldTrigger on Account (before insert) {
    for(Account a : Trigger.New) {
        a.Description = 'New description';
    }   
}
//内部调用外部类方法
//当联系人有新纪录新增或删除时,调用邮箱类的方法发信息给管理员
trigger ExampleTrigger on Contact (after insert, after delete) {
    if (Trigger.isInsert) {
        Integer recordCount = Trigger.New.size();
        // Call a utility method from another class
        EmailManager.sendMail('Your email address', 'Trailhead Trigger Tutorial', 
                    recordCount + ' contact(s) were inserted.');
    }
    else if (Trigger.isDelete) {
        // Process after delete
    }
}

使用触发器添加关联记录

//当客户记录被新增或者修改时,遍历所有客户对象,如果当前客户没有关联的业务机会,则自动帮客户创建一个关联当前用户的业务机会
trigger AddRelatedRecord on Account(after insert, after update) {
    List<Opportunity> oppList = new List<Opportunity>();
    
    // Get the related opportunities for the accounts in this trigger
    Map<Id,Account> acctsWithOpps = new Map<Id,Account>(
        [SELECT Id,(SELECT Id FROM Opportunities) FROM Account WHERE Id IN :Trigger.New]);
    
    // Add an opportunity for each account if it doesn't already have one.
    // Iterate through each account.
    for(Account a : Trigger.New) {
        System.debug('acctsWithOpps.get(a.Id).Opportunities.size()=' + acctsWithOpps.get(a.Id).Opportunities.size());
        // Check if the account already has a related opportunity.
        if (acctsWithOpps.get(a.Id).Opportunities.size() == 0) {
            // If it doesn't, add a default opportunity
            oppList.add(new Opportunity(Name=a.Name + ' Opportunity',
                                       StageName='Prospecting',
                                       CloseDate=System.today().addMonths(1),
                                       AccountId=a.Id));
        }           
    }
    if (oppList.size() > 0) {
        insert oppList;
    }

触发器异常

有时您需要对某些数据库操作添加限制,例如在满足某些条件时阻止保存记录。

以下触发条件可防止删除具有相关业务机会客户。默认情况下,删除客户会导致其所有相关记录的级联删除。此触发器可防止业务机会的级联删除。

trigger AccountDeletion on Account (before delete) {
   
    // Prevent the deletion of accounts if they have related opportunities.
    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.');
    }
    
}
触发器和标注(Triggers and Callouts)

Apex允许您调用Apex代码并将其与外部Web服务集成。对外部Web服务的Apex调用称为标注。

//标有@future(callout=true)的方法都是异步方法
public class CalloutClass {
    @future(callout=true)
    public static void makeCallout() {
        HttpRequest request = new HttpRequest();
        // Set the endpoint URL.
        String endpoint = 'http://yourHost/yourService';
        request.setEndPoint(endpoint);
        // Set the HTTP verb to GET.
        request.setMethod('GET');
        // Send the HTTP request and get the response.
        HttpResponse response = new HTTP().send(request);
    }
}

//异步调用外部类方法
trigger CalloutTrigger on Account (before insert, before update) {
    CalloutClass.makeCallout();
}
触发器批量操作
执行批量SOQL

反例:查询每个客户的业务机会,需要发n次SOQL(n为客户数量)

trigger SoqlTriggerNotBulk on Account(after update) {   
    for(Account a : Trigger.New) {
        // Get child records for each account
        // Inefficient SOQL query as it runs once for each account!
        Opportunity[] opps = [SELECT Id,Name,CloseDate 
                             FROM Opportunity WHERE AccountId=:a.Id];
        
        // Do some other processing
    }
}
如何优化批量SOQL?

将Trigger.New里面所有的客户作为一个集,使用IN查询此集里满足条件的业务机会对象

trigger SoqlTriggerBulk on Account(after update) {  
    // Perform SOQL query once.    
    // Get the related opportunities for the accounts in this trigger,
    // and iterate over those records.
    for(Opportunity opp : [SELECT Id,Name,CloseDate FROM Opportunity
        WHERE AccountId IN :Trigger.New]) {
  
        // Do some other processing
    }
}
执行批量DML

反例:将DML操作写在for循环中,会极大的影响其效率。

trigger DmlTriggerNotBulk on Account(after update) {   
    // Get the related opportunities for the accounts in this trigger.        
    List<Opportunity> relatedOpps = [SELECT Id,Name,Probability FROM Opportunity
        WHERE AccountId IN :Trigger.New];          
    // Iterate over the related opportunities
    for(Opportunity opp : relatedOpps) {      
        // Update the description when probability is greater 
        // than 50% but less than 100% 
        if ((opp.Probability >= 50) && (opp.Probability < 100)) {
            opp.Description = 'New description for opportunity.';
            // Update once for each opportunity -- not efficient!
            update opp;
        }
    }    
}
如何优化批量DML?

创建一个新的集合,装新的记录数据。遍历完之后再执行单个DML操作,操作集合。

trigger DmlTriggerBulk on Account(after update) {   
    // Get the related opportunities for the accounts in this trigger.        
    List<Opportunity> relatedOpps = [SELECT Id,Name,Probability FROM Opportunity
        WHERE AccountId IN :Trigger.New];
          
    List<Opportunity> oppsToUpdate = new List<Opportunity>();
    // Iterate over the related opportunities
    for(Opportunity opp : relatedOpps) {      
        // Update the description when probability is greater 
        // than 50% but less than 100% 
        if ((opp.Probability >= 50) && (opp.Probability < 100)) {
            opp.Description = 'New description for opportunity.';
            oppsToUpdate.add(opp);
        }
    }
    
    // Perform DML on a collection
    update oppsToUpdate;
}

APEX单元测试

编写Test类基本步骤可以分成4步:

1.创建测试数据;

2.调用Test.startTest()方法;

3.调用需要测试的方法();

4.调用Test.stopTest()方法。

测试方法语法
第一种
@isTest static void testName() {
    // code_block
}
第二种
static testMethod void testName() {
    // code_block
}
第三种
@isTest
private class MyTestClass {
    @isTest static void myTest() {
        // code_block
    }
}

使用==System.assertEquals()==验证。它有两个参数:第一个是期望值,第二个是实际值。

@isTest static void testWarmTemp() {
	Decimal number = 10/2;
    System.assertEquals(5,number);
}

也可以有三个参数,第一个是期望值,第二个是实际值,第三个是测试失败后提示的语句。

@isTest static void testBoilingPoint() {
	Decimal number = 10/2;       
	// Simulate failure
	System.assertEquals(0,number,'number 不是预期的值,测试失败');
}

等于运算符(==)执行不区分大小写的字符串比较

在单元测试中,建立代码测试覆盖100%,最低要求75%。

蓝色(覆盖)线和红色(未覆盖)线

如何全部覆盖?

再写一个测试方法保证他能进到红色的代码中。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iIdPdtnW-1572587509254)(https://res.cloudinary.com/hy4kyit2a/f_auto,fl_lossy,q_70/learn/modules/apex_testing/apex_testing_intro/images/4177c5d150c4add6e968944ddd37e9de_apex_testing_code_coverage_partial.png)]

查看测试代码的覆盖程度
  1. 在运行完测试类或者测试套件后点击控制台下方的Tests选项卡
  2. 在右侧Overall Code Coverage下能看到每个测试类的结果。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hqqvj3Fo-1572587509255)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1571218273379.png)]
  3. 双击某一个测试类还能看到详细的覆盖情况。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CgiXdyGP-1572587509256)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1571218310818.png)]
测试套件
创建测试套件
  1. 点击 Test | New Suite
  2. 给测试套件命名
  3. 选择你需要测试的测试类[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KbJD9j5Y-1572587509257)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1571216920139.png)]
  4. 点击save保存套件。
执行测试套件
  1. 选择 Test | New Suite Run
  2. 选择你要测试的套件名称,然后单击 **>**移动套件名称到“Selected Test Suites ”列。
  3. 单击Run Suites
  4. 在“测试”选项卡上,监视测试运行的状态。展开测试运行,然后再次展开,直到看到已运行的各个测试的列表。就像在各种测试方法中一样,您可以双击方法名称以查看详细的测试结果。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zgBlccVF-1572587509258)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1571217135915.png)]
创建Apex测试的测试数据

我们需要在测试类本身创建测试类的数据。默认情况下,测试类不能访问组织数据,但是如果您设置@isTest(seeAllData = true),那么它将有权访问组织的数据。

在测试类中使用的DML,不会影响真实数据库的数据。并且你还能创建测试数据以便测试。如下:

@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++) {
            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];
            // For each account just inserted, add opportunities
            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));
            }
        }
        // Insert all opportunities for all accounts.
        insert opps;
        
        return accts;
    }
}

TestMethod关键字

单元测试方法是不带参数,不向数据库提交数据,不发送电子邮件,并在方法定义中使用testMethod关键字或isTest注释声明的方法。此外,测试方法必须在测试类中定义,即用isTest注释的类。

Test.startTest()Test.stopTest()

这些是可用于测试类的标准测试方法。这些方法包含我们将模拟我们的测试的事件或动作。就像在这个例子中,我们将测试我们的触发器和帮助类来模拟火灾触发器,通过更新记录,我们已经做了开始和停止块。这也为在开始和停止块中的代码提供单独的调节器限制。

System.assert()

此方法用实际检查所需的输出。在这种情况下,我们期望插入一个发票记录,所以我们添加了assert来检查。

@isTest
private class TestAccountDeletion {
    @isTest static void TestDeleteAccountWithOneOpportunity() {
        // Test data setup
        // Create one account with one opportunity by calling a utility method
        Account[] accts = TestDataFactory.createAccountsWithOpps(1,1);
        // Perform test
        Test.startTest();
        Database.DeleteResult result = Database.delete(accts[0], false);
        Test.stopTest();
        // Verify that the deletion should have been stopped by the trigger,
        // so check that we got back an error.
        System.assert(!result.isSuccess());
        System.assert(result.getErrors().size() > 0);
        System.assertEquals('Cannot delete account with related opportunities.',
                             result.getErrors()[0].getMessage());
    }        
}
``
private class TestAccountDeletion {
    @isTest static void TestDeleteAccountWithOneOpportunity() {
        // Test data setup
        // Create one account with one opportunity by calling a utility method
        Account[] accts = TestDataFactory.createAccountsWithOpps(1,1);
        // Perform test
        Test.startTest();
        Database.DeleteResult result = Database.delete(accts[0], false);
        Test.stopTest();
        // Verify that the deletion should have been stopped by the trigger,
        // so check that we got back an error.
        System.assert(!result.isSuccess());
        System.assert(result.getErrors().size() > 0);
        System.assertEquals('Cannot delete account with related opportunities.',
                             result.getErrors()[0].getMessage());
    }        
}
  • 0
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值