In Terraform 1.6, Terraform Test was rearchitected and reintroduced. This feature offers a structured and automated way for users to write and execute tests against their infrastructure's code. Terraform Test's primary aim is to enhance the predictability and safety of infrastructure changes. No more manual checks between environment promotions and deployments.
This blog post looks at the benefits and typical autonomy of a Terraform test and how to do Test-Driven Development using IaC and Terraform.
Test design
On a high level, you write your terraform test code in a specific .hcl
file. This file consists of a series of run blocks
that run, in sequence from top to bottom, a particular part of your infrastructure, e.g., module
.
Let's look at an example:
provider "azurerm" {
features {}
subsription_id = "your-subscription-id"
}
run "setup_tests"{
command = apply
module {
source = "./tests/setup"
}
}
run "aks-creation" {
command = apply
variables {
resource_group_name = run.setup_tests.resource_group.name
location = run.setup_tests.resource_group.location
admin_group_object_id = "secret-id"
}
module {
source = "./modules/aks"
}
assert {
condition = azurerm_kubernetes_cluster.aks.name == run.setup_tests.resource_group.name
error_message = "Wrong aks name"
}
}
This simple test showcases a number of important concepts.
- You can target whatever code level you want, either a module or the entire IaC setup
- Test run in sequence, and you can use the output from one step as input to another
- The tests can run using either
Plan
orApply
. But be aware that asserts against generated values from the cloud provider are only available usingApply
- Variables need to be sent in using a specific
variables
block - The tests can be whatever name you want
It is possible, and often advisable, to have multiple assert
for a single run. Those familiar with running tests against “regular” code, e.g., C#, will often split out different tests for each assert. One Terraform test difference is the creation of resources, which can be time-consuming. Multiple assertions
in a single run speeds up the testing run.
The https://github.com/fredrkl/aks_fluxv2_demo repo shows the example above and its context.
In memory state file
Terraform test creates a state file in memory during execution. It adds to the file as the tests run in sequence, and by the end, it starts tearing down the infrastructure it created. You might run into some scenarios where your test had a bug, and the cleanup fails. To help with potential cleanup and to keep the tests as isolated as possible, it might be a good idea to create dedicated “containers” for all test-generated resources. In Azure, that would typically be a resource group
.
I keep a dedicated module in a folder called setup, which creates necessary test run resources. One example is a resource group
with a random name to store all test-generated resources.
Benefits
By running tests against your IaC using Terraform tests, you will be able to spot many potential bugs that are only spotted when executing the IaC code:
- Using the wrong Provider
- Wrong references
- Changes in your IaC that you thought did not have an impact
One of the greatest benefits of having tests is your increased confidence in potential changes. The tests will run against your changes, verifying that you did not break anything.
As with other types of testing, it is not possible to verify with 100% confidence that your changes will not break anything. However, Terraform test is a step in the right direction and can help you adopt continuous integration (CI) and continuous delivery (CD).
Test Execution
How and when you execute your test depends on your pursuit of CI/CD and, to some extent, your personal preference. I like to be able to run the tests locally to quickly iterate over changes, however those changes will only be verified once it is merged into the main branch. With CI, we aim at short-lived branches and continuously integrate your changes into everyone else's.
Local test runs
The Terraform test CLI makes it easy to run the tests locally. The only “trick” might be to explicitly remove any state storage configuration. The test state file is kept in memory anyway, and you might not be able to access the storage medium. Both are good things.
Start off your local developer inner loop by running:
terraform init --backend=false
This does all the steps you expect from terraform init
, but ignores any backend configuration.
Now, you should be able to run the following:
terraform test
locally. The test command looks for a folder called tests
and executes all of the .hcl
test files in it.
If you instead like to mock out the state storage you can create a dedicated mock file, e.g., mock.azurerm.tfbackend.
resource_group_name = "terraform-state-mock"
storage_account_name = "terraformstatemock"
container_name = "mock"
By running, terraform init -backend-config=mock.azurerm.of backend
, you, in effect, swap out your state storage.
CI runs
It is important to run the tests as part of your continuous integration and not only as a local developer flow. Dave Fowley has many great videos on the importance of continuous Integration and what that means.
Test Driven Development (TDD)
Test-Driven Development (TDD) is a software development approach in which tests are written before the actual code. In this process, developers first write failing tests, then produce code to pass those tests, and finally refactor the code for optimization. The primary benefits of TDD include a clearer understanding of the requirements and design, higher quality code, reduced debugging time, safety in refactoring, and quicker feedback cycles.
Terraform test can help facilitate TDD.
Conclusion
Terraform Test, reintroduced in Terraform 1.6, provides a structured, automated way to write and execute tests against infrastructure code, enhancing predictability and safety of changes. The tests are written in a specific .hcl
file and run in sequence, targeting either a module or the entire IaC setup. The tests can run using either Plan
or Apply
, with variables sent in using a specific variables
block. Terraform Test creates a state file in memory during execution, and it's advisable to create dedicated containers for all test-generated resources. Running tests against your IaC can help spot potential bugs and increase confidence in changes, aiding in the adoption of continuous integration and continuous delivery.