Skip to content →

TDD with IaC using Terraform Test

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 or Apply. But be aware that asserts against generated values from the cloud provider are only available using Apply
  • 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 testsand 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.

Published in azure programming

Subscribe
Notify of
guest

0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x