Skip to content

Commit af06566

Browse files
test: integration test framework, integration tests and updated doc (PSKD-1537) (#490)
* test: (PSKD-1534) initial integration tests Signed-off-by: Ian Dominno <[email protected]> * test: refactor integration tests (PSKD-1537) Signed-off-by: [email protected] <[email protected]> * test: refactor integration tests (PSKD-1537) Signed-off-by: [email protected] <[email protected]> * test: refactor integration tests (PSKD-1537) Signed-off-by: [email protected] <[email protected]> * test: refactor integration tests (PSKD-1537) Signed-off-by: [email protected] <[email protected]> * test: refactor integration tests (PSKD-1537) Signed-off-by: [email protected] <[email protected]> * test: refactor integration tests (PSKD-1537) Signed-off-by: [email protected] <[email protected]> * docs: update TerratestDockerUsage.md (PSKD-1582) Signed-off-by: Ian Dominno <[email protected]> * test: refactor integration tests (PSKD-1537) Signed-off-by: Ian Dominno <[email protected]> * test: add example non default apply test (PSKD-1537) Signed-off-by: [email protected] <[email protected]> * docs: update TestingPhilosophy.md (PSKD-1582) Signed-off-by: Ian Dominno <[email protected]> * docs: update TestingPhilosophy.md (PSKD-1583) Signed-off-by: Ian Dominno <[email protected]> * chore: fix typos and add comments (PSKD-1537) Signed-off-by: Ian Dominno <[email protected]> * fix: address review comments (PSKD-1537) Signed-off-by: Ian Dominno <[email protected]> * fix: address review comments (PSKD-1537) Signed-off-by: Ian Dominno <[email protected]> --------- Signed-off-by: Ian Dominno <[email protected]> Signed-off-by: [email protected] <[email protected]> Co-authored-by: [email protected] <[email protected]>
1 parent b1fd3c5 commit af06566

15 files changed

+798
-13
lines changed

Dockerfile.terratest

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
FROM golang:1.24
22

3-
# Install terraform from apt repository and terratest_log_parser
3+
# Install terraform from apt repository, terratest_log_parser, jq, azure-cli
44
RUN \
55
apt-get update \
66
&& apt-get install -y jq lsb-release \
@@ -11,7 +11,8 @@ RUN \
1111
&& apt update \
1212
&& apt install terraform \
1313
&& ssh-keygen -f ~/.ssh/id_rsa -P "" \
14-
&& go install github.com/gruntwork-io/terratest/cmd/terratest_log_parser@latest
14+
&& go install github.com/gruntwork-io/terratest/cmd/terratest_log_parser@latest \
15+
&& curl -sL https://aka.ms/InstallAzureCLIDeb | bash
1516

1617
WORKDIR /viya4-iac-azure/test
1718

docs/user/TerratestDockerUsage.md

Lines changed: 59 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,24 @@ The Docker image `viya4-iac-azure-terratest` will contain Terraform and Go execu
2121
### Docker Environment File for Azure Authentication
2222

2323
Follow either one of the authentication methods that are described in [Authenticating Terraform to access Azure](./TerraformAzureAuthentication.md), and create a file with the authentication variable values to use with container invocation. Store these values outside of this repository in a secure file, such as
24-
`$HOME/.azure_docker_creds.env`. Protect that file with Azure credentials so that only you have Read access to it. **NOTE**: Do not use quotation marks around the values in the file, and be sure to avoid any trailing blank spaces.
24+
`$HOME/.azure_docker_creds.env`.
25+
26+
**NOTE**: Do not use quotation marks around the values in the file, and be sure to avoid any trailing blank spaces.
27+
28+
#### Public Access Cidrs Environment File
29+
30+
In order to run ```terraform apply``` integration tests, you will also need to define your ```TF_VAR_public_cidrs``` as described in [Admin Access](../CONFIG-VARS.md#admin-access), and create a file with the public access cidr values to use with container invocation. Store these values in [CIDR notation](https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing) outside of this repository in a secure file, such as `$HOME/.azure_public_cidrs.env`. Protect that file with public access cidr values so that only you have Read access to it. Below is an example of what the file should look like.
31+
32+
```bash
33+
TF_VAR_public_cidrs=["123.456.7.8/16", "98.76.54.32/32"]
34+
```
2535

2636
Now each time you invoke the container, specify the file with the [`--env-file`](https://docs.docker.com/engine/reference/commandline/run/#set-environment-variables--e---env---env-file) option to pass Azure credentials to the container.
2737

2838
### Docker Volume Mounts
2939

30-
Run the following command:
31-
`--volume="$(pwd)":/viya4-iac-azure`
40+
To mount the current working directory, add the following argument to the docker run command:
41+
`--volume="$(pwd)":/viya4-iac-azure`
3242
Note that the project must be mounted to the `/viya4-iac-azure` directory.
3343

3444
## Command-Line Arguments
@@ -42,9 +52,9 @@ The `terratest_docker_entrypoint.sh` script supports several command-line argume
4252

4353
## Running Terratest Commands
4454

45-
### Running the Default Tests
55+
### Running the Plan Tests
4656

47-
To run the default suite of unit tests (only `terraform plan`), run the following Docker command:
57+
To run the suite of unit tests (only `terraform plan`), run the following Docker command:
4858

4959
```bash
5060
# Run from the ./viya4-iac-azure directory
@@ -54,6 +64,20 @@ docker run --rm \
5464
viya4-iac-azure-terratest
5565
```
5666

67+
### Running the Apply Tests
68+
69+
To run the suite of integration tests (only `terraform apply`), run the following Docker command:
70+
71+
```bash
72+
# Run from the ./viya4-iac-azure directory
73+
docker run --rm \
74+
--env-file=$HOME/.azure_docker_creds.env \
75+
--env-file=$HOME/.azure_public_cidrs.env \
76+
--volume "$(pwd)":/viya4-iac-azure \
77+
viya4-iac-azure-terratest \
78+
  -r=".*Apply.*"
79+
```
80+
5781
### Running a Specific Go Test
5882

5983
To run a specific test, run the following Docker command with the `-r` option:
@@ -62,12 +86,27 @@ To run a specific test, run the following Docker command with the `-r` option:
6286
# Run from the ./viya4-iac-azure directory
6387
docker run --rm \
6488
--env-file=$HOME/.azure_docker_creds.env \
89+
--env-file=$HOME/.azure_public_cidrs.env \ #env file for integration tests
6590
--volume "$(pwd)":/viya4-iac-azure \
6691
viya4-iac-azure-terratest \
6792
  -r="YourTest"
6893
```
6994
To run multiple tests, pass in a regex to the `-r` option - "TestName1|TestName2|TestName3"
7095

96+
#### Running a Specific Integration Go Test
97+
98+
To run a specific integration test, modify the main test runner function (e.g. YourIntegrationTestMainFunction) to define the test name you desire and run the following Docker command with the `-r` option:
99+
100+
```bash
101+
# Run from the ./viya4-iac-azure directory
102+
docker run --rm \
103+
--env-file=$HOME/.azure_docker_creds.env \
104+
--env-file=$HOME/.azure_public_cidrs.env \
105+
--volume "$(pwd)":/viya4-iac-azure \
106+
viya4-iac-azure-terratest \
107+
  -r="YourIntegrationTestMainFunction"
108+
```
109+
71110
### Running a Specific Go Package and Test
72111

73112
If you want to specify the Go package and test name, run the following Docker command with the following options:
@@ -82,6 +121,21 @@ docker run --rm \
82121
  -p="YourPackage"
83122
```
84123

124+
#### Running a Specific Integration Go Package and Test
125+
126+
To run a specific integration Go package and test name, modify the main test runner function in the desired packaged to define the test name you want and run the following Docker command with the following options:
127+
128+
```bash
129+
# Run from the ./viya4-iac-azure directory
130+
docker run --rm \
131+
--env-file=$HOME/.azure_docker_creds.env \
132+
--env-file=$HOME/.azure_public_cidrs.env \
133+
--volume "$(pwd)":/viya4-iac-azure \
134+
viya4-iac-azure-terratest \
135+
  -r="YourIntegrationTestMainFunction" \
136+
  -p="YourPackage"
137+
```
138+
85139
### Running the Go Tests with verbose mode
86140

87141
If you want to run the tests in verbose mode, run the Docker command with the `-v` option:

docs/user/TestingPhilosophy.md

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ The unit tests in this project are designed to quickly and efficiently verify th
1515

1616
The unit tests are written as [table-driven tests](https://go.dev/wiki/TableDrivenTests) so that they are easier to read, understand, and expand. The tests are divided into two packages, [defaultplan](../../test/defaultplan) and [nondefaultplan](../../test/nondefaultplan).
1717

18-
The test package defaultplan validates the default values of a Terraform plan. This testing ensures that there are no regressions in the default behavior of the code base. The test package nondefaultplan modifies the input values before running the Terraform plan. After generating the plan file, the test verifies that it contains the expected values. Both sets of tests are written to be table-driven.
18+
The test package defaultplan validates the default values of a `terraform plan`. This testing ensures that there are no regressions in the default behavior of the code base. The test package nondefaultplan modifies the input values before running the Terraform plan. After generating the plan file, the test verifies that it contains the expected values. Both sets of tests are written to be table-driven.
1919

2020
To see an example, look at the `TestPlanStorage` function in the defaultplan/storage_test.go file that is shown below.
2121

@@ -59,19 +59,59 @@ To create a unit test, you can add an entry to an existing test table if it's re
5959
6060
### Integration Testing
6161
62-
The integration tests are designed to thoroughly verify the code base using `terraform apply`. Unlike the unit tests, these tests provision resources in cloud platforms. Careful consideration is required to avoid unnecessary infrastructure costs.
63-
64-
These test are still a work-in-progress. We will update these sections once we have more examples to reference.
62+
The integration tests are designed to thoroughly verify the code base using `terraform apply`. The tests are intended to validate that the cloud provider creates the expected resources. Unlike the unit tests, these tests provision resources through the cloud provider. Careful consideration is required to avoid unnecessary infrastructure costs. The integration test framework is designed to optimize resource utilization and reduce associated costs by enabling multiple test cases to run against a single provisioned resource group, provided the test cases are compatible with the resource’s configuration and state.
6563
6664
### Integration Testing Structure
6765
68-
These test are still a work-in-progress. We will update these sections once we have more examples to reference.
66+
The integration tests are also written as [table-driven tests](https://go.dev/wiki/TableDrivenTests) so that they are easier to read, understand, and expand. The tests are divided into two packages, [defaultapply](../../test/defaultapply) and [nondefaultapply](../../test/nondefaultapply).
67+
68+
The test package defaultapply validates that the provisioned resources match the default configuration values. The test package nondefaultapply validates that, when given non-default input configuration values, the provisioned resources match the input configuration values. This level of integration testing ensures the cloud provider is correctly creating the resources via Terraform.
69+
70+
### Resource Management
71+
72+
As running `terraform apply` provisions infrastructure, it inherently incurs costs. To manage and minimize these expenses, it is essential that our testing framework optimizes resource utilization and ensures proper teardown and cleanup of any infrastructure created during testing.
73+
74+
To support this, we have implemented main function test runners for our integration tests that handle the setup of the testing environment by provisioning resources based on the provided Terraform options. These runners also include deferred cleanup routines that automatically decommission resources once tests are completed.
75+
76+
We encourage developers contributing integration tests to be mindful of resource usage. Add your tests to the defaultapply suite if no configuration changes are needed. If testing non default options, please modify the nondefault suite as long as the new options do not conflict with the existing overrides. If the existing packages do not fit your testing needs, please add a new non default apply package, test runner, and test suite for your unique option configuration.
77+
78+
### Error Handling
79+
80+
Terratest provides some flexibility with how to [handle errors](https://terratest.gruntwork.io/docs/testing-best-practices/error-handling/). Every method in Terratest comes in two versions (e.g., `terraform.Apply` and `terraform.ApplyE` )
81+
82+
* `terraform.Apply`: The base method takes a `t *testing.T` as an argument. If the method hits any errors, it calls `t.Fatal` to fail the test
83+
* `terraform.ApplyE`: Methods that end with the capital letter `E` always return an error as the last argument and never call `t.Fatal` themselves. This allows you to decide how to handle errors.
84+
85+
We recommend using the capital letter `E` version of Terratest methods. This allows the test to handle the error rather than immediately calling `t.Fatal`. `t.Fatal` causes test run to exit, preventing other tests from running and stopping the deferred cleanup routine from being executed.
86+
87+
Here's an example of how we handle terratest method calls:
88+
89+
```go
90+
resourceGroup, err := azure.GetAResourceGroupE(resourceGroupName, os.Getenv("TF_VAR_subscription_id"))
91+
if err != nil {
92+
t.Errorf("Error: %s\n", err)
93+
}
94+
}
95+
```
96+
97+
### Adding Integration Tests
98+
99+
To create an integration test, you can add a new test file with your table tests to the appropriate package and update the desired main function test runner to call and run your test. If you don't see a main function test runner that fits your needs, you are welcome to create a new package, main function test runner, and test suite in a similar format.
100+
101+
Below is an example of a possible structure for the new package, main function test runner, and test suite:
102+
103+
.
104+
└── test/
105+
└── nondefaultapply/
106+
└── nondefaultapplycustomconfig/
107+
├── non_default_apply_custom_config_main_test.go
108+
└── test_custom_config.go
109+
69110

70111
## How to Run the Tests Locally
71112

72113
Before changes can be merged, all unit tests must pass as part of the SAS CI/CD process. Unit tests are automatically run against every PR using the [Dockerfile.terratest](../../Dockerfile.terratest) Docker image. Refer to [TerratestDockerUsage.md](./TerratestDockerUsage.md) document for more information about running the tests locally.
73114

74-
75115
## Additional Documents
76116

77117
* [Go Table-Driven Testing](https://go.dev/wiki/TableDrivenTests)
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright © 2025, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package defaultapply
5+
6+
import (
7+
"test/helpers"
8+
"testing"
9+
)
10+
11+
func TestApplyDefaultMain(t *testing.T) {
12+
// terrafrom init and apply using the default configuration
13+
terraformOptions, plan := helpers.InitPlanAndApply(t, nil)
14+
15+
// deferred cleanup routine for the resources created by the terrafrom init and apply after the test have been run
16+
defer helpers.DestroyDouble(t, terraformOptions)
17+
18+
// Drop in new test cases here
19+
testApplyResourceGroup(t, plan)
20+
testApplyVirtualMachine(t, plan)
21+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// Copyright © 2025, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package defaultapply
5+
6+
import (
7+
"os"
8+
"test/helpers"
9+
"testing"
10+
11+
"github.com/gruntwork-io/terratest/modules/azure"
12+
"github.com/gruntwork-io/terratest/modules/terraform"
13+
"github.com/stretchr/testify/assert"
14+
)
15+
16+
func testApplyResourceGroup(t *testing.T, plan *terraform.PlanStruct) {
17+
resourceMapName := "azurerm_resource_group.aks_rg[0]"
18+
resourceGroupName := helpers.RetrieveFromPlan(plan, resourceMapName, "{$.name}")()
19+
resourceGroup, err := azure.GetAResourceGroupE(resourceGroupName, os.Getenv("TF_VAR_subscription_id"))
20+
if err != nil {
21+
t.Errorf("Error: %s\n", err)
22+
}
23+
24+
// validate resource group resource from the cloud provider match the plan
25+
tests := map[string]helpers.ApplyTestCase{
26+
"resourceGroupExistsTest": {
27+
Expected: nil,
28+
Actual: resourceGroup,
29+
AssertFunction: assert.NotEqual,
30+
Message: "Resource group does not exist",
31+
},
32+
"resourceGroupLocationTest": {
33+
ExpectedRetriever: helpers.RetrieveFromPlan(plan, resourceMapName, "{$.location}"),
34+
ActualRetriever: helpers.RetrieveFromStruct(resourceGroup, "Location"),
35+
Message: "Resource group location is incorrect",
36+
},
37+
"resourceGroupNameTest": {
38+
Expected: resourceGroupName,
39+
ActualRetriever: helpers.RetrieveFromStruct(resourceGroup, "Name"),
40+
Message: "Resource group name is incorrect",
41+
},
42+
"resourceGroupIdTest": {
43+
Expected: "nil",
44+
ActualRetriever: helpers.RetrieveFromStruct(resourceGroup, "ID"),
45+
AssertFunction: assert.NotEqual,
46+
Message: "Resource group ID is nil",
47+
},
48+
}
49+
50+
helpers.RunApplyTests(t, tests)
51+
}

0 commit comments

Comments
 (0)