Terraform Provider AWSのテストの書き方

[terraform provider aws](https://github.com/hashicorp/terraform-provider-aws)におけるテストの書き方について調べたのですが、Terraform自体のテストの書き方じゃなくてproviderの開発の話はあまりみつからなかったのでまとめました。

# 参考ドキュメントについて
公式の[Acceptance Tests](https://www.terraform.io/docs/extend/testing/acceptance-tests/index.html)
を読んで理解を深めました。

## そもそもどういうテストが行われているのか？
プルリクエストを作成するときにはAcceptanceTestがパスしていないといけないのでAcceptanceTestの書き方を主に記載します。
このAcceptanceテストではterraformのコマンドを実際に実行してAWS providerならAWS上にリソースの作成を行っていました。
なので内部ではterraformのスクリプトを用意してリソースの作成を行います。

## テストファイルはどれか
[terraform provider aws](https://github.com/hashicorp/terraform-provider-aws)のレポジトリに<対象リソース名>_test.goのような形でテストが用意されています。

### どのようにTerraformファイルを作っているのか
[公式のTestStepについての説明](https://www.terraform.io/docs/extend/testing/acceptance-tests/teststep.html)および
[cluster_test.go](https://github.com/hashicorp/terraform-provider-aws/blob/main/internal/service/ecs/cluster_test.go)を見てみると以下のような記述があります。
これがテスト定義になっています。

```cluster_test.go
func TestAccECSCluster_basic(t *testing.T) {
	var cluster1 ecs.Cluster
	rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
	resourceName := "aws_ecs_cluster.test"

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:     func() { acctest.PreCheck(t) },
		ErrorCheck:   acctest.ErrorCheck(t, ecs.EndpointsID),
		Providers:    acctest.Providers,
		CheckDestroy: testAccCheckClusterDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccClusterConfig(rName),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckClusterExists(resourceName, &cluster1),
					acctest.CheckResourceAttrRegionalARN(resourceName, "arn", "ecs", fmt.Sprintf("cluster/%s", rName)),
					resource.TestCheckResourceAttr(resourceName, "name", rName),
					resource.TestCheckResourceAttr(resourceName, "tags.%", "0"),
				),
			},
			{
				ResourceName:      resourceName,
				ImportStateId:     rName,
				ImportState:       true,
				ImportStateVerify: true,
			},
		},
	})
}
```

ここで

```
Config: testAccClusterConfig(rName)
```
と記載があります。
ここが実際にTerraformファイルの作成が行っている箇所です。
実際ファイルの下の方を見てみるとtestAccClusterConfigの定義があり見慣れたTerraformの定義がされています。

```
func testAccAWSEcsClusterConfig(rName string) string {
	return fmt.Sprintf(`
resource "aws_ecs_cluster" "test" {
  name = %q
}
`, rName)
}
```
なお名前については

```
rName := acctest.RandomWithPrefix("tf-acc-test")
```

と定義しており、テストの実行ごとにランダムな名前を付与して作るようになっている(おそらくリソースの重複なりでテストが失敗するのを避けたいのだろうと推定しています。)


### どこのリージョンにリソースは作られるの？
米国西部 (オレゴン)us-west-2
でした


## リソースのクリーンアップをおこなうSweepersについて
基本的にはacceptanceテスト終了時にはリソースを削除するが、
リソースの完全削除を行ってくれるようフレームワーク側で
[Sweepers](https://www.terraform.io/docs/extend/testing/acceptance-tests/sweepers.html)
を用意している。
リソースによってはSweepersの記載がないものもあるがテストの失敗時にリソースが残ったりすることもあるので用意したほうがよさそう

実際
[S3のSweeper](https://github.com/hashicorp/terraform-provider-aws/blob/main/internal/service/s3/sweep.go)を見てみると


```sweep.go
func init() {
	resource.AddTestSweepers("aws_s3_bucket", &resource.Sweeper{
		Name: "aws_s3_bucket",
		F:    testSweepS3Buckets,
		Dependencies: []string{
			"aws_s3_access_point",
			"aws_s3_bucket_object",
		},
	})
}
```
のように初期化時にSweeperを呼び出していました。

### Acceptance Testの実行方法
terraform-provider-awsのプルリクエストを見てみると
https://github.com/hashicorp/terraform-provider-aws/pull/10881
プルリクエストにはacceptance testingをパスした結果を添付する必要があります。

実行方法は

```
make testacc TEST=./aws TESTARGS='-run=<テスト対象のコンポーネント>
```

のようにします

なお

```
make test TEST=./aws TESTARGS='-run=<テスト対象のコンポーネント>
```
でもテストは実行できますがこちらはリソースの作成をおこなわないテストなので、このテストが通ってもAcceptanceTestが失敗したりしたので最終的には実際にリソース作成を行う必要がありました。

## 他のAWSリソースを使うテストはどうするのか？
実際にリソースの作成までおこなうように書かれていました。
例えばElastic Beanstalkのテストを見てみると[application_version_test.go](https://github.com/hashicorp/terraform-provider-aws/blob/main/internal/service/elasticbeanstalk/application_version_test.go)


```application_version_test.go
func testAccBeanstalkApplicationVersionConfigWithTags(randInt int, tag1, tag2 string) string {
	return fmt.Sprintf(`
resource "aws_s3_bucket" "default" {
  bucket = "tftest.applicationversion.bucket-%[1]d"
}
resource "aws_s3_bucket_object" "default" {
  bucket = aws_s3_bucket.default.id
  key    = "beanstalk/python-v1.zip"
  source = "test-fixtures/python-v1.zip"
}
resource "aws_elastic_beanstalk_application" "default" {
  name        = "tf-test-name-%[1]d"
  description = "tf-test-desc"
}
resource "aws_elastic_beanstalk_application_version" "default" {
  application = aws_elastic_beanstalk_application.default.name
  name        = "tf-test-version-label-%[1]d"
  bucket      = aws_s3_bucket.default.id
  key         = aws_s3_bucket_object.default.id
  tags = {
    firstTag  = "%[2]s"
    secondTag = "%[3]s"
  }
}
`, randInt, tag1, tag2)
}
```
としてサンプルアプリケーションをデプロイしていました
ちなみに[test-fixtures](https://github.com/hashicorp/terraform-provider-aws/blob/main/internal/service/elasticbeanstalk/test-fixtures/python-v1.zip)配下にこれらの入力ファイルはまとめられていました。

# まとめ
[terraform provider aws](https://github.com/hashicorp/terraform-provider-aws)におけるテストの書き方について調べたこと、わかったことをまとめました。
参考になれば幸いです。