您可以在编写代码后使用单元测试来检查代码的质量,同时也可以使用该功能来改进开发过程。建议您一边开发一边编写测试,而不是在完成应用的开发之后才编写测试。这样做有助于您设计出可维护、可重复使用的小型代码单元,也方便您迅速而彻底地测试您的代码。
在进行本地单元测试时,您的测试将限制于自己的开发环境中,而不会涉及远程组件。App Engine 提供的测试实用程序使用数据存储区和其他 App Engine 服务的本地实现。这意味着,您可以借助服务存根,在本地运行并测试您的代码对这些服务的使用情况,而无需将您的代码部署到 App Engine。
服务桩是模拟服务行为的一种方法。例如,凭借编写数据存储区和 Memcache 测试中所示的数据存储区服务桩,您可以对您的数据存储区代码进行测试,而不需要向实际数据存储区发送任何请求。在数据存储区单元测试过程中存储的任何实体都会保存在内存中,而不是保存在数据存储区中,并会在测试运行结束后删除。您无需依赖数据存储区本身便可快速运行小型测试。
本文档介绍了如何为几项本地 App Engine 服务编写单元测试,并提供了一些关于设置测试框架的信息。
Python 2 测试实用程序简介
App Engine Python testbed
模块为单元测试提供了服务存根。
服务存根适用于以下服务:
- 应用身份
init_app_identity_stub
- Blobstore(使用
init_blobstore_stub
) - 容量(使用
init_capability_stub
) - Datastore(使用
init_datastore_v3_stub
) - 文件(使用
init_files_stub
) - 映像(仅适用于 dev_appserver;使用
init_images_stub
) - LogService(使用
init_logservice_stub
) - 邮件(使用
init_mail_stub
) - Memcache(使用
init_memcache_stub
) - 任务队列(使用
init_taskqueue_stub
) - 网址提取(使用
init_urlfetch_stub
) - 用户服务(使用
init_user_stub
)
要同时初始化所有存根,您可以使用 init_all_stubs
。
编写数据存储区和 memcache 测试
本部分介绍如何编写代码以测试 Datastore 和 Memcache 服务的使用情况。
确保您的测试运行程序在 Python 加载路径上具有相应的库,包括 App Engine 库、yaml
(包含在 App Engine SDK 中)、应用根以及应用代码期望的对库路径(例如本地 ./lib
目录,前提是您具有该目录)的任何其他修改。例如:
import sys
sys.path.insert(1, 'google-cloud-sdk/platform/google_appengine')
sys.path.insert(1, 'google-cloud-sdk/platform/google_appengine/lib/yaml/lib')
sys.path.insert(1, 'myapp/lib')
导入 Python unittest
模块以及与被测试服务相关的 App Engine 模块,在本例中,为 memcache
和 ndb
,后者同时使用数据存储区和 Memcache。另外,导入 testbed
模块。
然后创建 TestModel
类。在本示例中,使用了一个函数来检查某实体是否存储在 Memcache 中。如果未找到实体,则检查数据存储区中是否存在实体。在现实中,这种操作往往是多余的,因为 ndb
在后台使用 Memcache,但其仍然不失为一种合适的测试模式。
接下来创建测试用例。无论您在测试哪些服务,测试用例都必须创建 Testbed
实例并将其激活。在使用 init_datastore_v3_stub
和 init_memcache_stub
时,测试用例还必须初始化相关的服务存根。要查看初始化其他 App Engine 服务存根的方法,请参阅 Python 测试实用程序简介。
不含参数的 init_datastore_v3_stub()
方法使用内存中的数据存储区,该数据存储区最初为空。如果想测试现有数据存储区实体,请将其路径名作为参数加入 init_datastore_v3_stub()
中。
除 setUp()
外,还应提供取消激活测试台的 tearDown()
方法。该方法可以恢复原始存根,使测试互不干扰。
然后执行测试。
现在,您可以利用 TestModel
编写使用数据存储区或 Memcache 服务存根而不是使用实际服务的测试。
例如,下面所示方法创建了两个实体:第一个实体使用默认值作为 number
特性 (42),第二个实体则使用非默认值作为 number
(17)。然后,该方法会为 TestModel
实体构建查询,但仅针对那些使用 number
默认值的实体。
在检索所有匹配的实体之后,该方法测试出只找出一个实体,且该实体的 number
特性值是默认值。
在另一个示例中,以下方法创建了一个实体,并使用我们在上面创建的 GetEntityViaMemcache()
函数检索该实体。然后,该方法会测试是否返回了实体,以及其 number
值是否与之前创建的实体的值相同。
最后,调用 unittest.main()
。
要运行测试,请参阅运行测试。
编写 Cloud Datastore 测试
如果您的应用使用 Cloud Datastore,您可能需要编写测试,以验证应用在面对最终一致性时的行为。
db.testbed
公开了可简化这一操作的选项:
通过 PseudoRandomHRConsistencyPolicy
类,您可以控制在每个全局(非祖先实体)查询之前应用某项写入操作的可能性。如果将可能性设置为 0%,则表示我们在指示数据存储区存根以最高的最终一致性进行操作。最高的最终一致性意味着,写入内容将被提交,但始终无法应用,因此,全局(非祖先实体)查询将始终无法看到变化。当然,这并不代表应用在生产环境中运行时也会面临这种程度的最终一致性,但在测试中,如果能够将本地数据存储区配置为每次都采取这种行为方式,是非常有用的。如果使用非零可能性,那么 PseudoRandomHRConsistencyPolicy
会建立确定的一致性决策顺序,使测试结果保持一致:
测试 API 对于检验应用是否针对最终一致性正常运行非常有用,但请切记,本地强复制读取一致性模型只是与生产强复制读取一致性模型非常近似,并不完全相同。在本地环境中,如果对一个 Entity
执行 get()
,而其属于一个带有未应用写入操作的实体组,则未应用写入的结果将对后续全局查询始终可见。在生产环境中,不会出现这种情况。
编写邮件测试
您可以使用邮件服务桩来测试邮件服务。与测试平台支持的其他服务类似,首先初始化存根,然后调用使用邮件 API 的代码,最后测试是否发送了正确的邮件。
编写任务队列测试
您可以使用任务队列存根来编写使用任务队列服务的测试。与测试平台支持的其他服务类似,首先初始化存根,然后调用使用任务队列 API 的代码,最后测试是否已将任务正确添加到队列。
设置 queue.yaml
配置文件
如果要针对与非默认队列交互的代码运行测试,您需要为要使用的应用创建并指定 queue.yaml
文件。具体流程请参阅以下示例 queue.yaml
:
如需详细了解可用的 queue.yaml 选项,请参阅任务队列配置。
queue.yaml
的位置需要在初始化存根时进行指定:
self.testbed.init_taskqueue_stub(root_path='.')
在示例中,queue.yaml
与测试位于同一目录中。如果它位于其他文件夹中,则需要在 root_path
中指定该路径。
过滤任务
任务队列存根的 get_filtered_tasks
可让您过滤已加入队列的任务。
这有助于您更加轻松地编写所需测试,以验证用来将多个任务加入队列的代码。
编写延迟任务测试
如果您的应用代码使用延迟库,则您可以搭配使用任务队列存根与 deferred
来验证延迟函数是否加入队列并正确执行。
更改默认环境变量
App Engine 服务通常依赖于环境变量。testbed.Testbed
类的 activate()
方法会使用这些环境变量的默认值,但您可以根据自己的测试需求使用 testbed.Testbed
类的 setup_env
方法设置自定义值。
例如,我们假设您的测试将一些实体存储在数据存储区中,所有这些实体都链接到同一个应用 ID。现在,您想要再次运行相同的测试,但使用与链接到所存储实体的应用 ID 不同的应用 ID。要执行此操作,请将新值作为 app_id
传入 self.setup_env()
。
例如:
模拟登录
setup_env
的另一种常见用途是,模拟用户登录(无论是否具有管理员权限),以检查您的处理程序能否在每种情况下都正常运行。
举例来说,您的测试方法现在可以调用 self.loginUser('', '')
来模拟无用户登录;调用 self.loginUser('test@example.com', '123')
来模拟非管理员用户登录;调用 self.loginUser('test@example.com',
'123', is_admin=True)
来模拟管理员用户登录。
设置测试框架
SDK 的测试实用程序未与特定框架关联。您可以使用任何可用的 App Engine 测试运行程序(例如 nose-gae 或 ferrisnose)来运行单元测试。您也可以自己编写简单的测试运行程序,或使用下面显示的测试运行程序。
以下脚本使用 Python 的 unittest 模块。
您可以随意为脚本命名。运行脚本时,请提供 Google Cloud CLI 或 Google App Engine SDK 的安装路径以及测试模块的路径。脚本会在提供的路径中发现所有测试,并会将结果输出到标准错误流。测试文件遵循在名称前添加 test
前缀的命名约定。
运行测试
只需运行 runner.py
脚本便可运行这些测试。如需了解这方面的详情,请参阅设置测试框架:
python runner.py <path-to-appengine-or-gcloud-SDK> .