使用 Cloud Build 和 Terraform 对 Cloud Functions 进行系统测试

本教程介绍如何对使用 Cloud Functions 构建的应用自动执行端到端测试。 Cloud Build 运行测试流水线,HashiCorp Terraform 设置并删除运行测试所需的 Google Cloud 资源。Cloud Build 触发器会在每次代码提交后启动流水线。

在设计应用和评估架构选择时,可测试性是一项关键考虑因素。创建并定期运行一系列全面的测试(包括自动化单元测试、集成测试和端到端系统测试)对于验证应用是否按预期运行至关重要。如需详细了解如何针对不同的 Cloud Functions 场景进行各类测试,请参阅测试最佳做法指南。

创建和运行单元测试通常较简单,因为这些测试是独立的并且独立于执行环境。但是,集成和系统测试则较复杂,尤其是在云环境中。使用无服务器技术(例如 Cloud Functions)的应用尤其需要使用端到端系统测试。这些应用通常由事件驱动且松散耦合,并且可能是独立部署的应用。对于验证函数是否正确响应 Google Cloud 执行环境中的事件,全面的端到端测试至关重要。

架构

以下架构图展示了您在本教程中使用的组件。

构建并测试项目的架构图。

该架构包含以下组件:

  • 托管并运行 Cloud Build 流水线的 build 项目。
  • 为受测示例应用托管 Google Cloud 资源的 test 项目。
    • 无服务器 Web 性能监控教程中描述的应用用作示例应用。
    • 系统会为每次构建迭代创建并销毁示例应用的 Google Cloud 资源。Firestore 数据库例外。该数据库是一次性创建,由所有后续构建重复使用。

目标

  • 创建一个 Cloud Build 流水线,对使用 Cloud Functions 构建的示例应用运行单元测试和端到端测试。
  • 在构建中使用 Terraform 来设置和销毁应用所需的 Google Cloud 资源。
  • 使用专用 Google Cloud 测试项目保持测试环境独立。
  • 在 Cloud Source Repositories 中创建一个 Git 代码库,并添加一个 Cloud Build 触发器以在提交后运行端到端构建。

费用

本教程使用 Google Cloud 的以下收费组件:

您可使用价格计算器根据您的预计使用量来估算费用。 Google Cloud 新用户可能有资格申请免费试用

完成本教程后,您可以删除所创建的资源以避免继续计费。如需了解详情,请参阅清理

准备工作

  1. 选择或创建 Cloud 项目。这是托管示例应用的 test 项目。

    转到项目选择器页面

  2. 记下 test 项目的 Google Cloud 项目 ID。 在下一部分设置环境时,您将需要此 ID。
  3. 启用该项目Cloud Build、Cloud Functions 和 Cloud Source Repositories API。

    启用 API

  4. 在 Cloud Console 中,转到 Firestore 页面。
  5. 转到 Firestore 页面
  6. 创建 Firestore 数据库。

    了解如何创建 Firestore 数据库

  7. 选择或创建其他 Google Cloud 项目。这是托管 Cloud Build 流水线的 build 项目。
  8. 转到“管理资源”页面
  9. 确保您的 Google Cloud 项目已启用结算功能。

    了解如何启用结算功能

设置环境

在本教程中,您将在 Cloud Shell 中运行命令。Cloud Shell 是一个已安装 Cloud SDK 的 shell 环境,其中包括 gcloud 命令行工具以及已为当前项目设置的值。初始化 Cloud Shell 可能需要几分钟。

  1. 在 Cloud Console 中,为 build 项目打开 Cloud Shell。

    打开 Cloud Shell

  2. 为您之前复制的 test Google Cloud 项目 ID 设置变量:

    export TEST_PROJECT=your-test-project-id
    

    替换以下内容:

    • your-test-project-id:您的 test Google Cloud 项目的 ID。
  3. 将当前 build Google Cloud 项目的项目 ID 和项目编号设置为变量:

    export BUILD_PROJECT=$(gcloud config get-value core/project)
    export BUILD_PROJECT_NUM=$(gcloud projects list) \
        --filter="$BUILD_PROJECT" --format="value(PROJECT_NUMBER)"
    
  4. 为部署地区设置变量:

    export REGION=us-central1
    

    虽然本教程使用的是 us-central1 地区,但您可以将其更改为支持 Cloud Functions 的任何地区

  5. 克隆包含本教程中使用的示例应用代码的代码库:

    git clone \
        https://github.com/GoogleCloudPlatform/solutions-serverless-web-monitoring.git
    
  6. 转到项目目录:

    cd solutions-serverless-web-monitoring
    

使用 Terraform 创建测试基础架构

本教程使用 Terraform 在测试项目中自动创建和销毁 Google Cloud 资源。为每次构建创建独立资源有助于将测试彼此隔离。隔离测试时,可以并发进行构建,并且可以针对特定资源作出测试断言。在每次构建结束时销毁资源有助于最大限度地降低费用。

本教程部署了无服务器 Web 监控教程中所述的应用。该应用包含一组 Cloud Functions 函数、Cloud Storage 存储分区、Pub/Sub 资源和一个 Firestore 数据库。Terraform 配置定义创建这些资源所需的步骤。Firestore 数据库不是由 Terraform 部署;该数据库是一次性创建,并由所有测试重复使用。

Terraform 配置文件 main.tf 中的以下代码示例展示了部署 trace Cloud Functions 函数所需的步骤。如需了解完整配置,请参阅完整文件。

data "archive_file" "local_tracer_source" {
  type        = "zip"
  source_dir  = "./functions/tracer"
  output_path = "${var.local_output_path}/tracer.zip"
}

resource "google_storage_bucket_object" "gcs_tracer_source" {
  name   = "tracer.zip"
  bucket = "${google_storage_bucket.bucket_source_archives.name}"
  source = "${data.archive_file.local_tracer_source.output_path}"
}

resource "google_cloudfunctions_function" "function_tracer" {
  name = "tracer-${var.suffix}"
  project = "${var.project_id}"
  region = "${var.region}"
  available_memory_mb = "1024"
  entry_point = "trace"
  runtime = "nodejs8"
  trigger_http = "true"
  source_archive_bucket = "${google_storage_bucket.bucket_source_archives.name}"
  source_archive_object = "${google_storage_bucket_object.gcs_tracer_source.name}"
  environment_variables = {
    BUCKET_METRICS = "${google_storage_bucket.bucket_metrics.name}"
    ALLOWED_HOSTS = "${var.allowed_hosts}"
  }
}

// prevent unauthenticated invocations
resource "google_cloudfunctions_function_iam_binding" "tracer_disallow_unauthenticated" {
  project = "${var.project_id}"
  region = "${var.region}"
  cloud_function = "${google_cloudfunctions_function.function_tracer.name}"
  role = "roles/cloudfunctions.invoker"
  members = [
  ]
  depends_on = [
    google_cloudfunctions_function.function_tracer
  ]
}

在此步骤中,您将运行 Terraform 配置以部署测试资源。 在稍后的步骤中,Cloud Build 会自动部署资源。

  1. 在 Cloud Shell 中,初始化 Terraform:

    docker run -v $(pwd):/app -w /app hashicorp/terraform:0.12.0 init
    

    您需要使用公共 Terraform Docker 映像。Docker 已安装在 Cloud Shell 中。当前工作目录以卷的形式装载,因此 Docker 容器可以读取 Terraform 配置文件。

  2. 使用 Terraform apply 命令创建资源:

    docker run -v $(pwd):/app -w /app hashicorp/terraform:0.12.0 apply \
        --auto-approve \
        -var "project_id=$TEST_PROJECT" \
        -var "region=$REGION" \
        -var "suffix=$TEST_PROJECT"
    

    该命令包含一些变量,这些变量指定您希望在其中创建测试资源的 Google Cloud 项目和地区。此外,它还包含一个用于在此步骤中创建命名资源的后缀。 在稍后的步骤中,Cloud Build 会自动提供合适的后缀。

    此操作需要几分钟时间才能完成。

  3. 确认已在 test 项目中创建资源:

    gcloud functions list --project $TEST_PROJECT
    

    输出会显示名称以之前提供的后缀结束的三个 Cloud Functions 函数。

运行端到端测试

在本部分中,您将针对在上一部分中部署的测试基础架构运行端到端测试。

以下代码段展示了这些测试。这些测试会同时验证成功和失败情况。测试流水线总结如下:

  • 首先,测试调用 trace 函数。此调用会通过触发其他函数的应用启动事件流。
  • 然后,测试验证每个函数的行为,并确认对象已写入 Cloud Storage、结果永久存储到 Firestore,并在失败时生成 Pub/Sub 提醒。
def test_e2e_pass():
  run_pipeline('http://www.example.com/', True)

def test_e2e_fail():
  run_pipeline('https://cloud.google.com/docs/tutorials', False)

def run_pipeline(url, should_pass):
  """Triggers the web analysis pipeline and verifies outputs of each stage.

  Args:
    url (str): The page to analyze.
    should_pass (bool): Whether the page should load within the threshold time.
  """
  trace_response = call_tracer(url)
  filename = assert_tracer_response(trace_response)
  assert_gcs_objects(filename)
  assert_firestore_doc(filename, should_pass)
  assert_pubsub_message(should_pass)

  # clean up
  delete_gcs_objects(filename)
  delete_firestore_doc(filename)

如需运行端到端测试,请完成以下步骤:

  1. 在 Cloud Shell 中,创建新的 virtualenv 环境。virtualenv 实用程序已安装在 Cloud Shell 中。

    virtualenv venv
    
  2. 激活 virtualenv 环境:

    source venv/bin/activate
    
  3. 安装所需的 Python 库:

    pip install -r requirements.txt
    
  4. 运行端到端测试:

    python -m pytest e2e/ --tfstate terraform.tfstate
    

    传递 Terraform 状态文件,该文件包含在上一部分中创建的测试资源的详细信息。

    测试可能需要几分钟时间才能完成。系统会显示一条消息,指示已通过两项测试。您可以忽略任何警告。

  5. 使用 Terraform destroy 命令删除测试资源:

    docker run -v $(pwd):/app -w /app hashicorp/terraform:0.12.0 destroy \
        --auto-approve \
        -var "project_id=$TEST_PROJECT" \
        -var "region=$REGION" \
        -var "suffix=$TEST_PROJECT"
    
  6. 确认资源已被销毁:

    gcloud functions list --project $TEST_PROJECT
    

    不再存在名称以之前提供的后缀结束的任何 Cloud Functions 函数。

提交 Cloud Build 流水线

在本部分中,您将使用 Cloud Build 自动执行测试流水线。

设置 Cloud Build 权限

您可以使用 Cloud Build 服务帐号运行 Cloud Build。 由构建执行的系统测试创建 Cloud Functions 函数、Cloud Storage 存储分区、Pub/Sub 资源和 Firestore 文档并与之交互。为此,Cloud Build 具有以下要求:

在此过程中,您需要添加适当的角色,然后添加 Cloud Build 服务帐号。

  1. 在 Cloud Shell 中,将适当的 IAM 角色添加到默认 Cloud Build 服务帐号:

    for role in cloudfunctions.developer pubsub.editor storage.admin datastore.user; do \
        gcloud projects add-iam-policy-binding $TEST_PROJECT \
        --member="serviceAccount:$BUILD_PROJECT_NUM@cloudbuild.gserviceaccount.com" \
        --role="roles/$role"; \
        done
    
  2. 将 Cloud Build 服务帐号添加为测试项目中 App Engine 服务帐号的 serviceAccountUser

    gcloud iam service-accounts add-iam-policy-binding \
        $TEST_PROJECT@appspot.gserviceaccount.com \
        --member="serviceAccount:$BUILD_PROJECT_NUM@cloudbuild.gserviceaccount.com" \
        --role=roles/iam.serviceAccountUser \
        --project $TEST_PROJECT
    

提交手动构建

构建执行四个逻辑任务:

  • 运行单元测试
  • 部署示例应用
  • 运行端到端测试
  • 销毁示例应用

查看 cloudbuild.yaml 文件中的以下代码段。该代码段说明了使用 Terraform 部署示例应用以及运行端到端测试的各个 Cloud Build 步骤。

# setup Terraform using public terraform Docker image
- id: terraform-init
  name: hashicorp/terraform:0.12.0
  args: ['init']

# deploy the required GCP resources
- id: terraform-apply
  name: hashicorp/terraform:0.12.0
  args: ['apply', '-auto-approve']
  env:
    - 'TF_VAR_project_id=$_TEST_PROJECT_ID'
    - 'TF_VAR_region=$_REGION'
    - 'TF_VAR_suffix=$BUILD_ID'

# run end-to-end tests to verify live interactions
- id: end-to-end-tests
  name: 'python:3.7-slim'
  entrypoint: /bin/sh
  args:
    - -c
    - 'pip install -r requirements.txt && python -m pytest e2e --tfstate terraform.tfstate'

如需向 Cloud Build 提交手动构建并运行端到端测试,请执行以下操作:

  • 在 Cloud Shell 中,输入以下命令:

    gcloud builds submit --config cloudbuild.yaml \
        --substitutions=_REGION=$REGION,_TEST_PROJECT_ID=$TEST_PROJECT
    

    构建需要几分钟时间才能运行。构建步骤如下:

    • Cloud Build 使用替换来提供为您创建的测试资源指定 Google Cloud 项目和地区的变量。

    • Cloud Build 在 build Google Cloud 项目中运行构建。测试资源在单独的 test 项目中创建。

    构建日志会流式传输到 Cloud Shell,以便您跟踪构建进度。日志流会在构建完成时终止。系统会显示一些消息,指示最终的 terraform-destroy 构建步骤已成功且构建已完成。

自动执行测试

持续集成 (CI) 的一个关键原则是定期运行一系列全面的自动化测试。通常,每次在提交到共享代码库时都会运行构建-测试流水线。此设置有助于确认对每次向共享代码库的提交都进行了测试和验证,以便您的团队尽早发现问题。

在接下来的各部分中,您将执行以下操作:

  • 在 Cloud Source Repositories 中创建 Git 代码库。
  • 添加 Cloud Build 触发器以在每次提交时运行端到端构建。
  • 将代码推送到代码库以触发构建。

创建 Cloud Source Repository 和 Cloud Build 触发器

  1. 在 Cloud Shell 中,创建新的 Cloud Source Repository:

    gcloud source repos create serverless-web-monitoring
    
  2. 在 Cloud Console 中,打开 Cloud Build 触发器页面。

    转到“触发器”页面

  3. 点击创建触发器

    此时会打开创建触发器页面。

  4. 填写以下选项:

    • 名称字段中,输入 end-to-end-tests
    • 事件下,选择推送到分支
    • 来源下,选择 severless-web-monitoring 作为您的代码库,选择 ^master$ 作为您的分支
    • 配置下,选择 Cloud Build 配置文件(yaml 或 json)
    • Cloud Build 配置文件位置字段中,输入 cloudbuild.yaml
    • 要添加变量替换以指定将在其中创建测试资源的 Google Cloud 地区,请点击添加变量

      • 变量_REGION
      • your-test-region

        替换以下内容:

        • your-test-region:Cloud Shell 中 $REGION 变量的值。
    • 要添加其他变量替换以指定将托管测试资源的项目的 ID,请点击添加变量

      • 变量_TEST_PROJECT_ID
      • your-test-project

        替换以下内容:

        • your-test-project:Cloud Shell 中 $TEST_PROJECT 变量的值。
  5. 点击创建以保存您的构建触发器。

启动构建

  1. 在 Cloud Shell 中,将代码库添加为 git 配置中的新远程:

    git remote add csr \
        https://source.developers.google.com/p/$BUILD_PROJECT/r/serverless-web-monitoring
    
  2. 要触发构建,请将代码推送到代码库:

    git push csr master
    
  3. 列出最新构建:

    gcloud builds list --limit 3
    

    输出将显示 WORKING 状态的构建,指示该构建已按预期触发。

  4. 复制 WORKING 构建的 ID 以供下一步使用。

  5. 将构建日志流式传输到 Cloud Console:

    gcloud builds log --stream build-id
    

    替换以下内容:

    • build-id:您在上一步中复制的 WORKING 构建的 ID。

    日志流会在构建完成时终止。系统会显示一些消息,指示最终的 terraform-destroy 构建步骤已成功且构建已完成。

清理

为避免因本教程中使用的资源导致您的 Google Cloud 帐号产生费用,请删除包含这些资源的项目,或者保留项目但删除各个资源。

删除项目

  1. 在 Cloud Console 中,转到管理资源页面。

    转到“管理资源”

  2. 在项目列表中,选择要删除的项目,然后点击删除
  3. 在对话框中输入项目 ID,然后点击关闭以删除项目。

后续步骤