[英]How to pytest python AWS CDK VPC from_lookup
当使用使用from_lookup
方法获取 VPC 的堆栈时,除非您设置凭证,否则运行单元测试将失败,这是不可取且不实用的
如何编写 pytest 来解决这个问题?
示例堆栈模块ec2_stack.py
# Deps
from aws_cdk import (
Stack,
aws_ec2 as _ec2,
)
from constructs import Construct
class Ec2Test(Stack):
"""
An EC2 test
"""
def __init__(
self,
scope: Construct,
construct_id: str,
**kwargs,
) -> None:
super().__init__(scope, construct_id, **kwargs)
self.vpc_id = self.node.try_get_context("vpc_id")
self.inst_type = self.node.try_get_context("inst_type")
self.base_ami_id = self.node.try_get_context("base_ami_id")
self._vpc = _ec2.Vpc.from_lookup(
scope=self,
id="my-vpc",
vpc_id=self.vpc_id,
)
self._machine_image = _ec2.MachineImage.generic_linux(
{"ap-southeast-2": self.base_ami_id}
)
self.__create_instance()
def __create_instance(self):
"""
Create EC2 instance resource
"""
_ec2.Instance(
self,
"ec2",
instance_type=_ec2.InstanceType(self.inst_type),
vpc=self._vpc,
machine_image=self._machine_image,
)
一种解决方案是使用夹具并添加可选参数来传递 IVpc 对象,直到为 CDK 编写模拟库( moto存在模拟 boto3,但找不到 CDK 等效项)
堆栈构造函数更新
def __init__(
self,
scope: Construct,
construct_id: str,
vpc: _ec2.IVpc = None, # New parameter
**kwargs,
) -> None:
super().__init__(scope, construct_id, **kwargs)
self.vpc_id = self.node.try_get_context("vpc_id")
self.inst_type = self.node.try_get_context("inst_type")
self.base_ami_id = self.node.try_get_context("base_ami_id")
if vpc is not None:
# Now VPC can be provided optionally
self._vpc = vpc
else:
self._vpc = _ec2.Vpc.from_lookup(
scope=self,
id="my-vpc",
vpc_id=self.vpc_id,
)
self._machine_image = _ec2.MachineImage.generic_linux(
{"ap-southeast-2": self.base_ami_id}
)
self.__create_instance()
示例 pytest 代码: test_ec2_stack_vpc.py
# deps
import aws_cdk as _cdk
from aws_cdk import (
assertions as assertions,
aws_ec2 as _ec2,
)
from constructs import Construct
import pytest
from ec2_stack import Ec2Test
EXPECTED_VPC = "vpc-123"
EXPECTED_INST_TYPE = "t3.micro"
EXPECTED_AMI_ID = "ami-123"
TEST_CONTEXT_EC2 = {
"inst_type": EXPECTED_INST_TYPE,
"vpc_id": EXPECTED_VPC,
"base_ami_id": EXPECTED_AMI_ID,
}
@pytest.fixture
def make_vpc():
def _make_vpc(scope, id):
return _ec2.Vpc.from_vpc_attributes(
scope,
id,
availability_zones=["ap-southeast-2"],
vpc_id=EXPECTED_VPC,
private_subnet_ids=["subnet-9876"],
private_subnet_names=["priv-8"],
)
return _make_vpc
class EmptyStack(_cdk.Stack):
"""
Stack only to provide context
"""
def __init__(
self, scope: Construct, construct_id: str, vpc: _ec2.IVpc = None, **kwargs
) -> None:
super().__init__(scope, construct_id, **kwargs)
def test_ec2_created(make_vpc):
app = _cdk.App(context=TEST_CONTEXT_EC2)
e_stack = EmptyStack(app, "empty-stack")
test_vpc = make_vpc(e_stack, "test-vpc")
stack = Ec2Test(app, "ec2-stack", vpc=test_vpc)
template = assertions.Template.from_stack(stack)
template.has_resource_properties(
"AWS::EC2::Instance", {"InstanceType": EXPECTED_INST_TYPE}
)
我认为您可能是从错误的角度考虑这一点:
from
方法假定资源在合成期间存在,并生成一个令牌,一旦 CloudFormation 收到合成模板并解析导入from
方法中导入的各种资源所需的所有内容,该令牌将与正确的值相对应。
此外,当您对 CDK 进行单元测试时,您实际上所做的只是验证模板是否包含您希望它包含的资源,并具有某些配置。 导入诸如 VPC 之类的资源将具有由任何进程生成它时所具有的任何值(另一个堆栈,组织级别设置功能,等等)。 因此,模拟它并测试它以使其在导入它的堆栈中具有特定值是愚蠢的 - 这可能随时更改,并且您的测试会报告它仍然通过,因为即使您的模拟值不会改变实际资源做到了。
所以,你应该:
测试需要 VPC 访问的资源是否设置了 VPC 属性。 它们可能会解析为令牌,或者它们可能不是 100% 清楚,但是您可以相信from
方法通常会按照描述的方式工作,并且会在部署时添加正确的值 - 您不必测试它确实如此,因为 CDK团队已经这样做了。 - 您不是在此处测试值,而只是在资源上存在给定的属性并且它们不是无/空/空。
然后,根据您的 VPC 创建方式的性质:
如果您在另一个堆栈中控制 VPC,则您可以在那里对其特定值进行单元测试。 只要您和您的团队了解一旦建立更改测试是不好的(并且如果您发现自己经常这样做,那么您的代码需要更改为更可单元测试,而不是您的测试),那么您就可以合理相信一切都会按预期进行
如果您根本不控制 vpc(例如使用默认值或您的组织在其每个子帐户中创建了 vpc),那么您将需要比单元测试更高级别的测试 - 集成/合同测试和健康检查测试(即-测试可以在哪里获得凭据来检查事物)。
当使用 cdk deploy 或从 CodeDeploy 初始化 CloudFormation 部署时,From 方法“基本上”(泛化传入)仅使用现有的 SDK 方法(即:如 boto3)在部署时获取此信息。 无论如何,SDK 只是构建在aws cli
sdk 之上,这几乎是每个内部资源在运行时从其他资源获取所需信息的方式。
因为这些方法是从 CDK 团队(和 SDK 团队)内置的,所以您不必测试它们是否按预期从当前测试范围之外的事物导入(在此测试范围内未创建 vpc,因此它超出了其范围),因为您可以相信 AWS 将尽其所能确保其继续按预期运行。 (错误显然会发生,但通常是的) - 毕竟 - 你不会去测试每个导入是否有效,对吧? 或者您安装的每个库,其记录的功能都按预期独立工作(而不是您行为的一部分),对吗? from
方法是一回事。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.