iT邦幫忙

2023 iThome 鐵人賽

DAY 26
0

可以用哪些方法做整合測試?

今天來介紹如何撰寫 Pulumi 的整合測試程式。整合測試的目標就是要讓 Pulumi 真正的建立雲端資源,並驗證所建立的雲端資源是否正確。

因此理論上是可以透過任意程式語言,使用 CLI 下達 pulumi uppulumi stack 等指令進行整合測試。或者也能透過 Pulumi 新推出的 Automation API 撰寫自動執行 Pulumi 的功能,進行整合測試。

不過今天要介紹的是 Pulumi 內建的整合測試工具 Pulumi golang SDK。我想,可能是因為 Pulumi 是使用 Golang 撰寫,因此本身就有測試的需求,所以才專門有 Go 語言的整合測試 SDK 可以使用。其他程式語言就只能透過 Automation API 來做。

那麼 Pulumi golang SDK 的整合測試功能與 Automation API 又差在哪裡呢?

  • Automation API 主要是提供自動化 Pulumi 操作功能用的,可以在不借助 Pulumi CLI 的狀況下執行 Pulumi 程式,適合用在開發自助服務,或自動化服務中。 Automation API 也有支援 Golang 的版本。
  • Pulumi golang integration test SDK 則是專門提供整合測試使用,不像 Automation API 那麼靈活。

撰寫介紹 Golang integration SDK

Golang integration SDK 的套件位於 "github.com/pulumi/pulumi/pkg/v3/testing/integration",由套件名就可以知道他是專門做整合測試用的。

主要使用方式就是定義要執行測試的待測專案位置、要測的 config、secret 等資訊、以及驗證部署結果的測試程式碼。

今天一樣是使用之前的 VPC component resource 專案做為待測程式。以下為使用 Golang 撰寫測試的 template。

func TestVpc(t *testing.T) {
	cwd, err := os.Getwd()
	if err != nil {
		t.FailNow()
	}
	test := integration.ProgramTestOptions{
		Dir:         path.Join(cwd, "../aws-vpc-ts"),
		Quick:       true,
		SkipRefresh: true,

		Config: map[string]string{
		},
		ExtraRuntimeValidation: func(t *testing.T, stack integration.RuntimeValidationStackInfo) {
		},
	}

	integration.ProgramTest(t, &test)

}

ProgramTestOptions 設定介紹

在這個 template 中,使用 integration.ProgramTestOptions struct 定義了要測試的所有參數。最後使用 integration.ProgramTest 運行測試。

在 integration.ProgramTestOptions struct 中,填入的設定說明如下:

  • Dir: 指定待測程式專案的位置,以下是專案的大略架構,測試程式碼在 vpc_test.go 中,可以藉由下圖的相對關係看出我的待測專案位於 ../aws-vpc-ts
.
├── aws-vpc-ts
│ ├── vpc.ts
│ ├── index.ts
│ └── package.json
│
└── integration-test
  ├── go.mod
  └── vpc_test.go
  • Quick: 根據說明,Quick 等同於 SkipPreview + SkipExportImport + SkipEmptyPreviewUpdate,也就是跳過
    • 總之就是跳過平常執行 pulumi up 的 preview 之類的工作
  • SkipRefresh: 跳過 refresh 階段,平常在執行 pulumi up 的時候也會跳過 refresh,如果要 refresh 還需要特別使用 pulumi up --refresh
  • Config: 在整合測試中,會自動建立一個運行測試的 stack,這個 Config 就是該 Stack 的 config 設定內容
  • ExtraRuntimeValidation: 最重要的功能,這個參數需要傳一個 callback function。整合測試建立完資源後,就會呼叫這個 function 執行驗證。

此外如果有用到 Secret 的話,可以透過 Secrets 參數傳入,也設定要可以使用的 SecretsProvider

更詳細的資訊就要參考 integration.ProgramTestOptions 的文件了

開始撰寫測試程式

介紹完參數的作用後,就可以開始撰寫整合測試程式了。首先先來設定 Config 的內容,這邊 Config 的資料一定要是設定字串。

Config: map[string]string{
			"aws:region":    "ap-east-1",
			"vpcCidrBlocks": "10.18.0.0/16",
			"numOfAzs":      "1",
			"numOfPublic":   "2",
			"numOfPrivate":  "1",
		},

接著就來撰寫 ExtraRuntimeValidation 內,驗證的程式碼。

ExtraRuntimeValidation 中,第二個參數 stack 就是建立好的 stack。可以透過 stack.Deployment 得到所有建立的資源列表。

只要可以把列表中每個我們想要驗證的資源都找出來進行驗證即可

驗證 VPC 是否有使用傳入的 cidrBlock 建立

以下是驗證資源的標準方式,使用迴圈列出所有資源內容,並對我們關注的資源做處理:

// 列出所有資源
for _, res := range stack.Deployment.Resources {
	// 如果資源是 VPC
	if res.Type == "aws:ec2/vpc:Vpc" {
		// 取得 cidrBlock 的內容
		cidrBlock := res.Outputs["cidrBlock"].(string)
		// 驗證是否為傳入的 Config 的值
		assert.Equal(t, cidrBlock, "10.18.0.0/16")
	}
}

驗證是否有建立對應數量的 Subnet

首先要怎麼知道是 Public Subnet 還是 Private Subnet 呢? AWS 的 Subnet 上並沒有太多的資訊告訴我們這點。

這邊我想到的方式是透過 Subnet 關聯到的 RouteTable 判斷是 Public 還是 Private。如果是 Public Subnet,應該會使用 defaultRouteTable。如果是 Private 則會有自己的 RouteTable。因為有多少個 Subnet 就會建立多少個 RouteTableAssociation,因此這邊就使用 RouteTableAssociation 判斷 Subnet 的數量。

當然也可以透過在 component resource 上定義 Output 標註是 private 或是 public subnet。這樣就比較省事,只要判斷 component resource 的資訊就可以。

// 列出所有資源

var defaultRouteTableId string

routeTableAssociations := make([]apitype.ResourceV3, 0)

for _, res := range stack.Deployment.Resources {
	// 如果資源是 VPC
	if res.Type == "aws:ec2/vpc:Vpc" {
		// 記錄下 default Route Table 的 ID 
		defaultRouteTableId = res.Outputs["defaultRouteTableId"].(string)
	}

	// 如果資源是 RouteTableAssociation
	if res.Type == "aws:ec2/routeTableAssociation:RouteTableAssociation" {
		// 將所有 RouteTableAssociation 蒐集到 array 中
		routeTableAssociations = append(routeTableAssociations, res)
	}
}

// 透過 routeTableAssociations 統計 subnet 的數量 

publicSubnetIds := mapset.NewSet[string]()
privateSubnetIds := mapset.NewSet[string]()

for _, rta := range routeTableAssociations {
	if rta.Outputs["routeTableId"].(string) == defaultRouteTableId {
		// 如果 routeTableAssociations 上的是 default route table,則視為 public subnet
		publicSubnetIds.Add(rt.Outputs["subnetId"].(string))
	} else {
		// 否則就是 private subnet
		privateSubnetIds.Add(rt.Outputs["subnetId"].(string))
	}
}

// 是否有 2 個 public subnet
assert.Equal(t, publicSubnetIds.Cardinality(), 2)

// 是否有 1 個 private subnet
assert.Equal(t, privateSubnetIds.Cardinality(), 1)

這個範例中使用了 golang-set 來收集 subnetId 的資料,set 資料結構的好處就是同樣的資料只會出現一次,不用煩惱重複加一樣的 subnetId 導致判斷出錯。

驗證是否只在一個 AZ 上建立 Subnet

最後一個範例,就是驗證所有 Subnet 是否只用到一個 AZ。因為在 config 中 numOfAz 設定為 1,因此所有 Subnet 都必須建立在同一個 AZ 中。

驗證方式一樣是將所有 Subnet 的 availabilityZoneId 放進 Set 中。最後檢查 Set 內的元素個數即可。

apset.NewSet[string]()

// 列出所有資源
for _, res := range stack.Deployment.Resources {
	// 如果資源是 Subnet
	if res.Type == "aws:ec2/subnet:Subnet" {
		az := res.Outputs["availabilityZoneId"].(string)
		azSet.Add(az)
	}
}

// 只用到 1 個 AZ
assert.Equal(t, azSet.Cardinality(), 1)

執行整合測試

最後,執行整合測試的方式就很簡單,就跟執行一般 Golang 的測試一樣,使用 go test <filename> 執行。

$ go test vpc_test.go
ok      command-line-arguments  189.810s

如果驗證都通過,就會輸出 ok。這邊可以看到這麼簡單的整合測試案例,就需要花費 3 分多鐘的執行。與昨天單元測試都是 1 秒內完成的測試比起來,要費時多了。

那如果錯誤的話,就會看到 fail ,並且會輸出錯誤的原因。

$ go test vpc_test.go
vpc_test.go:77 1 does not equal 2
--- FAIL: TestVpc (190.50s)
... stacktrace ...
FAIL
FAIL    command-line-arguments  190.954s
FAIL

上一篇
[Day 25] 撰寫 Pulumi 的單元測試
下一篇
[Day 27] 使用 Automation API 進行整合測試
系列文
30 天學習 Pulumi:用各種程式語言控制雲端資源30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言