Salesforce 基础(1)

学习目标

完成本单元后,您将能够:

  • 定义Salesforce平台。
  • 描述DreamHouse场景。
  • 创建一个Trailhead游乐场。
  • 解释声明式和程序式开发之间的区别。

Salesforce快速入门

您可能认为Salesforce只是一个CRM。它存储您的客户数据,为您提供培训潜在客户的流程,并提供与您合作的人员进行协作的方法。它能完成所有这些事情。但是说Salesforce是“只是一个CRM”,就像说房子只是一个厨房。还有比这更多的东西。

Salesforce提供了许多标准功能,或者可用于运行业务的开箱即用产品和功能。以下是企业想要使用Salesforce的一些常见事项以及我们为您提供的支持这些活动的功能。

你需要: 所以我们给你:
销售给潜在客户和客户 潜在客户和机会来管理销售
售后帮助客户 客户参与的案例和社区
在旅途中工作 可定制的Salesforce移动应用程序
与同事,合作伙伴和客户合作 Chatter和社区连接你的公司
向您的观众展示市场 营销云来管理您的客户旅程

根据你的公司购买什么,你可以得到这些功能,而不是一个手指。但是你几乎可以把这些功能想象成一个房地产经纪人炫耀的样板房。你当然可以住在那里,但它不会是你的家。它不会有你的艺术在墙上或那个不寻常的外套架子,你的阿姨蒂尔达给你作为乔迁礼物。

这就是Salesforce平台的用武之地。有了这个平台,您可以自定义和构建让您的公司独一无二的东西。当你有一个独特的商业应用程序,每个人都更成功。

Salesforce的故事

在整个Trailhead中,您将以不同的方式介绍许多使用Salesforce的公司和角色。我们来见一些球员。

These four companies appear throughout Trailhead to help you learn about our services.
  1. 云踢—这家定制运动鞋公司正在制鞋行业掀起波澜。他们使用Salesforce管理销售,并帮助简化复杂的订单创建和执行流程。
  2. 大熊之光—在可再生能源的前沿,大熊之光需要商业软件,不会背离开创性的技术。他们使用Salesforce管理全国的销售和客户服务。
  3. 多云咨询 —作为业内最好的云咨询公司之一,多云知道CRM。他们使用Salesforce来管理现有的和潜在的客户,他们一直在寻找创新的Salesforce服务的新方法。
  4. DreamHouse Realty— 以其新颖的房地产方式而闻名,DreamHouse使用Salesforce连接员工,提高住宅销售的效率。

我们正在挖掘这个房子的主题,所以让我们看看DreamHouse Realty,开始我们的第一个模块。我们将使用DreamHouse的Salesforce实施来解释Salesforce平台的一些基本术语,概念和功能。

让我们多了解DreamHouse。

Michelle是DreamHouse的主要房地产经纪人。她通过DreamHouse的网络和移动应用程序发现了许多潜在的购房者。通过这些应用程序,客户可以浏览可用的房屋,并创建他们感兴趣的物业的最爱列表。他们也可以直接联系米歇尔或其他经纪人设置放映。

Michelle, the lead broker at DreamHouse.

D’Angelo是DreamHouse的Salesforce管理员。使用Salesforce平台,他正在构建一套自定义功能来支持Michelle及其团队。米歇尔可以使用这个自定义功能来编辑和查看有关她卖的物业的信息,以及跟踪她的潜在买家。

D'Angelo, the Salesforce Admin at DreamHouse.

请记住,Salesforce具有用于跟踪帐户,联系人和潜在客户等常见销售对象的标准功能。但是DreamHouse是一家房地产公司,所以它的行业和商业模式都有特定的需求。在本模块中,我们将与D’Angelo合作,了解Salesforce平台如何满足这些需求。

了解我们的条款

也许你在最后一段中注意到一个奇怪的词:对象。对象是您了解Salesforce时学到的许多重要术语之一。

首先,了解Salesforce上下文中的数据库非常重要。当我们谈论数据库时,想想一个巨大的电子表格。当您将信息放入Salesforce时,它将被存储在数据库中,以便稍后再次访问它。它存储在一个非常特定的方式,所以你总是访问你需要的信息。

我们来看一下DreamHouse应用中的一个页面,以定义它的一些重要元素以及它们与数据库的关系。

A labeled property record.
  1. Salesforce中的应用程序是一组支持业务流程的对象,字段和其他功能。您可以看到您正在使用的应用程序,并使用应用程序启动器 (App Launcher icon)在应用程序之间切换。.
  2. 对象是Salesforce数据库中用于存储特定类型信息的表。有像客户和联系人这样的标准对象和图形中看到的像Property对象这样的自定义对象。
  3. 记录是对象数据库表中的行。记录是与对象关联的实际数据。在这里,查尔斯街211号的房产是一个纪录。
  4. 字段是对象数据库表中的列。标准和自定义对象都有字段。在我们的Property对象中,我们有像Address和Price这样的字段。

组织中另一个难以捕捉的重要术语是org。组织是组织的简称,它指的是Salesforce的特定实例。这里的图片取自DreamHouse的组织。你的公司可以有一个或多个组织。

这是很多新的东西来解决。如果你不能立刻得到它,不要担心。在您继续了解Salesforce的过程中,术语将自然而然地出现。

你的第一个Trailhead游乐场

Trailhead Playground(TP)组织是一个安全的环境,您可以在将自己学习的技能练习到实际工作之前练习。 TP配备了测试应用程序开发版本所需的所有标准应用程序构建和定制工具。如果您听说过开发版(DE)组织,则TP是特殊类型的DE。

当您注册Trailhead时,我们会自动为您创建一个TP。所以,如果你还没有注册,现在是一个很好的时机。如果您已经登录,请滚动到该页面的底部,然后点击“启动”打开您的TP。

TP组织是免费的,一次最多可以有10个组织。要创建一个,请执行任何操作上的挑战,单击启动旁边的向下箭头,然后选择创建一个Trailhead游乐场..如果你打你最大或想要管理你的TP,你可以查看和删除你的Trailhead配置文件。如果您需要使用您的TP的用户名和密码,可以使用这里的说明访问它们。

继续前进,启动你的TP,这样我们就可以开始把我们的手弄脏了。

自定义Salesforce平台

您已经知道您可以使用Salesforce平台来开发特定于您的业务的自定义对象和功能。你可能不知道的是,你可以在不写一行代码的情况下完成大部分的开发工作。

无代码开发被称为声明式开发。通过声明式开发,您可以使用表单和拖放工具来执行强大的自定义任务。该平台还提供程序开发,使用Lightning组件,Apex代码和Visualforce页面等。但是如果你不是程序员,你仍然可以在平台上构建一些令人惊叹的东西。

我们从小开始。米歇尔希望能够迅速表明潜在的购房者是否被预先通过了房屋贷款。为了做出这个改变,D’Angelo想要在联系人对象上创建一个预审定的复选框。在Salesforce中,我们正在向标准对象添加自定义字段。让我们看看他是如何做到的。

  1. 从齿轮图标 (The gear icon to open Setup.), 单击Setup 启动设置页面。我们使用安装程序很多,所以请记住这一步!
  2. 单击 Object Manager 选项卡.
  3. 点击 Contact.
  4. 在字段和关系下,单击 New.
  5. 数据类型表示您的字段拥有哪种类型的信息。对于此字段,选择复选框,然后单击 Next.
  6. 字段标签是您在联系页面上看到的内容。输入资格预审?并点击 Next.
  7. 点击 Next 然后Save.

你只是定制你的第一个对象。做得好!

让我们来看看我们做了什么。打开 App Launcher The AppLauncher icon.并点击联系人。使用箭头列表视图箭头图标。查看所有联系人并单击联系人姓名。在“详细信息”选项卡下,您可以看到新的字段。现在米歇尔和其他经纪人更容易记录和检索这一重要的客户信息。

A contact detail page with the new Prequalified field displaying.

具有新的资格预审字段的联系人详细信息页面显示。
我们很快加入了这个字段。但事实证明,我们做的不仅仅是添加一个字段。同时,该平台也做了很多工作。显然,新的字段被添加到用户界面。您还可以运行报告并创建引用新字段的仪表板。该字段甚至已经准备好进入Salesforce移动应用程序。除了点击下一步之外,您无需执行任何操作。

这是Salesforce平台的强大功能。在下一个单元中,我们将讨论一些可以利用平台进行业务的方法。

Apex 测试(3)

学习目标

完成本单元后,您将能够:

  • 创建一个测试工具类。
  • 使用测试实用程序方法为各种测试用例设置测试数据。
  • 执行一个类中的所有测试方法。

为Apex测试创建测试数据

使用测试实用程序类为测试数据设置添加可重用的方法。

先决条件

如果您还没有这样做,请完成上一个单元“测试Apex触发器”中的先决条件。

添加一个测试工具类

让我们通过用一个实用程序类方法的调用替换测试数据创建来重构以前的测试方法。首先,您需要创建测试实用程序类。

TestDataFactory类是一种特殊的类,它是一个公共类,它是用isTest注释的,只能从正在运行的测试中访问。测试工具类包含可以被测试方法调用来执行有用任务的方法,例如设置测试数据。测试工具类从组织的代码大小限制中排除。

要添加TestDataFactory类:

  1. 在开发者控制台中,点击 File | New | Apex Class, 然后输入TestDataFactory作为类名,然后单击OK。
  2. 用下面的代码替换默认的类体。
    @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 (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 opps;
            
            return accts;
        }
    }
    此测试实用程序类包含一个静态方法createAccountsWithOpps(),该方法接受numAccts参数中保存的帐户数量以及为每个帐户(保存在numOppsPerAcct参数中)创建的相关机会数量。方法中的第一个循环创建了指定数量的帐户并将其存储在accts列表变量中。在第一个循环之后,将调用insert()DML语句在数据库的列表中创建所有帐户。

第二个循环创造了机会。由于每个机会组都链接到一个客户,所以外部循环通过客户进行迭代,并包含一个嵌套的循环,为当前客户创建相关的机会。下次运行嵌套循环时,使用add()方法将机会添加到同一个列表中。使用AccountId字段将机会链接到其父帐户。创建的所有机会总数是机会数量与客户数量的乘积(numOppsPerAcct * numAccts)。接下来,在循环之外有效地调用insert()DML语句,以便仅在一个调用中为所有帐户创建集合中的所有机会。

最后,这个方法返回新帐户的列表。

注意

尽管此方法不会返回相关的机会,但您可以通过编写SOQL查询来获取这些记录,该查询利用Account和Opportunity之间的现有关系,例如Testing Apex Triggers中的触发器中使用的查询。

调用测试数据创建的实用程序方法

现在您已经添加了测试实用程序类,请修改测试类以利用此类。在TestAccountDeletion类中,替换以// Test data setup开始的块,并以insert opp结束:

        // 测试数据设置
        // 通过调用实用程序方法创建一个拥有一个机会的帐户
        Account[] accts = TestDataFactory.createAccountsWithOpps(1,1);
TestDataFactory.createAccountsWithOpps(1,1)调用返回的数组包含一个Account sObject。

这是修改后的测试方法。更短的版本!

@isTest
private class TestAccountDeletion {

    @isTest static void TestDeleteAccountWithOneOpportunity() {
        // 测试数据设置
        // 通过调用实用程序方法创建一个拥有一个机会的帐户
        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('不能删除有相关机会的帐户.',
                             result.getErrors()[0].getMessage());
    }        
}

测试不同的条件

一种测试方法不足以测试触发器的所有可能输入。我们需要测试一些其他条件,例如何时删除没有机会的客户。我们还需要使用批量数量的记录来测试相同的方案,而不是只记录一个记录。这里是包含三个附加测试方法的测试类的更新版本。保存此类更新的版本。

@isTest
private class TestAccountDeletion {

    @isTest static void TestDeleteAccountWithOneOpportunity() {
        // 测试数据设置
        // 通过调用实用程序方法创建一个拥有一个机会的帐户
        Account[] accts = TestDataFactory.createAccountsWithOpps(1,1);
        
        // Perform test
        Test.startTest();
        Database.DeleteResult result = Database.delete(accts[0], false);
        Test.stopTest();

        // 验证删除是否应该被触发器停止,
        // 检查我们是否收到错误
        System.assert(!result.isSuccess());
        System.assert(result.getErrors().size() > 0);
        System.assertEquals('不能删除有相关机会的帐户',
                             result.getErrors()[0].getMessage());
    }
    
    @isTest static void TestDeleteAccountWithNoOpportunities() {
        // 测试数据设置
        // 通过调用实用程序方法创建一个没有机会的帐户
        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() {
        // 测试数据设置
        // 通过调用一个实用程序方法创建每个客户
        Account[] accts = TestDataFactory.createAccountsWithOpps(200,1);
        
        // 执行测试
        Test.startTest();
        Database.DeleteResult[] results = Database.delete(accts, false);
        Test.stopTest();

        // 验证每个记录。
        // 在这种情况下,删除应该已经被触发器停止,
        // 检查我们是否收到错误
        for(Database.DeleteResult dr : results) {
            System.assert(!dr.isSuccess());
            System.assert(dr.getErrors().size() > 0);
            System.assertEquals('不能删除有相关机会的帐户',
                                 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());
        }
    }
}
运行所有测试方法

最后一步是在我们的测试类中运行测试方法,现在该类包含更全面的测试,并被重构为使用测试数据工厂。由于您已经在TestAccountDeletion类中运行了测试,因此您可以重新运行此测试类以运行其所有测试方法。

  1. 要执行相同的测试运行,请单击测试选项卡,选择您的测试运行,然后单击Test | Rerun.
  2. 通过展开最新的测试运行来检查“测试”选项卡中的结果。测试运行应报告所有四个测试通过!

Apex 测试(2)

学习目标

完成本单元后,您将能够:

  • 编写对单个记录操作触发的触发器的测试。
  • 执行一个类中的所有测试方法。

测试Apex 触发器

在部署触发器之前,编写单元测试以执行触发触发器的操作并验证预期的结果。
让我们测试一下我们之前在Writing Apex Triggers单元中使用的触发器。如果一个客户记录有相关的机会,AccountDeletion触发器将阻止记录的删除。

先决条件

  1. 如果您尚未添加AccountDeletion触发器,请按照下列步骤操作。
    1. 在开发者控制台中,点击File | New | Apex Trigger.
    2. 输入AccountDeletion作为触发器名称,然后选择sObject的Account。点击Submit.
    3. 用下面的代码替换默认的代码。
      trigger AccountDeletion on Account (before delete) {
         
          // 如果他们有相关的联系人,防止删除帐户。
          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(
                  '不能删除有相关机会的帐号');
          }
          
      }
  2. 如果您在之前的单元中添加了AccountDeletion触发器,但已将其禁用,以便系统可以检查您的挑战,请重新启用它。
    1. 从设置中搜索 Apex Triggers.
    2. 在Apex触发器页面上,单击AccountDeletion触发器旁边的Edit
    3. 选择Is Active.
    4. 点击Save.
  3. 如果您的组织包含以前的单元(称为AddRelatedRecord,CalloutTrigger或HelloWorldTrigger)的触发器,请禁用它们。例如,要禁用AddRelatedRecord触发器:
    1. 从设置中搜索 Apex Triggers.
    2. 在Apex触发器页面上,单击AddRelatedRecord触发器旁边的 Edit .
    3. 取消选择Is Active.
    4. 点击Save.
  4. 要禁用HelloWorldTrigger和CalloutTrigger触发器,请重复上述步骤。

添加和运行单元测试

首先,我们开始添加一个测试方法。这个测试方法验证触发器设计要做什么(正面情况):防止一个帐户被删除,如果它有相关的机会。

  1. 在开发者控制台中,点击 File | New | Apex Class.
  2. 输入TestAccountDeletion作为类名称,然后单击 OK.
  3. 用下面的代码替换默认的类体。
    @isTest
    private class TestAccountDeletion {
    
        @isTest static void TestDeleteAccountWithOneOpportunity() {
            // 测试数据设置
            // 创建一个有机会的帐户,然后尝试删除它
            Account acct = new Account(Name='Test Account');
            insert acct;
            Opportunity opp = new Opportunity(Name=acct.Name + ' Opportunity',
                                           StageName='Prospecting',
    CloseDate=System.today().addMonths(1),
                                           AccountId=acct.Id);
            insert opp;
            
            // 执行测试
            Test.startTest();
            Database.DeleteResult result = Database.delete(acct, false);
            Test.stopTest();
    
            // 验证
            // 在这种情况下,删除应该已经被触发器停止,
            // 确认我们收到了错误
            System.assert(!result.isSuccess());
            System.assert(result.getErrors().size() > 0);
            System.assertEquals('不能删除有相关机会的帐户',
                                 result.getErrors()[0].getMessage());
        }
        
    }

    测试方法首先建立一个机会的测试帐户。接下来,它将删除测试帐户,该帐户触发AccountDeletion触发器。测试方法通过检查Database.delete()调用的返回值来验证触发器是否阻止删除测试帐户。返回值是一个Database.DeleteResult对象,其中包含有关删除操作的信息。测试方法验证删除不成功并验证获得的错误消息。

  4. 要运行此测试,请单击 Test | New Run.
  5. 在Test Classes下,单击TestAccountDeletion。
  6. 要将TemperatureConverterTest类中的所有方法添加到测试运行中,请单击添加 Add Selected.
  7. 点击Run.

    在最新运行的“测试”选项卡中找到测试结果。

TestAccountDeletion测试类只包含一个测试方法,用于测试单个帐户记录。此外,这个测试是针对正面的情况。始终测试更多方案,以确保触发器在所有情况下都能正常工作,包括删除没有机会的帐户和批量帐户删除。

测试数据是在测试方法内部设置的,添加更多的测试方法会耗费时间。如果您有许多测试方法,请将测试数据创建放在测试实用程序类中,并从多个测试方法中调用该实用程序类。下一个单元将向您展示如何利用测试工具类并添加更多的测试方法。

告诉我更多

测试方法包含Test.startTest()和Test.stopTest()方法对,该方法对代码块进行分隔,得到一组新的控制器限制。 在此测试中,测试数据设置在执行测试之前使用两个DML语句。 要测试Apex代码在限速范围内运行,请将数据设置的限制使用与测试隔离。 要隔离数据设置过程的限制使用,请将测试调用包含在Test.startTest()和Test.stopTest()块中。 测试异步Apex时也使用此测试块。 有关更多信息,请参阅使用限制,startTest和stopTest。

注意

开发人员控制台的一个已知问题是,在运行测试子集时,无法正确更新代码覆盖率。 要更新代码覆盖率结果,请使用 Test | Run All 而不是Test | New Run.

Apex 测试(1)

学习目标

完成本单元后,您将能够:

  • 描述Apex单元测试的主要优点。
  • 用测试方法定义一个类。
  • 执行班级中的所有测试方法并检查故障。
  • 创建并执行一组测试类。

Apex 单元测试

Apex测试框架使您能够为Force.com平台上的Apex类和触发器编写和执行测试。 Apex单元测试可确保您的Apex代码的高质量,并让您满足部署Apex的要求。

测试是成功实现长期发展的关键,也是开发过程的关键组成部分。 Apex测试框架可以轻松测试您的Apex代码。 Apex代码只能在沙箱环境或开发者组织中编写,而不能在生产环境中编写。 Apex代码可以从沙箱部署到生产组织。此外,应用程序开发人员可以通过将软件包上传到Force.com AppExchange,将Apex代码从开发人员组织分发给客户。除了对质量保证至关重要之外,Apex单元测试也是部署和分销Apex的要求。以下是Apex单元测试的好处。

  • 确保您的Apex类和触发器按预期工作
  • 拥有一套可以在每次更新类和触发器时重新运行的回归测试,以确保您对应用程序进行的未来更新不会破坏现有的功能
  • 满足部署Apex生产或通过包装向客户分销Apex的代码覆盖要求
  • 高质量的应用程序交付给生产组织,使生产用户的工作效率更高
  • 高品质的应用程序交付给包用户,这增加了客户的信任

注意

在每次重大服务升级之前,Salesforce都会通过名为Apex Hammer的流程代表您运行所有Apex测试。 Hammer进程在当前版本和下一版本中运行,并比较测试结果。此过程可确保您的自定义代码中的行为未因服务升级而改变。锤子过程选择性地选择组织,并且不运行在所有组织。发现的问题是基于特定的标准进行分类的。 Salesforce致力于解决每个新版本之前发现的所有问题。

维护数据的安全性是我们的首要任务。我们不会查看或修改您的组织中的任何数据,并且所有测试都是在安全的数据中心中运行的副本中完成的。

代码覆盖要求的部署

在部署代码或将其打包为Force.com AppExchange之前,至少有75%的Apex代码必须由测试覆盖,所有这些测试都必须通过。另外,每个触发器都必须有一定的覆盖范围。尽管代码覆盖率是部署的要求,但是不要只写测试来满足这个要求。确保在您的应用中测试常见用例,包括正面和负面的测试用例,以及批量和单一记录处理。

测试方法语法

测试方法不带任何参数,并具有以下语法:

@isTest static void testName() {
    // code_block
}
或者,一个测试方法可以有这样的语法:
static testMethod void testName() {
    // code_block
}
使用isTest注释而不是testMethod关键字更灵活,因为您可以在注释中指定参数。我们稍后会介绍一个这样的参数。

测试方法的可见性并不重要,因此将测试方法声明为公共或私有并不会造成影响,因为测试框架总是能够访问测试方法。为此,语法中省略了访问修饰符。

测试方法必须在测试类中定义,这些类是用isTest注释的类。这个样本类用一种测试方法显示了一个测试类的定义。

@isTest
private class MyTestClass {
    @isTest static void myTest() {
        // code_block
    }
}
测试类可以是私有的或公共的。如果您仅使用测试类进行单元测试,则将其声明为私有。公共测试类通常用于测试数据工厂类,稍后会介绍。

单元测试示例:测试TemperatureConverter类

下面这个简单的例子是三个测试方法的测试类。正在测试的类方法以华氏温度作为输入。它将此温度转换为摄氏温度并返回转换后的结果。让我们添加自定义类和它的测试类。

  1. 在开发者控制台中,点击 File | New | Apex Class, 然后输入TemperatureConverter作为类名称,然后单击OK.
  2. 用下面的代码替换默认的类体。
    public class TemperatureConverter {
        // 拍摄华氏温度并返回摄氏温度。
        public static Decimal FahrenheitToCelsius(Decimal fh) {
            Decimal cs = (fh - 32) * 5/9;
            return cs.setScale(2);
        }
    }
  3. 按下Ctrl + S保存你的课程。
  4. 重复之前的步骤来创建TemperatureConverterTest类。添加以下这个类。
    @isTest
    private class TemperatureConverterTest {
    
        @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);
        }
          
    }
    TemperatureConverterTest测试类通过以华氏温度的不同输入调用该方法来验证该方法是否按预期工作。每种测试方法验证一种类型的输入:暖温度,冰点温度,沸点温度和负温度。验证是通过调用System.assertEquals()方法完成的,该方法有两个参数:第一个是期望值,第二个是实际值。这个方法有另一个版本,它接受第三个参数 – 一个描述比较的字符串,在testBoilingPoint()中使用。如果断言失败,则会记录此可选字符串。

让我们来运行这个类中的方法。

  1. 在开发者控制台中,点击 Test | New Run.
  2. 在Test Classes下,单击TemperatureConverterTest.
  3. 要将TemperatureConverterTest类中的所有测试方法添加到测试运行,请单击 Add Selected.
  4. 点击Run.
  5. 在“测试”选项卡中,您可以看到运行中的测试状态。展开测试运行,然后再次展开,直到看到运行的单个测试列表。他们都有绿色的选中标记。
    Inspect test results in the Developer Console

运行测试后,将自动为组织中的Apex类和触发器生成代码覆盖率。您可以在开发者控制台的“测试”标签中查看代码覆盖百分比。在这个例子中,你测试过的类,TemperatureConverter类,具有100%的覆盖率,如图所示。

View code coverage percentage in the Developer Console

注意

无论何时您修改Apex代码,请重新运行测试以刷新代码覆盖率结果。

开发人员控制台的一个已知问题是,在运行测试子集时,无法正确更新代码覆盖率。要更新代码覆盖率结果,请使用Test | Run All 而不是Test | New Run.

虽然一种测试方法会导致TemperatureConverter类的全面覆盖,但测试不同的输入以确保代码质量仍然很重要。显然,不可能验证每个数据点,但可以测试常见的数据点和不同的输入范围。例如,您可以验证传递正数和负数,边界值和无效参数值以验证负面行为。 TemperatureConverter类别的测试验证常用数据点,如沸腾温度和负温度。

TemperatureConverterTest测试等级不包括无效输入或边界条件。边界条件是关于最小值和最大值。在这种情况下,温度转换方法接受一个可以接受大于Double值的Decimal。对于无效的输入,没有无效的温度,但唯一的无效输入为空。转换方法如何处理这个值?在这种情况下,当Apex运行时将参数变量解引用以评估公式时,它会引发System.NullPointerException。您可以修改FahrenheitToCelsius()方法来检查无效输入,并在这种情况下返回null,然后添加一个测试来验证无​​效的输入行为。

到目前为止,所有的测试都通过了,因为类方法中使用的转换公式是正确的。但是那很无聊!让我们尝试模拟一个失败,看看断言失败时会发生什么。例如,让我们修改沸点温度测试,并传递沸点摄氏温度(0而不是100)的错误预期值。这导致相应的测试方法失败。

  1. 将testBoilingPoint()测试方法更改为以下。
        @isTest static void testBoilingPoint() {
            Decimal celsius = TemperatureConverter.FahrenheitToCelsius(212);        
            // 模拟失败
            System.assertEquals(0,celsius,'不期望沸点温度');
        }
  2. 要执行相同的测试运行,请单击测试选项卡中的最新运行,然后单击 Test | Rerun.

    testBoilingPoint()中的断言失败,并引发一个致命错误(一个无法捕获的AssertException)。

  3. 通过展开最新的测试运行来检查“测试”选项卡中的结果。测试运行报告四分之一的测试失败。要获得有关失败的更多详细信息,请双击测试运行。

    详细的结果显示在单独的选项卡中,如图所示。

    Inspect results of a failed test in the Developer Console
  4. 要获取测试失败的错误消息,请双击失败测试的Errors列。你会看到以下内容。 Assertion Failed旁边的描述性文本是我们在System.assertEquals()语句中提供的文本。

    System.AssertException: Assertion Failed: 不期望沸点温度.: Expected: 0, Actual: 100.00

这些测试方法中的测试数据是数字而不是Salesforce记录。您将了解更多有关如何测试Salesforce记录以及如何在下一单元中设置数据的信息。

增加您的代码覆盖率

在编写测试时,尽可能实现最高的代码覆盖率。不要只瞄准75%的覆盖率,这是Force.com平台对部署和软件包的最低覆盖率。测试覆盖的测试用例越多,代码的健壮性就越高。有时,即使在为所有类方法编写测试方法之后,代码覆盖率也不是100%。一个常见原因是没有涵盖条件代码执行的所有数据值。例如,当你的类方法有if语句时,一些数据值往往会被忽略,这些语句会根据是否满足条件评估条件而导致不同的分支被执行。确保您的测试方法考虑到这些不同的值。

此示例包含类方法getTaskPriority(),它包含两个if语句。这种方法的主要任务是根据给定的导联状态返回一个优先级字符串值。该方法首先验证状态,如果状态无效则返回null。如果状态是CA,则方法返回“高”;否则,返回任何其他状态值的“正常”。

public class TaskUtil {
    public static String getTaskPriority(String leadState) {
        // 验证输入
        if (String.isBlank(leadState) || leadState.length() > 2) {
            return null;
        }
            
        String taskPriority;
        
        if (leadState == 'CA') {
             taskPriority = 'High'; 
        } else {
             taskPriority = 'Normal';
        }
        
        return taskPriority;
    }
}
注意

等号运算符(==)执行不区分大小写的字符串比较,因此不需要先将字符串转换为小写字母。这意味着传入’ca’或’Ca’将满足字符串字面值’CA’的相等条件。

这是getTaskPriority()方法的测试类。测试方法只需调用一个状态(’NY’)的getTaskPriority()。

@isTest
private class TaskUtilTest {
    @isTest static void testTaskPriority() {
        String pri = TaskUtil.getTaskPriority('NY');
        System.assertEquals('Normal', pri);
    }
}
让我们在开发者控制台中运行这个测试类(TaskUtilTest),并检查测试覆盖的相应TaskUtil类的代码覆盖率。测试运行结束后,TaskUtil的代码覆盖率显示为75%。如果您在开发人员控制台中打开此课程,则会看到六个蓝色(被覆盖)线条和两个红色(未被覆盖)线条,如图所示。
Lines covered for the TaskUtil class in the Developer Console

第五行没有被覆盖的原因是因为我们的测试类没有包含一个测试来传递一个无效的状态参数。同样,第11行没有被覆盖,因为测试方法没有通过“CA”作为状态。我们再添加两个测试方法来覆盖这些情况。以下显示了添加testTaskHighPriority()和testTaskPriorityInvalid()测试方法后的完整测试类。如果您重新运行此测试类,TaskUtil的代码覆盖率现在为100%!

@isTest
private class TaskUtilTest {
    @isTest static void testTaskPriority() {
        String pri = TaskUtil.getTaskPriority('NY');
        System.assertEquals('Normal', pri);
    }
    
    @isTest static void testTaskHighPriority() {
        String pri = TaskUtil.getTaskPriority('CA');
        System.assertEquals('High', pri);
    }
    
    @isTest static void testTaskPriorityInvalid() {
        String pri = TaskUtil.getTaskPriority('Montana');
        System.assertEquals(null, pri);
    }
}
创建并执行测试套件

测试套件是一起运行的Apex测试类的集合。例如,创建一套您每次准备部署时运行的测试,或者Salesforce发布新版本。在开发者控制台中设置一个测试套件来定义一组定期一起执行的测试类。

您现在在您的组织中有两个测试课程。这两个阶级是不相关的,但让我们暂时假装他们是。假设有些情况下你想运行这两个测试类,但不想运行你的组织中的所有测试。创建一个包含两个类的测试套件,然后在套件中执行测试。

  1. 在开发者控制台中,选择 Test | New Suite.
  2. 输入TempConverterTaskUtilSuite作为套件名称,然后单击OK.
  3. 选择TaskUtilTest,按住Ctrl键,然后选择TemperatureConverterTest
  4. 要将所选测试类添加到套件,请单击 >.

    Test suite editing window with two selected test classes

  5. 点击Save.
  6. 选择Test | New Suite Run.
  7. 选择TempConverterTaskUtilSuite,然后单击>将TempConverterTaskUtilSuite移动到选择的测试套件列。
  8. 点击Run Suites.
  9. 在“测试”选项卡上,监视测试运行状态。展开测试运行,然后再次展开,直到看到运行的单个测试列表。就像在单个测试方法的运行中一样,您可以双击方法名称以查看详细的测试结果。

创建测试数据

在测试方法中创建的Salesforce记录不会提交到数据库。当测试结束执行时,它们会回滚。这个回滚行为对于测试是很方便的,因为在测试执行后你不必清理你的测试数据。

默认情况下,除了访问设置和元数据对象(如User或Profile对象)外,Apex测试不能访问组织中预先存在的数据。为您的测试设置测试数据。创建测试数据可使您的测试更健壮,并防止组织中缺少或更改数据导致的故障。您可以直接在测试方法中创建测试数据,也可以使用实用程序测试课程,稍后您将会看到。

注意

尽管这样做不是最佳实践,但有时候测试方法需要访问预先存在的数据。要访问组织数据,请使用@isTest注释测试方法(SeeAllData = true)。本机中的测试方法示例不访问组织数据,因此不使用SeeAllData参数。

告诉我更多…

  • 您可以在每个组织中最多保存3 MB的Apex代码。用@isTest注释的测试类不计入此限制。
  • 即使测试数据回滚,也不会使用单独的数据库进行测试。因此,对于某些具有唯一约束的字段的sObjects,插入重复的sObject记录会导致错误。
  • 测试方法不发送电子邮件。
  • 测试方法不能调出外部服务。您可以在测试中使用模拟标注。
  • 在测试中执行的SOSL搜索返回空结果。为了确保可预测的结果,使用Test.setFixedSearchResults()来定义搜索返回的记录。

Salesforce 开发经验(5)

学习目标

完成本单元后,您将能够:

  • 确定可以转向模块化(基于工件)方法的用例。
  • 确定一个不适用于基于工件的开发的场景。

下一站:规划您向Salesforce DX的过渡

现在您已经了解了如何使用Salesforce DX并认识到它的价值,您有兴趣继续研究它。那么你如何开始使用它?这取决于您的生产组织和相关开发流程的复杂性和成熟度。这里有一些建议可以帮助你开始。

寻找方法来解放组织成文物

评估你的开发过程的所有方面,寻找可能的方法转移到基于模块化神器的方法。在生产组织中寻找与其他一切不同的应用程序。你有不同的团队来建立和维护这些应用程序吗?如果是这样,你可以将这些应用程序分离成它们自己的工件。 AppExchange有许多独立应用程序的绝佳示例,它们遵循将源和元数据集合分离为单个工件的思路。

在某些情况下,您不会有可以拆分为工件的独特应用程序,但是您会发现一段时间内您的工作组中已经有不同的部分。例如,您的核心应用程序的扩展可能会作为工件发布。您可以将您在定制公司销售流程中所做的所有扩展分离成一个工件。如果您可以隔离特定于这些部分的元数据,则可以使用它来开发工件。

您也可以寻找已经或者希望与其他人分开制作和交付的团队。有些团队可能正在寻找更灵活和更灵活的机会 – 他们希望将他们的变更与其生产组织的更大变更管理流程分开。这些团队可以隔离他们的元数据并将其存储在自己的工件中。

注意共享元数据

一路上,确保您评估共享元数据组件的所有潜在工件。您不希望将共享元数据无意中隔离到特定团队或应用程序拥有的工件。如果元数据组件是共享的,我们建议您将这些共享组件组织到单个基本工件中。通过这种方式,您可以确保所有工件都可以引用共享工件中的组件。 (请记住,元数据组件一次只能在一个工件中生存。)

检索元数据源

识别出潜在的工件后,您将使用Metadata API来检索与工件相关的来源。使用Salesforce DX模块查看应用程序开发,了解如何使用Salesforce CLI和测试组织来创建标识工件组件的package.xml。一旦你提取了源,为每个工件创建一个VCS存储库。从那里,您可以通过构建特定于这些应用程序的发布周期来继续分离过程。

罗马不是一天建成的

你是一个只使用基于组织的开发模式的开发人员,还是以管理员身份开始职业生涯?那么Salesforce DX源代码驱动模型是一个接受更灵活和更灵活的开发流程的绝好机会。

如果你的组织有一个成熟的或者复杂的组织,那么随着时间的推移,转向源驱动的模型需要发生。你的生产组织是你最珍视的财产,所以要谨慎计划你的转变。使用本单元中的指导来确定您可以转移到Salesforce DX的部分组织。每次移动一件神器,并继续评估和改进您的流程。

现在您已经对Salesforce DX开发模型有了更多的了解,现在是时候通过尝试将指甲弄脏了。

Salesforce 开发经验(4)

学习目标

完成本单元后,您将能够:

  • 描述如何支持各种类型的测试。
  • 描述沙箱在部署中的角色。

工件测试和持续集成使用临时组织

如何使用Salesforce DX进行测试,构建和发布,是从当前应用程序生命周期转变而来的。然而,Salesforce DX提供了一些重要的优势。

当您准备好对您的开发工作进行手动/探索性测试时,请创建元数据并将其推送到为此目的指定的独立临时组织中。你永远不会从这个组织中提取任何东西,因为它只被用于测试/验证。

持续集成(CI)是针对与应用程序合并的每一组更改自动执行一致的测试运行。这可确保在任何损坏的更改进入源代码库之前的应用程序质量。

临时组织可以很容易地集成到CI流程中。 CLI可以创建临时组织,因此将它们脚本化为CI流程是一件小事。您可以使用适当版本的源存储库填充组织,并对特定更改运行测试。

与开发人员沙箱不同的是,可以全天创建抓取组织,而不是每天刷新一次。您可以删除一个临时组织,并在需要时快速创建一个新组织。你可以有多个划痕组织为不同的目的。从零开始组织给你一个很大的灵活性和非常有限的开销。

转换为元数据API格式进行构建

构建和部署过程与当前基于组织的交付方法相似。这意味着元数据API转换和部署过程将继续处理构建和部署用例。在您转换回Metadata API源代码后,您的所有工件源都可以部署。您可以部署所有源,部署操作负责更新已更改的文件。如果您需要从部署中省略某些文件,则可以通过构建package.xml文件来构建要部署的内容。

完成单元测试后,您就可以将源和元数据部署到沙箱。但首先,您需要以Salesforce DX格式获取项目中当前的源代码和元数据,并将其转换回Metadata API格式。然后,您可以使用Salesforce CLI将其部署到组织中。您可以在App Development with Salesforce DX模块中使用CLI尝试部署过程。

使用沙箱连续交付

为了持续交付,您希望开始测试与部署到生产组织时相同的过程。在这个用例中,您需要使用您在构建阶段创建的元数据API包进行测试,并将其部署在沙箱上,这是生产组织的最佳表示形式。在沙箱中,您可以复制和测试将要发布到生产组织的步骤。

Salesforce 开发经验(3)

学习目标

完成本单元后,您将能够:

  • 描述Salesforce DX项目的目的。
  • 描述Salesforce DX如何帮助您管理更改跟踪。
  • 解释在开发过程中划伤组织的作用。

现在乐趣开始了

正如我们在前面的单元中所了解的那样,使用Salesforce DX,您可以决定使用哪些工具。您可以将您最喜爱的文本编辑器与Salesforce CLI或Salesforce Extensions for VS代码结合使用。您可以选择要使用的VCS。如果使用Salesforce Extensions for VS Code,我们提供了几个扩展来帮助用Salesforce DX进行开发。

对于源代码驱动的开发,您的源代码将根据您想要一起交付的一组功能或自定义设置组织为工件。 Salesforce DX项目反映了这种基于工件的方法来组织您的源代码。

什么是Salesforce DX项目?

Salesforce DX项目是工件来源和Salesforce DX元数据的本地目录结构,可让您使用Salesforce DX工具进行开发和测试。

它包含用于创建临时组织的配置文件。它可以包含要加载到组织以供开发或测试的数据。它还应该包含您依赖验证您的工件的测试。

当您使用CLI创建新的Salesforce DX项目时,它会为您创建项目目录结构。从零开始创建项目时,会为您创建许多事物。我们创建一个基础项目配置文件。我们为您的测试和样本数据集创建示例临时定义文件和目录。我们还为您的工件源创建了一个默认的“包”目录。

请记住,工件是一组相关的代码和自定义项。您可以独立于组织中的其他组件测试工件。一个神器也可以独立释放。工件中的元数据组件一次只能存在一个工件。

该项目至少管理一个工件的来源。也就是说,如果多个构件一起构建并发布,则可以将这些构件组织到单个SFDX项目中。每个工件都与项目配置文件中定义的软件包目录对齐。

如何抓住发展进程

从您的源代码和元数据构建的临时组织,使您可以轻松地一遍又一遍地构建应用程序。您只能使用特定项目的源和元数据。没有必要复制你不需要的东西(坦率地说,我们不推荐它)。由于临时组织是临时的Salesforce环境,因此可以为每个工件或开发项目快速创建新的临时组织。

一旦你的VCS被设置好了,你的源代码被组织到工件中,你就可以开始一个新的开发项目了。打开你最喜欢的IDE或文本编辑器,然后添加或修改你的源代码。当您准备好在组织中查看更改时,可以创建一个临时组织。

在创建一个scratch组织之后,您仍然需要完成一些设置任务。您需要将项目中的所有源码都推送到临时组织,设置权限,以及创建或加载使用工件所需的任何测试数据。

虽然IDE或文本编辑器可用于编程(基于代码)的开发,但您可以使用scratch org进行声明式(指向并单击)开发,就像您在沙盒或生产组织中进行的基于组织的开发模型。源驱动模型的不同之处在于,您可以将您从头开始的任何开发与本地项目同步。这使您可以提交设置页面中所做的更改以及在本地IDE中所做的更改。

在提交到版本控制系统之前,请确保运行测试。您可以使用相同的scratch org来运行测试,也可以在提交源代码之前专门进行测试。同样的测试模式将成为自动化持续集成系统的关键部分。

保持你的项目和组织同步

Salesforce DX的一个主要特点是,您可以轻松地保持项目和暂存组织的同步。所以你可以放弃那些粘滞便笺!您不必记下在本地文件系统,IDE或编辑器中更改的内容,或者在组织中更改的内容。

Salesforce DX跟踪您在项目中本地进行的任何更改以及您在从头开始的任何更改。

在将源元数据更改推送到临时组织之前,或者从临时组织中将更改提交到本地项目之前,可以查看您所做的更改列表。这就是Salesforce CLI在行动中的力量。

$ sfdx force:source:status

STATE                     FULL NAME    TYPE        PROJECT PATH
─────                     ──────────   ──────────  ─────────────────────────────────
Local Deleted             MyClass      ApexClass   /MyClass.cls-meta.xml
Local Deleted             MyClass      ApexClass   /MyClass.cls.xml
Local Add                 OtherClass   ApexClass   /OtherClass.cls-meta.xml
Local Add                 OtherClass   ApexClass   /OtherClass.cls.xml
Local Add                 Event        QuickAction /Event.quickAction-meta.xml
Remote Deleted            TempClass    ApexClass   /TempClass.cls-meta.xml
Remote Deleted            TempClass    ApexClass   /TempClass.cls.xml
Remote Changed (Conflict) NewClass     ApexClass   /NewClass.cls-meta.xml
Remote Changed (Conflict) NewClass     ApexClass   /NewClass.cls.xml
在生产组织中,源文件可能非常大,占用空间大于Sasquatch。 考虑组成一个组织的所有自定义对象,自定义标签和静态资源,等等。

Salesforce DX解决了这个问题,它提供了一个新的源代码形式,可以分解这些大的源文件,使它们更容易理解,并且更易于使用版本控制系统进行管理。 例如,Salesforce DX将自定义对象和自定义对象翻译转换为多个文件和目录。 这个源结构使得更容易找到你想要更改或更新的内容。 源代码管理中的较小文件意味着团队开发过程中的合并冲突更少。 只要说再见,凌乱的合并!

完成开发之后,请始终将您的更改回复到VCS回购。 现在您已准备好使用Salesforce DX进行测试,构建和发布。

Salesforce 开发经验(2)

学习目标

完成本单元后,您将能够:

  • 描述Salesforce命令行界面(CLI)提高生产力的方式。
  • 在新的开发人员体验中描述版本控制系统,临时组织和沙箱组织的作用。
  • 描述新的从头开发环境提供的功能。

现有工具,新工具,您的工具

在Force.com平台上开发的特性之一是用于开发和部署的独特工具。一些工具很熟悉,比如Eclipse和ANT。有些是平台独有的,比如变更集和沙箱。总是有API来尝试集成到您喜欢的工具中,但这并不容易 – 尤其是考虑到Force.com平台的独特发布模式。

Salesforce DX的指导原则之一是支持与开发工具相关的开放标准。我们希望提供一种基础结构,使您能够使用熟悉的工具链,同时还提供一套建议的工具,如果您尚未使用任何工具,则可以使用这些工具。

Salesforce DX development flow lifecycle

Salesforce命令行界面

如果Salesforce DX是一辆汽车,则Salesforce CLI将是其方向盘。如果Salesforce CLI是你的朋友,那将是你最好的朋友。你明白了。您可以使用CLI从命令行管理整个应用程序生命周期。而且它也做菜! (不,不是真的,但不是很好?)

CLI结合了来自多个Salesforce API的许多功能,例如Metadata API和Packaging API。它还结合了其他Salesforce工具的功能,如Force.com迁移工具和Salesforce工作台。所有在一个地方。因为它在命令行,所以它是可以编写脚本的。想想你可以创建的所有酷脚本,使重复的​​开发任务更容易!

computer screens with CLI commands

Salesforce CLI是一个机会均等的生产力增强器:

  • 开发人员可以使用它来管理他们的Salesforce DX项目,创建(开发)临时组织,将资源和元数据推送到临时组织或从其中取出,并运行单元测试。
  • DevOps可以将其用作构建自动化脚本的一部分,从源代码创建环境并运行测试。

VS代码的Salesforce扩展

将Visual Studio(VS)代码与Salesforce Extensions for VS Code结合使用时,您将获得一个强大的集成开发环境,该开发环境专门用于Salesforce平台上的定制开发。这些扩展提供:

  • 与Salesforce CLI进行交互的功能
  • 访问Apex语言服务器进行语法高亮显示和代码完成
  • 支持Lightning组件捆绑
  • 支持Visualforce页面和组件
  • 支持实时Apex调试器

它也与Git预集成,但可以与其他版本控制系统一起使用。

版本控制系统

VCS是源驱动开发的核心。您需要使用VCS来管理和版本化源,以充分利用Salesforce DX提供的功能。

所以如果你目前没有使用VCS,你怎么开始这个旅程呢?使用Salesforce DX,您的本地项目与存储库绑定。使用每个存储库保存您为工件完成的所有工作的历史记录。使用分支来跟踪每个版本的更改。每个项目至少包含一件神器。在更复杂的组织中,您可能会发现需要将多个相关的工件作为同一个工程的一部分进行开发。当一组组件和自定义取决于其他组件时,会发生这种情况。这在后面的单元中会有更多的讨论。

新的孩子在块:划痕组织

你是否厌倦了其他的孩子玩你的沙箱?当您使用划痕组织作为开发和测试过程的一部分时,您不会在您的眼睛里看到沙子。

设计为短暂且容易重新创建的,从头开始是适合的,可配置的Salesforce环境,您可以快速启动以实现许多不同的目的。他们可以成为你自己的个人发展环境,或者你可以创建无头的自动化测试。如果您想要:您可能会启动一个新的备份组织:

  • 开始一个新的项目。
  • 开始一个新的功能分支。
  • 测试一个新功能。
  • 开始自动化测试。
  • 直接在组织中执行开发任务。
  • 从一个新的组织“从头开始”。

您可以使用不同的Salesforce版本配置临时组织,只配置所需的功能和首选项。您可以与其他团队成员共享scratch org配置文件,这样您就可以拥有相同的基本组织机构来完成您的开发任务。

沙箱仍扮演重要角色

尽管我们认为创业公司将会摇摆您的世界并提高您的工作效率,但沙箱仍然是Salesforce开发生命周期中非常重要的一部分。 您仍将使用它们进行用户验收测试,作为从源代码构建的部署目标,作为临时环境,以及用于持续交付测试。 将源代码开发用例与您的临时组织对齐,并将发布/部署测试与您的沙箱对齐。 但更多的是在后来的单位。

Salesforce 开发经验(1)

学习目标

完成本单元后,您将能够:

  • 描述传统的整体组织开发模型与模块化开发的不同之处。
  • 列举源驱动工件开发的优势。
  • 描述一个神器的关键特征。

全世界都是一个组织

一位着名的演员,或者也许是一位Salesforce的幻想家,曾经说过:“世界上所有的组织都是这样。”传统上,这个世界的中心是你的生产组织,而且你已经在一个沙箱或生产组织。 (如果你是一个AppExchange的合作伙伴,你的世界有点不一样,但是本书中介绍的新工具也可以使用,请继续阅读。)

Salesforce Developer Experience(DX)引入了一种新的开发模式,将您的世界中心从基于组织的单一开发转变为模块化,基于工件的开发。该模型简化了整个开发生命周期,具有以下好处:

  • 改善团队发展和协作。
  • 便于自动化测试和持续集成。
  • 使发布周期更加高效和灵活。

在本单元中,我们引导您了解Salesforce DX的全新世界。获得此徽章后,我们建议您查看带有Salesforce DX模块的应用程序开发,以便更多地了解Salesforce DX如何在实践中使用。

我们现在的世界:组织发展

让我们来看看您的开发团队如何在基于组织的Salesforce开发模型中工作。

你是一个快节奏的高科技公司的发展领导者。对于您的版本,您需要定制核心CRM应用程序,并且还希望为您的公司构建一个内部应用程序。开发的第一步是确保获得生产组织的最新快照。在基于组织的模型中,您的生产组织是所有代码,配置和自定义的真实来源。

无论您正在构建什么,您最终都会创建适用于您的生产组织的部署。如图所示,即使您有多个团队在单独的开发项目上工作,他们也会使用相同的部署来开发和发布更新。一切都进入一个package.xml。

org-based dev flow from dev to build to single release

对于第一个版本,假设您正在开发两个新项目:

(1) 项目 (1) 开发 (2) 元数据API(package.xml)
CRM扩展/自定义(第一版) 自定义对象(添加)

自定义字段(添加)

页面布局(添加)

工作流程(添加)

Apex 类: 1

Apex 页面: 1

自定义对象: 2

自定义字段: 2

页面布局: 1

工作流程: 1

暂停管理器应用程序(第一版) 自定义对象(添加)

自定义字段(添加)

Apex 类(添加)

Apex 页面(添加)

(3) – 一切都在生产组织中一起发布。

对于第二个版本,你正在做一些小的更新。但是,您仍然同时将两个项目发布并部署到生产组织:

(1) 项目 (1) 开发 (2) 元数据API(package.xml)
CRM扩展/自定义(第一版) 自定义对象(更新)

工作流程(更新)

Apex 页面: 1

自定义对象: 2

工作流程: 1

休假管理器应用程序(第一版) 自定义对象(更新)

Apex 页面(更新)

(3) – 一切都在生产组织中一起发布。

我们将此模型称为基于组织的开发,因为发布或部署是关于生产组织的。您的开发人员和发布经理将组织视为混合的代码和自定义集合。为了把这个观点带回家,再看看图。最终的部署不在“延时管理器”应用程序或CRM扩展范围内;它包括对组织的所有更改。

随着您的发展,您必须跟踪您正在改变的内容,以确保您知道要将什么部署到生产组织。你的改变与其他人改变了,所以这可能是一个棘手的,有点手动的过程。如果在这个过程中使用源代码管理,源代码管理系统就会体现出混合代码和定制的相同感觉。您将源代码库与组织的一部分(例如,“休假管理器”应用程序)对齐。

但是如果能够开发一种改进开发和发布工作流程的新范例呢?

Salesforce DX如何不同:基于工件的开发

Salesforce DX旨在通过提供与多个开发团队一起管理复杂组织所需的重大改进来重塑您的开发流程。为了实现这些改进,Salesforce DX将开发模式从单一的基于组织的开发流程转变为基于模块化的基于工件的开发流程。

Salesforce DX提供:

  • 通过更改跟踪安装程序功能改进版本控制系统(VCS)同步
  • 通过持续集成(CI)和持续交付(CD)来提高质量和上市时间的能力,
  • 更细粒度的可视性和清晰的生产组织的变更管理
  • 实施更灵活的版本管理流程的能力

这是什么意思?代替构建组织的代码和自定义,您可以将代码和自定义构建到表示组织的子集的工件(一组逻辑代码)。

那么什么是神器?工件是一组相关的代码和定制。工件可以独立于组织中的其他组件进行测试。一个神器也应该能够独立发布。工件中的元数据组件一次只能存在于一个工件中。

工件内的组件可以代表很多东西。工件可以是为支持销售团队而创建的一组定制。工件可以是组织中构建的应用程序的Lightning组件,对象和工作流程。或者,工件可以是您在从AppExchange安装的托管包周围构建的扩展。也就是说,AppExchange包本身是一个神器,但第三方拥有它。

VCS是开发人员最好的朋友,在Salesforce DX开发生命周期中起着不可或缺的作用。使用Salesforce DX,可以将工件的所有源代码存储在源代码管理存储库中;这就是真理源头所在的地方。您可以从该源构建Salesforce DX开发组织,以便专门处理您的工件。我们提供更改跟踪功能,以监视您在开发组织中创建,更新和删除的内容,以便您可以轻松地将修改后的源代码下载到您的文件系统,并将其检入到您的VCS中。

通过这个新的流程,您可以将您的组织组织为一组工件。通过将源代码和元数据组织到工件中,您可以更好地理解组织中元数据组件之间的关系。你的组织越大,这个过程就变得越重要,所以要尽早规划,总是在考虑如何组织你的组织。

基于模块化工件的开发使您可以更灵活地管理团队和版本。您可以指定小组拥有特定的工件。开发团队可以单独开发并构建工件发布版本,而不是发布组织更新。使用这个敏捷模型,您可以获得更频繁的独立版本,就像您在开发,构建和部署流程中看到的那样。

artifacts-based dev flow from dev to build to release

在本例中,对于第一个版本,您将创建两个新项目。它们的版本均为1.0,可以构建,然后使用元数据API单独部署到生产组织。

(1) 开发 (2) 构建 (3) 发布(package.xml)
CRM扩展/自定义v1.0 自定义对象(添加)

自定义字段(添加)

页面布局(添加)

工作流程(添加)

自定义对象:1

自定义字段:1

页面布局:1

工作流程:1

暂停管理器应用v1.0 自定义对象(添加)

自定义字段(添加)

Apex 类(添加)

Apex 页面(添加)

Apex 类:1

Apex页面:1

自定义对象:1

自定义字段:1

对于下一个版本,您可以再次独立构建和部署每个工件到生产组织。这样,您可以为每个工件维护截然不同的版本。

(4) 开发 (5) 构建 (6) 发布(package.xml)
CRM扩展/自定义v1.1 自定义对象(更新)

工作流程(更新)

自定义对象:1

自定义字段(不变)

页面布局(不变)

工作流程:1

暂停管理器应用程序v2.0 自定义对象(更新)

Apex页面(更新)

Apex 类(不变)

Apex 页面:1

自定义对象:1

自定义字段(不变)

这也延伸到CI和CD。构建工件可让您创建专门为该项目设计的测试计划。您可以使用Salesforce DX工具自动执行测试计划,通过在多个环境中运行测试(通过VCS修改源代码)来确保连续的质量水平。

Apex REST外部调用

学习目标

完成后,您将能够:

  • 执行外呼以接收来自外部服务的数据。
  • 执行外呼将数据发送到外部服务。
  • 使用模拟标注测试外呼。

HTTP和外呼基础

REST外呼基于HTTP。要了解外呼如何工作,了解一些有关HTTP的内容很有帮助。每个外呼请求都与HTTP方法和端点相关联。 HTTP方法指示需要什么类型的动作。
Apex callouts to an external service

最简单的请求是一个GET请求(GET是一个HTTP方法)。 GET请求意味着发送者想要从服务器获取关于资源的信息。当服务器接收并处理此请求时,它将请求信息返回给收件人。 GET请求与导航到浏览器中的地址相似。当您访问网页时,浏览器会在幕后执行GET请求。在浏览器中,导航的结果是显示的新HTML页面。用外呼,结果是响应对象。

为了说明GET请求的工作方式,打开浏览器并导航到以下URI: https://th-apex-http-callout.herokuapp.com/animals.您的浏览器以奇怪的格式显示动物列表,因为服务以JSON格式返回响应。有时GET响应也是用XML格式化的。

以下是常用HTTP方法的说明。

表1.一些常用的HTTP方法

HTTP方法 描述
GET 检索由URL标识的数据
POST 创建资源或将数据发布到服务器。
DELETE 删除由URL标识的资源。
PUT 创建或替换请求正文中发送的资源。

如果您有空闲时间,请在参考资料部分浏览所有HTTP方法的详尽列表。

除了HTTP方法之外,每个请求都会设置一个URI,这是服务所在的端点地址。例如,一个端点可以是 http://www.example.com/api/resource.在 “HTTP and Callout Basics” 单元中的示例中,端点是https://th-apex-http-callout.herokuapp.com/animals.

当服务器处理请求时,它在响应中发送一个状态码。状态码指示请求是否成功处理或是否遇到错误。如果请求成功,服务器会发送一个200的状态码。您可能已经看到一些其他的状态码,例如404没有找到文件,500没有找到内部服务器错误。

如果在浏览HTTP方法列表后仍然有空闲时间,请查看参考资料部分的所有响应状态代码列表。如果你晚上睡得很困难,这两个资源可以帮助你。

从服务获取数据

现在是时候把你的新HTTP知识用于一些Apex标注。本示例向Web服务发送GET请求以获取林地生物列表。该服务以JSON格式发送响应。 JSON本质上是一个字符串,所以内置的JSONParser类将它转换为一个对象。然后,我们可以使用该对象将每个动物的名称写入调试日志。
在本机运行示例之前,您需要使用“授权端点地址”部分中的步骤来授权标注的端点URL , https://th-apex-http-callout.herokuapp.com, 使用“授权端点地址”部分的步骤。
  1. 从设置档(打开设备齿轮图标)打开开发者控制台。 (Setup gear icon).
  2. 在开发者控制台中,选择 Debug | Open Execute Anonymous Window.
  3. 删除现有的代码并插入下面的代码片段
    Http http = new Http();
    HttpRequest request = new HttpRequest();
    request.setEndpoint(‘https://th-apex-http-callout.herokuapp.com/animals’);
    request.setMethod(‘GET’);
    HttpResponse response = http.send(request);
    // 如果请求成功,则解析JSON响应。
    if (response.getStatusCode() == 200) {
    // 将JSON字符串反序列化为原始数据类型的集合。
    Map<String, Object> results = (Map<String, Object>) JSON.deserializeUntyped(response.getBody());
    // “动物”键中的值作为列表进行投射
    List<Object> animals = (List<Object>) results.get(‘animals’);
    System.debug(‘Received the following animals:’);
    for (Object animal: animals) {
    System.debug(animal);
    }
    }
  4. 选择 Open Log, 然后单击Execute.
  5. 调试日志打开后,选择 Debug Only 查看System.debug语句的输出。
    显示动物的名字。

我们例子中的JSON相当简单并且易于解析。对于更复杂的JSON结构,可以使用JSON2Apex。该工具生成用于解析JSON结构的强类型的Apex代码。您只需粘贴JSON,该工具就会为您生成必要的Apex代码。荣耀!

发送数据到服务

HTTP 外呼的另一个常见用例是将数据发送到服务。例如,当您购买最新的贾斯汀·比伯(Justin Bieber)专辑或评论您最喜欢的“Cat in a Shark Costume Chases a Duck While Riding a Roomba”视频时,您的浏览器正在提交POST请求以提交数据。我们来看看我们如何在Apex中发送数据。
本示例向Web服务发送POST请求以添加动物名称。新名称作为JSON字符串添加到请求正文中。请求Content-Type头设置为让服务知道发送的数据是JSON格式,以便它可以适当地处理数据。该服务通过发送状态代码和所有动物列表(包括您添加的动物)作出响应。如果请求已成功处理,则状态码将返回201,因为已创建资源。如果返回201以外的任何内容,则将响应发送到调试日志。
  1. 从设置档(打开设备齿轮图标)打开开发者控制台 (Setup gear icon).
  2. 在开发者控制台中,选择 Debug | Open Execute Anonymous Window.
  3. 删除任何现有的代码并插入以下片段。
    Http http = new Http();
    HttpRequest request = new HttpRequest();
    request.setEndpoint(‘https://th-apex-http-callout.herokuapp.com/animals’);
    request.setMethod(‘POST’);
    request.setHeader(‘Content-Type’, ‘application/json;charset=UTF-8’);
    // 将主体设置为JSON对象
    request.setBody(‘{“name”:”mighty moose”}’);
    HttpResponse response = http.send(request);
    // 解析JSON响应
    if (response.getStatusCode() != 201) {
    System.debug(‘返回的状态代码不是预期的: ‘ +
    response.getStatusCode() + ‘ ‘ + response.getStatus());
    } else {
    System.debug(response.getBody());
    }

  4. 选择打开 Open Log, 然后单击 Execute.
  5. 调试日志打开时,请选择 Debug Only 查看System.debug语句的输出。动物列表中的最后一项是 “mighty moose”.

测试外呼

外呼测试有好消息和坏消息。坏消息是Apex测试方法不支持外呼,执行标注的测试失败。好消息是测试运行时允许你“mock”外呼。模拟标注允许您指定在测试中返回的响应,而不是实际调用Web服务。你实际上在告诉运行时,“我知道这个web服务将会返回什么,所以不要在测试过程中调用它,而是返回这个数据。”在你的测试中使用模拟标注有助于确保你获得足够的代码覆盖率,代码由于标注而被跳过。

先决条件

在编写测试之前,让我们创建一个类,其中包含我们在“将数据发送到服务”单元中匿名执行的GET和POST请求示例。这些示例稍有修改,以便请求在方法和返回值中,但它们与前面的示例基本相同。

  1. 在开发者控制台中,选择 File | New | Apex Class.
  2. 对于课程名称,请输入AnimalsCallouts 然后单击OK.
  3. 将自动生成的代码替换为以下类定义。

    public class AnimalsCallouts {

    public static HttpResponse makeGetCallout() {
    Http http = new Http();
    HttpRequest request = new HttpRequest();
    request.setEndpoint(‘https://th-apex-http-callout.herokuapp.com/animals’);
    request.setMethod(‘GET’);
    HttpResponse response = http.send(request);
    //如果请求成功,则解析JSON响应。
    if (response.getStatusCode() == 200) {
    // 将JSON字符串反序列化为原始数据类型的集合。
    Map<String, Object> results = (Map<String, Object>) JSON.deserializeUntyped(response.getBody());
    // 将“animals”键中的值作为列表进行投射
    List<Object> animals = (List<Object>) results.get(‘animals’);
    System.debug(‘Received the following animals:’);
    for (Object animal: animals) {
    System.debug(animal);
    }
    }
    return response;
    }

    public static HttpResponse makePostCallout() {
    Http http = new Http();
    HttpRequest request = new HttpRequest();
    request.setEndpoint(‘https://th-apex-http-callout.herokuapp.com/animals’);
    request.setMethod(‘POST’);
    request.setHeader(‘Content-Type’, ‘application/json;charset=UTF-8’);
    request.setBody(‘{“name”:”mighty moose”}’);
    HttpResponse response = http.send(request);
    // 解析JSON响应
    if (response.getStatusCode() != 201) {
    System.debug(‘预期不会返回状态码: ‘ +
    response.getStatusCode() + ‘ ‘ + response.getStatus());
    } else {
    System.debug(response.getBody());
    }
    return response;
    }

    }

  4. 按下 CTRL+S 保存..

使用StaticResourceCalloutMock测试外呼

要测试您的标注,请通过实现接口或使用静态资源来使用模拟标注。在这个例子中,我们稍后使用静态资源和模拟接口。静态资源包含要返回的响应主体。同样,使用模拟标注时,请求不会发送到端点。相反,Apex运行时知道查找在静态资源中指定的响应,并返回它。 Test.setMock方法通知运行时在测试方法中使用模拟标注。让我们看看模拟标注在行动。首先,我们创建一个包含JSON格式字符串的静态资源,用于GET请求。

  1. 在开发者控制台中,选择 File | New | Static Resource.
  2. 对于名称,输入GetAnimalResource.
  3. 对于MIME类型,选择text/plain, 即使使用JSON.
  4. 点击Submit.
  5. 在为资源打开的选项卡中,插入以下内容。确保它全部在一行上,不会中断到下一行。这个内容就是模拟标注返回的内容。这是三个林地生物阵列。
    {“animals”: [“pesky porcupine”, “hungry hippo”, “squeaky squirrel”]}
  6. 点击CTRL+S保存.

您已成功创建您的静态资源!现在,让我们为使用此资源的标注添加一个测试。

  1. 在开发者控制台中,选择 File | New | Apex Class.
  2. 对于类名称,输入 AnimalsCalloutsTest 然后单击OK.
  3. 将自动生成的代码替换为以下测试类定义。

    @isTest
    private class AnimalsCalloutsTest {

    @isTest static void testGetCallout() {
    // 基于静态资源创建模拟响应
    StaticResourceCalloutMock mock = new StaticResourceCalloutMock();
    mock.setStaticResource(‘GetAnimalResource’);
    mock.setStatusCode(200);
    mock.setHeader(‘Content-Type’, ‘application/json;charset=UTF-8’);
    // 外呼与模拟响应相关联
    Test.setMock(HttpCalloutMock.class, mock);
    // 调用方法来测试
    HttpResponse result = AnimalsCallouts.makeGetCallout();
    // 验证模拟响应不为空
    System.assertNotEquals(null,result,
    ‘标注返回了一个空响应。’);
    // 验证状态码
    System.assertEquals(200,result.getStatusCode(),
    ‘状态码不是200.’);
    // 验证内容类型
    System.assertEquals(‘application/json;charset=UTF-8’,
    result.getHeader(‘Content-Type’),
    ‘The content type value is not expected.’);
    // 验证数组包含3个项目
    Map<String, Object> results = (Map<String, Object>)
    JSON.deserializeUntyped(result.getBody());
    List<Object> animals = (List<Object>) results.get(‘animals’);
    System.assertEquals(3, animals.size(),
    ‘数组只应该包含3个项目’);
    }

    }

  4. 点击CTRL+S 保存.
  5. 选择 Test | Always Run Asynchronously. 如果不选择“始终运行异步”,则测试运行只包含一个同步运行的类。您只能从“测试”选项卡打开日志,以进行同步测试运行。
    要运行测试,请选择Test | New Run.
  6. 从Test Classes列表中选择 AnimalsCalloutsTest.
  7. 点击Add Selected | Run.

测试结果显示在测试运行ID下的测试选项卡中。测试执行完成后,展开测试运行以查看详细信息。现在在“代码总体覆盖率”窗格中双击AnimalCallouts,查看测试覆盖了哪些行。

使用HttpCalloutMock测试外呼

为了测试你的POST标注,我们提供了一个HttpCalloutMock接口的实现。此接口使您能够指定在响应方法中发送的响应。您的测试类指示Apex运行时通过再次调用Test.setMock发送此假响应。对于第一个参数,传递HttpCalloutMock.class。对于第二个参数,传递一个AnimalsHttpCalloutMock的新实例,它是您的HttpCalloutMock的接口实现。 (在这个例子之后的例子中,我们将写AnimalsHttpCalloutMock)

Test.setMock(HttpCalloutMock.class, new AnimalsHttpCalloutMock());

现在添加实现HttpCalloutMock接口的类来拦截标注。如果在测试上下文中调用HTTP标注,则不会进行标注。相反,您会收到您在AnimalsHttpCalloutMock中的响应方法实现中指定的模拟响应。

  1. 在开发者控制台中,选择 File | New | Apex Class.
  2. 对于类名,输入 AnimalsHttpCalloutMock然后单击OK.
  3. 自动生成的代码替换为以下类定义。
    @isTest
    global class AnimalsHttpCalloutMock implements HttpCalloutMock {
    // 实现这个接口方法
    global HTTPResponse respond(HTTPRequest request) {
    // 创建一个假的回应
    HttpResponse response = new HttpResponse();
    response.setHeader(‘Content-Type’, ‘application/json’);
    response.setBody(‘{“animals”: [“majestic badger”, “fluffy bunny”, “scary bear”, “chicken”, “mighty moose”]}’);
    response.setStatusCode(200);
    return response;
    }
    }
  4. 点击CTRL+S保存.

在您的测试类中,创建testPostCallout方法来设置模拟标注,如下例所示。 testPostCallout方法调用Test.setMock,然后调用AnimalsCallouts类中的makePostCallout方法。然后验证返回的响应是您在模拟实现的响应方法中指定的。

  1. 修改测试类AnimalsCalloutsTest添加第二个测试方法。单击类选项卡,然后在右括号之前添加以下方法。
    @isTest static void testPostCallout() {
    // 设置模拟外呼类
    Test.setMock(HttpCalloutMock.class, new AnimalsHttpCalloutMock());
    //这会导致一个假的响应被发送
    // 从实现HttpCalloutMock的类中
    HttpResponse response = AnimalsCallouts.makePostCallout();
    //验证收到的响应是否包含假值
    String contentType = response.getHeader(‘Content-Type’);
    System.assert(contentType == ‘application/json’);
    String actualValue = response.getBody();
    System.debug(response.getBody());
    String expectedValue = ‘{“animals”: [“majestic badger”, “fluffy bunny”, “scary bear”, “chicken”, “mighty moose”]}’;
    System.assertEquals(actualValue, expectedValue);
    System.assertEquals(200, response.getStatusCode());
    }
  2. 点击CTRL+S 保存.
  3. 选择 Test | New Run.
  4. 从Test Classes列表中选择AnimalsCalloutsTest.
  5. 点击 Add Selected | Run.

    测试结果显示在“测试”选项卡下的新测试运行ID下。当测试执行完成时,展开测试运行以查看有关这两个测试的详细信息。

更多…

了解如何在触发器和异步Apex中使用标注,以及如何制作异步标注。
当从一个方法进行标注时,该方法在执行后续代码行之前,等待外部服务发回标注响应。或者,您可以将标注代码置于用@future(callout = true)注释的异步方法中,或者使用Queueable Apex。这样,标注在单独的线程上运行,调用方法的执行不会被阻止。

从触发器进行标注时,标注不得在等待响应时阻止触发过程。为了使触发器能够进行标注,包含标注代码的方法必须使用@future(callout = true)进行注释,以便在单独的线程中运行。

自己动手尝试

创建一个调用REST端点的Apex类并编写一个测试类。

为了通过这个练习,创建一个调用REST端点的Apex类来返回一个动物的名字,编写单元测试,使用模拟响应实现类的100%代码覆盖率,并运行Apex测试。

  • Apex类必须被称为“AnimalLocator”,有一个“getAnimalNameById”方法接受一个Integer并返回一个String。
  • ‘getAnimalNameById’方法必须使用传入方法的ID调用https://th-apex-http-callout.herokuapp.com/animals/:id。 该方法返回“name”属性(即动物名称)的值。
  • 创建一个名为AnimalLocatorTest的测试类,该类使用名为AnimalLocatorMock的模拟类模拟标注响应。
  • 单元测试必须涵盖AnimalLocator类中包含的所有代码行,从而得到100%的代码覆盖率。
  • R在尝试验证这个挑战之前,至少运行一次测试类(通过“全部运行”测试开发者控制台)。