- Mock
- 测试
【免费下载链接】moto
A library that allows you to easily mock out tests based on AWS infrastructure.
本篇技术指南围绕 moto 仓库中 CodeBuild 服务文档 展开,系统梳理 moto 当前对 AWS CodeBuild API 的模拟覆盖范围,并结合 models.py 与 responses.py 的源码实现,讲解如何基于@mock_aws装饰器在单元测试中创建构建项目、启动构建、查询构建详情与停止构建,以及各 API 的参数校验规则和构建生命周期模拟机制。
一、当前 API 实现覆盖情况
CodeBuild 服务文档 以清单形式列出了该服务所有 API 的实现状态。截至目前,已实现([X])的接口共 9 个:
| API | 说明 |
|---|---|
batch_get_builds | 按构建 ID 批量获取构建详情 |
batch_get_projects | 按项目名或 ARN 批量获取项目元数据 |
create_project | 创建构建项目 |
delete_project | 删除构建项目 |
list_builds | 列出当前账户/区域下所有构建 ID |
list_builds_for_project | 列出指定项目的构建历史 ID |
list_projects | 列出所有项目名称 |
start_build | 对指定项目启动一次构建 |
stop_build | 停止一个指定构建 |
文档中其余接口(如update_project、retry_build、start_build_batch、create_fleet、update_webhook、import_source_credentials等)均未实现。因此,如果你的测试代码依赖上述未列出的 API,moto 尚无法接管这些调用,需要评估改造测试逻辑或等待社区补齐。
二、快速上手:用 boto3 模拟 CodeBuild 全流程
以下示例改编自仓库官方测试 test_codebuild.py,覆盖了创建项目、启动构建、查询构建详情、停止构建和删除项目的完整链路。
import boto3 import pytest from botocore.exceptions import ClientError from moto import mock_aws from moto.core import DEFAULT_ACCOUNT_ID as ACCOUNT_ID @mock_aws def test_codebuild_lifecycle(): client = boto3.client("codebuild", region_name="eu-central-1") source = {"type": "S3", "location": "bucketname/path/file.zip"} artifacts = {"type": "NO_ARTIFACTS"} environment = { "type": "LINUX_CONTAINER", "image": "contents_not_validated", "computeType": "BUILD_GENERAL1_SMALL", } service_role = f"arn:aws:iam::{ACCOUNT_ID}:role/service-role/my-codebuild-service-role" # 1. 创建项目 project = client.create_project( name="some_project", source=source, artifacts=artifacts, environment=environment, serviceRole=service_role, tags=[{"key": "k1", "value": "v1"}], )["project"] assert project["name"] == "some_project" assert project["tags"] == [{"key": "k1", "value": "v1"}] # 2. 启动构建(可指定 sourceVersion / artifactsOverride 覆盖) build = client.start_build(projectName="some_project")["build"] assert build["currentPhase"] == "QUEUED" # 3. 查询构建历史并获取构建详情 ids = client.list_builds_for_project(projectName="some_project")["ids"] detail = client.batch_get_builds(ids=ids)["builds"][0] assert detail["currentPhase"] == "COMPLETED" assert detail["buildStatus"] == "SUCCEEDED" # 4. 停止构建 stopped = client.stop_build(id=ids[0])["build"] assert stopped["buildStatus"] == "STOPPED" # 5. 删除项目后,再查询构建历史将抛 ResourceNotFoundException client.delete_project(name="some_project") with pytest.raises(ClientError) as err: client.list_builds_for_project(projectName="some_project") assert err.value.response["Error"]["Code"] == "ResourceNotFoundException"运行前提:安装 moto(pip install moto[server]视使用方式而定)并配置 boto3。@mock_aws装饰器接管期间,所有发往codebuild.{region}.amazonaws.com域名的请求都会被路由到本地内存后端,无需真实 AWS 凭据(账户 ID 使用 moto 默认的DEFAULT_ACCOUNT_ID)。
三、请求路由与服务端点
urls.py 定义了 moto 拦截 CodeBuild 请求的域名模式:
url_bases = [r"https?://codebuild\.(.+)\.amazonaws\.com"] url_paths = {"{0}/$": CodeBuildResponse.dispatch}即只要 boto3 客户端以 region 形式指向 CodeBuild 端点(如codebuild.eu-central-1.amazonaws.com),请求体就会交由 CodeBuildResponse 分发。exceptions.py 则定义了模拟服务端会抛出的三类 JSON 错误,均继承自 moto 核心层的JsonRESTError,HTTP 状态码统一为 400:
InvalidInputException:参数非法(名称、源类型、构建 ID 等);ResourceNotFoundException:项目或构建不存在;ResourceAlreadyExistsException:重复创建同名项目。
四、create_project 的参数校验规则
responses.py 中的create_project入口在执行前会依次调用五组校验函数,这些规则决定了你在测试里构造请求参数时的合法取值范围:
1. 项目名(_validate_required_params_project_name)
- 长度不得达到 150 字符(
len(name) >= 150即拒绝); - 必须以字母开头、不能以特殊符号结尾,正则要求名称只含字母数字、连字符与下划线(见 responses.py#L78-L87)。
2. 源代码(_validate_required_params_source)
source.type必须是以下枚举之一:BITBUCKET、CODECOMMIT、CODEPIPELINE、GITHUB、GITHUB_ENTERPRISE、NO_SOURCE、S3;source.location必须存在且非空。
3. 服务角色(_validate_required_params_service_role)
serviceRole必须以arn:{partition}:iam::{当前账户ID}:role/为前缀,即角色 ARN 中的账户 ID 必须与调用者账户一致,否则抛InvalidInputException。
4. 构建产物(_validate_required_params_artifacts)
artifacts.type必须是CODEPIPELINE、S3或NO_ARTIFACTS;- 若类型为
NO_ARTIFACTS,则不允许再携带location字段; - 其余类型必须提供非空
location。
5. 构建环境(_validate_required_params_environment)
environment.type支持:WINDOWS_CONTAINER、LINUX_CONTAINER、LINUX_GPU_CONTAINER、ARM_CONTAINER;environment.computeType支持:BUILD_GENERAL1_SMALL、BUILD_GENERAL1_MEDIUM、BUILD_GENERAL1_LARGE、BUILD_GENERAL1_2XLARGE。
此外,若同区域已存在同名项目,create_project会直接抛出ResourceAlreadyExistsException,这一点在测试 test_codebuild_create_project_when_exists 中有对应断言。
五、项目与构建的内存数据模型
models.py 用三个核心结构维护全部状态,均挂载在按区域隔离的codebuild_backends(BackendDict)上:
codebuild_projects: dict[str, CodeBuild]:项目名到项目对象的映射;build_history: dict[str, list[str]]:项目名到构建 ID 列表的映射(构建历史);build_metadata / build_metadata_history:最新构建元数据及其历史副本。
CodeBuild对象(models.py#L94-L150)会在构造时生成项目 ARN(arn:{partition}:codebuild:{region}:{account}:project/{name})、把非 ARN 形式的serviceRole自动补全为 IAM 角色 ARN,并把description、tags、cache(默认{"type": "NO_CACHE"})、timeoutInMinutes、queuedTimeoutInMinutes、sourceVersion、logsConfig、vpcConfig等可选字段写入project_metadata,供batch_get_projects原样返回。batch_get_projects同时支持传入项目名或项目 ARN 两种寻址方式,可在 test_codebuild_batch_get_projects 中验证。
六、构建生命周期:从 QUEUED 到 COMPLETED
start_build是模拟构建的核心入口,其实现细节值得逐点理解:
- 构建 ID 生成:
{project_name}:{uuid4}格式(models.py#L226),这也是为什么batch_get_builds、stop_build都强制要求 ID 中包含冒号——不含冒号的 ID 会触发InvalidInputException(见_validate_required_params_id)。 - 初始状态:
CodeBuildProjectMetadata会把构建初始化为currentPhase = QUEUED、buildStatus = IN_PROGRESS,sourceVersion未指定时回退为refs/heads/main,并填充一套固定的source、environment、logs、phases(SUBMITTED + QUEUED)等字段(models.py#L41-L59)。 - 状态推进:调用
batch_get_builds查询某个构建时,后端会触发_set_phases(models.py#L248-L276),把QUEUED阶段置为SUCCEEDED,并追加PROVISIONING、DOWNLOAD_SOURCE、INSTALL、PRE_BUILD、BUILD、POST_BUILD、UPLOAD_ARTIFACTS、FINALIZING、COMPLETED共 9 个阶段(加上初始 2 个阶段,总计 11 个),整体状态改为currentPhase = COMPLETED、buildStatus = SUCCEEDED,endTime取startTime之后 1~5 分钟的随机值。测试 test_codebuild_batch_get_builds_1_project 正是断言了phases数量为 11、buildNumber为整数。 - stop_build 的差异:stop_build 走同样的阶段推进逻辑,但将最终
buildStatus置为STOPPED而非SUCCEEDED,同样会补齐endTime。
需要特别注意的模拟语义:构建“完成”是惰性触发的——只有在查询(batch_get_builds)或停止(stop_build)时才把状态从 IN_PROGRESS 推进到终态;且stop_build返回的是STOPPED状态,这与真实 CodeBuild 中“停止后进入 FINALIZING 再置 STOPPED”的过程有简化差异,编写断言时应以仓库测试的行为为准。
七、边界行为与测试依据
tests/test_codebuild/test_codebuild.py 覆盖了上述所有边界情况,可作为编写断言时的参照:
- 无效输入:名称超长、名称以
!开头、服务角色 ARN 账户不匹配,均抛InvalidInputException(test_codebuild_create_project_with_invalid_inputs); - 项目不存在时启动构建:
start_build抛ResourceNotFoundException(test_codebuild_start_build_no_project); - 多次构建累积:对同一项目连续三次
start_build,list_builds返回 3 个 ID(test_codebuild_start_build_multiple_times); - 构建覆盖参数:
start_build支持sourceVersion与artifactsOverride透传到构建元数据(test_codebuild_start_build_with_overrides); - 删除项目级联:
delete_project会同时清理该项目的构建元数据与项目记录,之后list_builds_for_project抛ResourceNotFoundException(test_codebuild_delete_project)。
八、小结与使用建议
moto 的 CodeBuild 模拟聚焦“项目 CRUD + 构建生命周期”这一最常用的测试场景:9 个已实现 API 足以支撑 CI 流水线集成、构建状态轮询类逻辑的离线测试。使用时的三条建议:
- 只使用文档清单中标记为已实现的 API,避免在
update_project等未实现接口上投入改造成本; - 断言构建状态时,区分“
start_build返回的初始 QUEUED 状态”与“batch_get_builds查询后的终态 SUCCEEDED”; - 项目名、源类型、计算类型等参数必须严格匹配上文第四节的枚举与正则规则,否则会触发与真实 AWS 一致的错误码语义(
InvalidInputException/ResourceNotFoundException/ResourceAlreadyExistsException)。
相关源码入口:服务实现、请求分发与校验、异常定义、URL 路由、完整测试用例、API 覆盖清单。
- Mock
- 测试
【免费下载链接】moto
A library that allows you to easily mock out tests based on AWS infrastructure.
相关推荐
AWS CLI `codebuild retry-build-batch` 实战指南:重试失败的 CodeBuild 批量构建
AWS CLI codebuild retry build batch 实战指南:重试失败的 CodeBuild 批量构建 导读 本文聚焦 AWS CLI 的
开发工具云原生运维aws-cli 实战:用 `codebuild batch-get-builds` 批量查询 AWS CodeBuild 构建详情
aws cli 实战:用 codebuild batch get builds 批量查询 AWS CodeBuild 构建详情 本篇技术指南聚焦 AWS CLI
开发工具云原生运维AWS CLI 实战:使用 codebuild delete-webhook 删除 CodeBuild 项目的 Webhook 构建触发器
AWS CLI 实战:使用 codebuild delete webhook 删除 CodeBuild 项目的 Webhook 构建触发器 导读 本文围绕 AW
开发工具云原生运维
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考