博客 RSS 订阅

更好的 Cordova 插件测试方法
作者:John M. Wargo
2018年2月2日

Cordova 开发者有多种方法来测试和调试他们的 Cordova 应用程序。对于功能测试,开发者使用模拟器、仿真器和物理设备。设备可以是本地的,也可以有许多可用的云服务。甚至有一些很棒的工具可以用来调试你的应用程序,例如 Chrome 和 Safari 的 Web 应用调试功能,以及微软 Visual Studio Code 针对 Apache Cordova 的出色调试扩展。

对于调试插件,或调试使用 Cordova 插件的应用程序,情况还不错。对于大多数插件,我想象任何物理设备都有运行插件所需的一切,除非该插件需要某些外部硬件设备或具有并非每个设备都满足的其他要求。对于几个核心 Cordova 插件,设备模拟器和仿真器公开了各种功能,使测试人员能够模拟诸如摄像头、加速度计、指南针和其他设备端功能(尽管令人惊讶的是,早期的 iOS 模拟器不支持摄像头模拟)。

当涉及到测试插件的所有功能时,特别是模拟错误情况以便你可以了解应用程序如何响应时,事情就变得复杂了。开发者经常发现自己在插件代码中进行修改,要么模拟仿真场景,要么在测试期间手动更改插件的行为。在许多情况下,开发者必须手动强制插件中的错误情况,以便他们可以验证应用程序中的错误检查。我没有编写过很多 Cordova 插件,但在我做过的少量工作中,我希望有更好的方法。嗯,事实证明确实有。

很多很多年前,一家名为 Tiny Hippos 的小公司创建了 Ripple 模拟器,这是一个针对许多移动设备的基于浏览器的模拟器。Ripple 在许多方面都很有趣,但就本文的范围而言,一个有趣的特性是它为许多核心 Cordova 插件实现的模拟器。当你在一个窗格中运行 Cordova 应用程序时,一个单独的窗格会打开,其中包含控制模拟环境的许多方面的选项,如下图所示。

Figure 1

好吧,长话短说,Research In Motion (BlackBerry) 的人员购买了 Tiny Hippos,并维护了 Ripple 一段时间,然后最终将其作为孵化项目贡献给了 Apache 基金会。许多公司都参与了进来,包括 Adobe、Microsoft 和其他公司,但该项目从未真正起飞,也从未成为一个稳定的产品。它实际上从未走出测试阶段。

无论如何,快进到今天,你会发现微软接手了 Ripple 项目并对其进行了重建。我们保留了一些原始代码(一些插件模拟面板和支持实用程序函数),重写了一些部分,创建了一些新代码,然后将其作为名为 Cordova Simulate 的开源项目发布(https://github.com/Microsoft/cordova-simulate)。我们选择这种方法,而不是投资于 Ripple,是因为

  • 我们经常收到关于 Ripple 启动的问题报告,以及 Visual Studio 无法成功连接到它,因此我们希望使用更简单的架构来渲染 Cordova Web 应用程序(一个单独的浏览器窗口,只托管应用程序,而没有其他任何内容)。
  • 调试 Ripple 是一项挑战,因为你实际上是在调试应用程序和 Ripple 本身(你必须深入研究 Ripple UI 的 HTML 才能找到托管的 Cordova Web 应用程序,并且在调试时,Ripple 的 JavaScript 代码很容易成为你堆栈的一部分)。
  • 由于 Ripple 和 Cordova Web 应用程序在同一浏览器窗口中渲染,如果 Cordova Web 应用程序因任何原因冻结,Ripple 也会冻结(或者,在不太严重的情况下,当 Cordova Web 应用程序忙碌时,Ripple UI 可能会看起来没有响应)。

Cordova Simulate 解决了这些问题

  • 应用程序窗口也不托管模拟 UI - 只有应用程序(和一小段代码,用于启用与模拟 UI 的通信)。这解决了上面确定的前两个问题。
  • 模拟 UI 在单独的窗口中渲染,并且所有通信都是异步的,因此无响应的应用程序不会干扰模拟 UI。这解决了第三个问题。

Cordova Simulate 的另一个驱动目的,是我们希望使其具有可扩展性;允许插件定义自己的模拟功能(正如你很快将了解到的)。

关于我之前说的 Ripple 从未走出测试阶段,Cordova Simulate 不仅是一个完整、健壮且已发布的解决方案,它甚至还是几个微软产品(商业的和开源的)的一部分。它捆绑在 Visual Studio 2017 中的 Visual Studio Tools for Apache Cordova (TACO - http://taco.visualstudio.com/) 中。它也包含在 Visual Studio Code 的 Apache Cordova 扩展中 (https://github.com/Microsoft/vscode-cordova)。

为什么这对你很重要?好吧,让我解释一下…

借助 Cordova Simulate,你现在可以访问一个完整的解决方案,用于针对核心 Cordova 插件的模拟版本测试你的应用程序。这消除了修改代码以模拟插件响应的需要,因为 Cordova Simulate 会为你处理。

注意:如果你只是读了上一段,并且对自己说“那又怎样?”请稍等片刻。

对你来说最重要的好处是,你也可以为每个自定义插件添加对 Cordova Simulate 的支持。然后,当你在插件开发期间测试插件时,或者当你的客户使用你的插件时,你有一种标准的方法来模拟插件的功能(包括错误场景)。当第三方提供商为其插件添加对 Cordova Simulate 的支持时,开发者就可以突然使用各种插件准确地测试其应用程序的每个方面。

让我向你展示如何安装和使用 Cordova Simulate,然后我将向你展示如何为你的自定义插件添加对 Cordova Simulate 的支持。如果你正在使用其他人的插件,而他们的插件不支持 Cordova Simulate,请自己添加并提交拉取请求,这并不难。

安装 Cordova Simulate

正如我之前提到的,Visual Studio Tools for Apache Cordova 和 Visual Studio Code 的 Cordova 扩展都包含 Cordova Simulate,因此无需遵循额外的安装说明 - 只需安装这些工具,你就会拥有所需的一切。

你也可以从命令行或第三方 IDE 调用 Cordova Simulate。要为这些场景安装 Cordova Simulate,请打开一个终端窗口,并执行以下命令

npm install -g cordova-simulate

就这样,仅此而已。当该过程完成时,你现在可以使用 simulate 命令来启动 Cordova Simulate。

启动 Cordova Simulate

以下各节描述了如何以不同的方式启动 Cordova Simulate。

Visual Studio

如果你正在 Visual Studio 和 TACO 中运行应用程序,只需选择 在浏览器中模拟 选项,应用程序就会启动 Cordova Simulate。Cordova Simulate 将打开 Chrome 浏览器并运行你的 Cordova 应用程序的 Web 应用程序部分。Cordova Simulate 还将在 Visual Studio 中打开一个模拟器控制窗口(使用 Internet Explorer,不足为奇)。你将在 Chrome 窗口中与应用程序交互,并使用模拟器控制窗口模拟插件中的方法和属性。我稍后会向你展示如何操作。

Visual Studio Code

如果你正在使用 Visual Studio Code,请转到 调试 选项卡,启用 Cordova 调试,然后执行 在浏览器中模拟 Android在浏览器中模拟 iOS 选项。Cordova Simulate 将打开 Chrome 浏览器并执行你的 Cordova 应用程序的 Web 应用程序部分。Cordova Simulate 还将在 Visual Studio Code 内打开一个模拟器控制窗口。你将在 Chrome 窗口中与应用程序交互,并使用模拟器控制窗口模拟插件中的方法和属性。我稍后会向你展示如何操作。

命令行

要从命令行启动 Cordova Simulate,请打开一个终端窗口,导航到 Cordova 项目文件夹,然后执行以下命令

simulate \[platform\]

平台指的是支持的目标平台之一(例如:androidiosbrowser)。例如,要模拟你的 Cordova 应用程序的 Android 版本,你将使用以下命令

simulate android

Cordova Simulate 将在 Chrome 浏览器中打开两个标签页,一个运行 Cordova 应用程序 Web 应用程序,另一个显示如下图所示的模拟器控制窗口。

Figure 2

默认情况下,Cordova Simulate 加载一组默认的插件模拟器

  • 由于 HTML 原生支持地理定位,因此 Cordova Simulate 会自动加载地理定位模拟器窗格。
  • 事件模拟器窗格会自动加载,以允许开发者根据需要触发 Cordova 和设备事件
  • 持久执行调用窗格为不包含 Cordova Simulate 支持的第三方插件启用基本模拟器支持。
  • 设备模拟器会自动加载,允许你更改用于渲染 Web 应用程序的目标设备。

如果我向我的项目添加了其他 Cordova 核心插件,Cordova Simulate 将为每个插件加载模拟器(如果可用)。

此时,你可以根据需要与你的应用程序交互,并切换到模拟器控制窗口以调整插件属性和方法调用结果。例如,如果你的 Cordova 应用程序使用地理定位插件来跟踪设备的位置,则更改地理定位模拟器窗格中的任何值将导致应用程序下次调用 navigator.geolocation.getCurrentPosition 时接收到你输入到窗格中的更新值。

为你的插件添加 Cordova Simulate 支持

好了,是时候向你展示如何为自己的插件添加 Cordova Simulate 支持了。首先,你必须在插件的 src 文件夹中添加一个 simulation 文件夹(即 src/simulation/)。在该文件夹中,你将根据插件的需要创建一个或多个以下文件

  • app-host-clobbers.js
  • app-host-handlers.js
  • app-host.js
  • sim-host-dialogs.html
  • sim-host-handlers.js
  • sim-host-panels.html
  • sim-host.js

对于我的简单示例,我只需要三个文件,但请参阅 https://github.com/Microsoft/cordova-simulate 上的 Cordova Simulate 文档,了解有关每个文件的详细信息。

我为我的示例使用的插件是我多年前为我的一本 Cordova 书创建的简单 Carrier 插件:https://npmjs.net.cn/package/johnwargo-cordova-plugin-carrier。它公开了两种方法

  • getCarrierName:一种异步方法,用于检索运行应用程序的设备的当前运营商。
  • getCountryCode:一个异步方法,用于检索运行应用程序的设备的国家/地区代码。

在练习此插件时,我需要能够根据不同的运营商和国家/地区代码值验证我的应用程序代码。为了模拟这种情况,我需要一个 HTML 面板,提供多个运营商和国家/地区代码选择。为此,我在插件项目的 src/simulation/ 文件夹中创建了一个 sim-host.panels.html 文件。该文件创建了一个简单的面板,其中包含两个下拉列表,其中包含一些国家/地区和运营商值。

<cordova-panel id="johnwargo-cordova-plugin-carrier" caption="Carrier" 
  spoken-text="Carrier">
    <cordova-panel-row>
        Select carrier and country code options from the drop-down lists below.
    </cordova-panel-row>
    <cordova-panel-row>
        <cordova-label label="Carrier Name" spoken="carrier name"></cordova-label>
        <cordova-combo spoken-text="Simulated value for wireless carrier" 
           id="carrier-select" style="width:auto; min-width:0; display:inline;">
            <option value="AT&T" selected="selected">AT&T</option>
            <option value="Sprint">Sprint</option>
            <option value="T-Mobile">T-Mobile</option>
            <option value="US Cellular">US Cellular</option>
            <option value="Verizon">Verizon</option>
        </cordova-combo>
    </cordova-panel-row>
    <cordova-panel-row>
        <cordova-label label="Country Code" spoken="country code"></cordova-label>
        <cordova-combo spoken-text="Simulated value for country code" 
          id="country-code-select" style="width:auto; min-width:0; display:inline;">
            <option value="CA">Canada</option>
            <option value="MX">Mexico</option>
            <option value="US" selected="selected">United States</option>
        </cordova-combo>
    </cordova-panel-row>
    <cordova-panel-row>
      Enable the error checkbox to execute the error callback instead of the success callback on plugin API calls.
    </cordova-panel-row>
    <cordova-checkbox id="is-error" spoken="">Error condition</cordova-checkbox>
</cordova-panel>

正如您从代码中看到的,该面板使用了 Cordova Simulate 支持的特殊 HTML 元素类型。大多数核心 Cordova 插件的模拟器代码都包含在 Cordova Simulate Github 存储库中,因此您可以在那里找到正在使用的可用 UI 元素的示例。

接下来,我向该文件夹添加了一个 sim-host.js 文件。这个文件不是必须的,但它提供了一个初始化模拟插件的机会,我使用它在用户在模拟面板中进行更改时更新控制台。这并不是至关重要的,但在我构建模拟时,它有助于我理清发生了什么。

module.exports = {
    initialize: function() {
        //Get access to the carrier selection drop-down
        var carrierSel = document.getElementById('carrier-select');
        //Add a change event listener to the field
        carrierSel.addEventListener('change', carrierChange);
        //Get access to the country code selection drop-down
        var ccSel = document.getElementById('country-code-select');
        //Add a change event listener to the field
        ccSel.addEventListener('change', ccChange); 

        function carrierChange() {
            console.log("Carrier selection changed to " + this.value);
        }
 
        function ccChange() {
            console.log("Country code selection changed to " + this.value);
        }
    }
};

如果您研究核心 Cordova 插件中模拟器功能的源代码,您会看到它们中的许多都使用代码中刚刚展示的更改事件来更新插件公开的对象的属性。当开发人员在模拟器面板中更改值时,更改事件会注册更改并更新本地对象。然后,当 Cordova 应用程序访问这些属性之一时,代码会从本地对象返回该值。

最后,我向项目添加了一个 sim-host-handlers.js 文件。此文件中的代码覆盖了模拟环境中插件的 cordova.exec 调用。在这里,我导出了插件支持的本机方法,并从模拟器面板中提取选定的值,然后将它们返回给调用应用程序。

module.exports = function(messages) {
    return {
        carrier: {
            getCarrierName: function(successCallback, errorCallback) {
                console.log("Cordova-Simulate: getCarrierName invoked");
                //Get access to the carrier selection drop-down
                var carrierSel = document.getElementById('carrier-select');
                //Pull the value from the selected item
                var selValue = carrierSel.options[carrierSel.selectedIndex].value;
                console.log('Simulating carrier: ' + selValue);
                //And return it to the calling method
                successCallback(selValue);
            },
            getCountryCode: function(successCallback, errorCallback) {
                console.log("Cordova-Simulate: getCountryCode invoked");
                //Get access to the country code selection drop-down
                var ccSel = document.getElementById('country-code-select');
                //Pull the value from the selected item
                var selValue = ccSel.options[ccSel.selectedIndex].value;
                console.log('Simulating country code: ' + selValue);
                //And return it to the calling method
                successCallback(selValue);
            }
        }
    };
};

这是一个非常简单的示例,展示了您可以做的事情,请务必查看核心 Cordova 插件中包含的模拟器功能的源代码,以获取更复杂的示例。

现在,当您创建一个使用此插件的 Cordova 应用程序,然后执行 Cordova Simulate 时,您将在 Cordova Simulate Simulator Control 窗口上看到以下面板。

Figure 3

对面板进行相应的更改,然后从 Cordova 应用程序调用相关的 API,以查看选定的结果。

如果您考虑一下我们目前所做的事情,我们只解决了成功的情况 - 更改插件在模拟环境中的行为,以便我们可以使用不同的 API 结果来测试应用程序。大多数插件还会报告错误情况,为了正确地练习插件或使用该插件的应用程序,您还必须能够模拟错误情况。让我来告诉你如何做到这一点。

首先,我在 sim-host.panels.html 文件中添加了一个新行。

<cordova-panel-row>
  Enable the error checkbox to execute the error callback instead of the success callback on plugin API calls.
</cordova-panel-row>
<cordova-checkbox id="is-error" spoken="">Error condition</cordova-checkbox>

这会在面板中添加一个错误复选框,使开发人员能够从每个插件 API 调用返回错误。更新后的 sim-host.panels.html 文件如下所示

<cordova-panel id="johnwargo-cordova-plugin-carrier" caption="Carrier" 
  spoken-text="Carrier">
    <cordova-panel-row>
        Select carrier and country code options from the drop-down lists below.
    </cordova-panel-row>
    <cordova-panel-row>
        <cordova-label label="Carrier Name" spoken="carrier name"></cordova-label>
        <cordova-combo spoken-text="Simulated value for wireless carrier" 
           id="carrier-select" style="width:auto; min-width:0; display:inline;">
            <option value="AT&T" selected="selected">AT&T</option>
            <option value="Sprint">Sprint</option>
            <option value="T-Mobile">T-Mobile</option>
            <option value="US Cellular">US Cellular</option>
            <option value="Verizon">Verizon</option>
        </cordova-combo>
    </cordova-panel-row>
    <cordova-panel-row>
        <cordova-label label="Country Code" spoken="country code"></cordova-label>
        <cordova-combo spoken-text="Simulated value for country code" 
          id="country-code-select" style="width:auto; min-width:0; display:inline;">
            <option value="CA">Canada</option>
            <option value="MX">Mexico</option>
            <option value="US" selected="selected">United States</option>
        </cordova-combo>
    </cordova-panel-row>
    <cordova-panel-row>
      Enable the error checkbox to execute the error callback instead of the success callback on plugin API calls.
    </cordova-panel-row>
    <cordova-checkbox id="is-error" spoken="">Error condition</cordova-checkbox>
</cordova-panel>

现在,在 sim-host-handlers.js 文件中,我在文件中公开的每个方法中添加了对复选框状态的检查。对于我的插件,如果选中复选框,则该方法会调用错误回调函数,返回一个虚拟的 JSON 对象,其中包含简单的错误消息和代码。对于您的插件,您可能需要扩展它,以便您可以模拟不同的错误情况。

module.exports = function(messages) {
	return {
		carrier: {
			getCarrierName: function(successCallback, errorCallback) {
				console.log("Cordova-Simulate: getCarrierName invoked");
				if (document.getElementById('is-error').checked) {
					console.error("Error condition enabled");
					errorCallback({ code: 1, msg: "Simulated error condition" });
				} else {
					//Get access to the carrier selection drop-down
					var carrierSel = document.getElementById('carrier-select');
					//Pull the value from the selected item
					var selValue = carrierSel.options[carrierSel.selectedIndex].value;
					console.log('Simulating carrier: ' + selValue);
					//And return it to the calling method
					successCallback(selValue);
				}
			},
			getCountryCode: function(successCallback, errorCallback) {
				if (document.getElementById('is-error').checked) {
					console.error("Error condition enabled");
					errorCallback({ code: 2, msg: "Simulated error condition" });
				} else {
					console.log("Cordova-Simulate: getCountryCode invoked");
					//Get access to the country code selection drop-down
					var ccSel = document.getElementById('country-code-select');
					//Pull the value from the selected item
					var selValue = ccSel.options[ccSel.selectedIndex].value;
					console.log('Simulating country code: ' + selValue);
					//And return it to the calling method
					successCallback(selValue);
				}
			}
		}
	};
};

现在,当我执行 Cordova 模拟时,我将看到如下所示的略有不同的面板。

Figure 4

就是这样,这就是在您自己的插件中启用模拟的全部内容。正如您希望看到的那样,Cordova Simulate 为开发人员(插件开发人员和在应用程序中使用这些开发人员插件的开发人员)提供了一种更简单的方式来练习插件并测试使用该插件的应用程序。

Cordova Simulate 是微软的一个开源项目,但我们很乐意得到您的帮助来增强它并解决出现的问题。请在 Github 上关注该项目,并了解您如何帮助增强此项目。