gdt is a testing library that allows test authors to cleanly describe tests in a YAML file. gdt reads YAML files that describe a test's assertions and then builds a set of Go structures that the standard Go testing package can execute.
This github.com/gdt-dev/kube (shortened hereafter to gdt-kube) repository is a companion Go library for gdt that allows test authors to cleanly describe functional tests of Kubernetes resources and actions using a simple, clear YAML format. gdt-kube parses YAML files that describe Kubernetes client/API requests and assertions about those client calls.
gdt-kube is a Go library and is intended to be included in your own Go application's test code as a Go package dependency.
Import the gdt and gdt-kube libraries in a Go test file:
import ( "github.com/gdt-dev/gdt" gdtkube "github.com/gdt-dev/kube" )In a standard Go test function, use the gdt.From() function to instantiate a test object (either a Scenario or a Suite) that can be Run() with a standard Go context.Context and a standard Go *testing.T type:
func TestExample(t *testing.T) { s, err := gdt.From("path/to/test.yaml") if err != nil { t.Fatalf("failed to load tests: %s", err) } ctx := context.Background() err = s.Run(ctx, t) if err != nil { t.Fatalf("failed to run tests: %s", err) } }To execute the tests, just run go test per the standard Go testing practice.
gdt is a declarative testing framework and the meat of your tests is going to be in the YAML files that describe the actions and assertions for one or more tests. Read on for an explanation of how to write tests in this declarative YAML format.
A gdt test scenario (or just "scenario") is simply a YAML file.
All gdt scenarios have the following fields:
name: (optional) string describing the contents of the test file. If missing or empty, the filename is used as the namedescription: (optional) string with longer description of the test file contentsdefaults: (optional) is a map, keyed by a plugin name, of default options and configuration values for that plugin.fixtures: (optional) list of strings indicating named fixtures that will be started before any of the tests in the file are runtests: list ofSpecspecializations that represent the runnable test units in the test scenario.
To set gdt-kube-specific default configuration values for the test scenario, set the defaults.kube field to an object containing any of these fields:
defaults.kube.config: (optional) file path to akubeconfigto use for the test scenario.defaults.kube.context: (optional) string containing the name of the kube context to use for the test scenario.defaults.kube.namespace: (optional) string containing the Kubernetes namespace to use when performing some action for the test scenario.
As an example, let's say that I wanted to override the Kubernetes namespace and the kube context used for a particular test scenario. I would do the following:
name: example-test-with-defaults defaults: kube: context: my-kube-context namespace: my-namespaceAll gdt test specs have the same [base fields][base-spec-fields]:
name: (optional) string describing the test unit.description: (optional) string with longer description of the test unit.timeout: (optional) an object containing [timeout information][timeout] for the test unit.timeout: (optional) a string duration of time the test unit is expected to complete within.retry: (optional) an object containing retry configurationu for the test unit. Some plugins will automatically attempt to retry the test action when an assertion fails. This field allows you to control this retry behaviour for each individual test.retry.interval: (optional) a string duration of time that the test plugin will retry the test action in the event assertions fail. The default interval for retries is plugin-dependent.retry.attempts: (optional) an integer indicating the number of times that a plugin will retry the test action in the event assertions fail. The default number of attempts for retries is plugin-dependent.retry.exponential: (optional) a boolean indicating an exponential backoff should be applied to the retry interval. The default is is plugin-dependent.wait(optional) an object containing wait information for the test unit.wait.before: a string duration of time that gdt should wait before executing the test unit's action.wait.after: a string duration of time that gdt should wait after executing the test unit's action.
gdt-kube test specs have some additional fields that allow you to take some action against a Kubernetes API and assert that the response from the API matches some expectation:
config: (optional) file path to thekubeconfigto use for this specific test. This allows you to override thedefaults.configvalue from the test scenario.context: (optional) string containing the name of the kube context to use for this specific test. This allows you to override thedefaults.contextvalue from the test scenario.namespace: (optional) string containing the name of the Kubernetes namespace to use when performing some action for this specific test. This allows you to override thedefaults.namespacevalue from the test scenario.kube: (optional) an object containing actions and assertions the test takes against the Kubernetes API server.kube.get: (optional) string or object containing a resource identifier (e.g.pods,po/nginxor label selector for resources that will be read from the Kubernetes API server.kube.create: (optional) string containing either a file path to a YAML manifest or a string of raw YAML containing the resource(s) to create.kube.apply: (optional) string containing either a file path to a YAML manifest or a string of raw YAML containing the resource(s) for whichgdt-kubewill perform a Kubernetes Apply call.kube.delete: (optional) string or object containing either a resource identifier (e.g.pods,po/nginx, a file path to a YAML manifest, or a label selector for resources that will be deleted.var: (optional) an object describing variables that can have values saved and referred to by subsequent test specs. Each key in thevarobject is the name of the variable to define.var.$VARIABLE_NAME.from: (required) a JSONPath expression describing where the variable with name$VARIABLE_NAMEshould source its value from the Kubernetes resource returned in akubectl getresponse.assert: (optional) object containing assertions to make about the action performed by the test.assert.require: (optional) a boolean indicating whether a failed assertion will cause the test scenario's execution to stop. The default behaviour ofgdtis to continue execution of subsequent test specs in a test scenario when an assertion fails.assert.error: (optional) string to match a returned error from the Kubernetes API server.assert.len: (optional) int with the expected number of items returned.assert.notfound: (optional) bool indicating the test author expects the Kubernetes API to return a 404/Not Found for a resource.assert.unknown: (optional) bool indicating the test author expects the Kubernetes API server to respond that it does not know the type of resource attempting to be fetched or created.assert.matches: (optional) a YAML string, a filepath, or amap[string]interface{}representing the content that you expect to find in the returned result from thekube.getcall. Ifassert.matchesis a string, the string can be either a file path to a YAML manifest or inline an YAML string containing the resource fields to compare. Only fields present in the Matches resource are compared. There is a check for existence in the retrieved resource as well as a check that the value of the fields match. Only scalar fields are matched entirely. In other words, you do not need to specify every field of a struct field in order to compare the value of a single field in the nested struct.assert.conditions: (optional) a map, keyed byConditionTypestring, of any of the following:- a string containing the
Statusvalue that theConditionwith theConditionTypeshould have. - a list of strings containing the
Statusvalue that theConditionwith theConditionTypeshould have. - an object containing two fields:
statuswhich itself is either a single string or a list of strings containing theStatusvalues that theConditionwith theConditionTypeshould havereasonwhich is the exact string that should be present in theConditionwith theConditionType
- a string containing the
assert.placement: (optional) an object describing assertions to make about the placement (scheduling outcome) of Pods returned in thekube.getresult.assert.placement.spread: (optional) an single string or array of strings for topology keys that the Pods returned in thekube.getresult should be spread evenly across, e.g.topology.kubernetes.io/zoneorkubernetes.io/hostname.assert.placement.pack: (optional) an single string or array of strings for topology keys that the Pods returned in thekube.getresult should be bin-packed within, e.g.topology.kubernetes.io/zoneorkubernetes.io/hostname.assert.json: (optional) object describing the assertions to make about resource(s) returned from thekube.getcall to the Kubernetes API server.assert.json.len: (optional) integer representing the number of bytes in the resulting JSON object after successfully parsing the resource.assert.json.paths: (optional) map of strings where the keys of the map are JSONPath expressions and the values of the map are the expected value to be found when evaluating the JSONPath expressionassert.json.path-formats: (optional) map of strings where the keys of the map are JSONPath expressions and the values of the map are the expected format of the value to be found when evaluating the JSONPath expression. See the list of valid format stringsassert.json.schema: (optional) string containing a filepath to a JSONSchema document. If present, the resource's structure will be validated against this JSONSChema document.
Here are some examples of gdt-kube tests.
Testing that a Pod with the name nginx exists:
name: test-nginx-pod-exists tests: - kube: get: pods/nginx # These are equivalent. "kube.get" is a shortcut for the longer object.field # form above. - kube.get: pods/nginxTesting that a Pod with the name nginx does not exist:
name: test-nginx-pod-not-exist tests: - kube: get: pods/nginx assert: notfound: trueTesting that there are two Pods having the label app:nginx:
name: list-pods-with-labels tests: # You can use the shortcut kube.get - name: verify-pods-with-app-nginx-label kube.get: type: pods labels: app: nginx assert: len: 2 # Or the long-form kube:get - name: verify-pods-with-app-nginx-label kube: get: type: pods labels: app: nginx assert: len: 2 # Like "kube.get", you can pass a label selector for "kube.delete" - kube.delete: type: pods labels: app: nginx # And you can use the long-form kube:delete as well - kube: delete: type: pods labels: app: nginxTesting that a Pod with the name nginx exists by the specified timeout (essentially, gdt-kube will retry the get call and assertion until the end of the timeout):
name: test-nginx-pod-exists-within-1-minute tests: - kube.get: pods/nginx timeout: 1mTesting creation and subsequent fetch then delete of a Pod, specifying the Pod definition contained in a YAML file:
name: create-get-delete-pod description: create, get and delete a Pod fixtures: - kind tests: - name: create-pod kube: create: manifests/nginx-pod.yaml - name: pod-exists kube: get: pods/nginx - name: delete-pod kube: delete: pods/nginxTesting creation and subsequent fetch then delete of a Pod, specifying the Pod definition using an inline YAML blob:
name: create-get-delete-pod description: create, get and delete a Pod fixtures: - kind tests: # "kube.create" is a shortcut for the longer object->field format - kube.create: | apiVersion: v1 kind: Pod metadata: name: nginx spec: containers: - name: nginx image: nginx imagePullPolicy: IfNotPresent # "kube.get" is a shortcut for the longer object->field format - kube.get: pods/nginx # "kube.delete" is a shortcut for the longer object->field format - kube.delete: pods/nginxA gdt test scenario is comprised of a list of test specs. These test specs are executed in sequential order. If you want to have one test spec be able to use some output or value calculated or asserted in a previous step, you can use the gdt variable system.
Here's a test scenario that shows how to define variables in a test spec and how to use those variables in later test specs.
file: testdata/var-save-restore.yaml:
name: var-save-restore description: scenario showing different variations of variable definition and references defaults: kube: namespace: var-save-restore tests: - name: create-pod kube: create: testdata/manifests/nginx-pod.yaml - name: define a variable and populate it with the pod's IP address kube: get: pods/nginx assert: conditions: ready: status: true var: POD_IP: from: $.status.podIP POD_NAME: from: $.metadata.name - name: get the pod's information and assert same values as variables kube.get: pods/$$POD_NAME assert: matches: status: podIP: $$POD_IP - name: delete-pod kube: delete: pods/$$POD_NAMEIn the first test spec, we create new Pod by specifying the filepath to a Kubernetes manifest (testdata/manifests/nginx-pod.yaml):
- name: create-pod kube: create: testdata/manifests/nginx-pod.yamlIn the second test spec, we get the Pod that we created in the first step, asserting that the Pod is in a Ready state:
- name: define a variable and populate it with the pod's IP address kube: get: pods/nginx assert: conditions: ready: status: trueThe assert.conditions.ready.status=true means that the above test spec will not succeed until the Pod is in a Ready state.
NOTE: Pods that are in a Ready state have their
status.podIPfield set to a non-empty value.
In the same second test spec, we add a var: section to define two variables that we can refer to in subsequent test specs:
var: POD_IP: from: $.status.podIP POD_NAME: from: $.metadata.nameThe above creates two variables. The first variable is named POD_IP and will get its value from the field in the returned Pod resource representation at the JSONPath $.status.podIP. The second variable is named POD_NAME and gets its value from the field in the Pod resource representation at the JSONPath $.metadata.name.
NOTE: People familiar with using
kubectl get ... -o jsonpath=EXPRESSIONshould find this syntax easy to understand. Just remember that in gdt (and proper JSONPath expressions), you must begin the JSONPath expression with the$.characters instead of thekubectlbehaviour of beginning the JSONPath expression with the{.characters.If you do something like this to get a Pod's IP using
kubectl:
kubectl get pod/my-pod -o jsonpath='{.status.podIP}'You would do the following in a
gdt-kubetest spec:
from: $.status.podIP
Having defined the POD_IP and POD_NAME variables, you can then refer to the values contained in those variables in subsequent gdt test specs by using the double-dollar-sign notation.
NOTE: We use the double-dollar-sign notation because by default,
gdtreplaces all single-dollar-sign notations with environment variables BEFORE executing the test specs in a test scenario. Using the double-dollar-sign notation means that environment variable substitution does not impact the referencing ofgdtvariables referenced in a test spec.
In the third test spec, you see an example of referring to the POD_NAME variable in the kube.get field:
- name: get the pod's information and assert same values as variables kube.get: pods/$$POD_NAMEas well as an example of referring to the POD_IP variable in the assert field of that same test spec:
assert: matches: status: podIP: $$POD_IPFinally, the fourth test spec demonstrates that you can continue to refer to a variable defined in any previous test spec. In the fourth test spec, we refer to the POD_NAME variable from the kube.delete field:
- name: delete-pod kube: delete: pods/$$POD_NAMEYou can mix other gdt test types in a single gdt test scenario. For example, here we are testing the creation of a Pod, waiting a little while with the wait.after directive, then using the gdt exec test type to test SSH connectivity to the Pod.
name: create-check-ssh description: create a Deployment then check SSH connectivity fixtures: - kind tests: - kube.create: manifests/deployment.yaml wait: after: 30s - exec: ssh -T someuser@ipNote that you can make use of the gdt variable system to pass values between gdt test specs, as this example demonstrates:
file: testdata/curl-pod-ip.yaml
name: curl-pod-ip description: scenario showing how to create two NGinx Pods and test connectivity to internal Pod IP addresses fixtures: - kind defaults: kube: namespace: curl-pod-ip tests: - name: create-server kube: create: testdata/manifests/nginx-server.yaml - name: get-server-pod-ip kube: get: pods/server assert: conditions: ready: status: true var: SERVER_IP: from: $.status.podIP - name: create-connect-tester kube: create: testdata/manifests/nginx-connect-test.yaml - name: wait-connect-test-ready kube: get: pods/connect-test assert: conditions: ready: status: true - name: curl-server-from-connect-tester exec: kubectl exec -n curl-pod-ip pods/connect-test -- curl -s -I -v $$SERVER_IP timeout: 2s assert: out: contains: "200 OK" # curl -v causes output to be sent to stderr that looks like this: # * Connected to 10.244.0.17 (10.244.0.17) port 80 (#0) # > GET / HTTP/1.1 # > Host: 10.244.0.17 # > User-Agent: curl/7.88.1 # > Accept: */* # > # < HTTP/1.1 200 OK err: contains: "Host: $$SERVER_IP" - name: delete-connect-tester kube: delete: pods/connect-test - name: delete-server kube: delete: pods/serverIn the above example, we use a few gdt-kube test specs (the ones with kube: field in the test spec definition) along with an exec test spec that executes curl from a test Pod that a previous test spec creates and the command that the test spec executes references the SERVER_IP variable defined in the second step:
- name: curl-server-from-connect-tester exec: kubectl exec -n curl-pod-ip pods/connect-test -- curl -s -I -v $$SERVER_IPYou can see that the assert: field in the exec test spec references the SERVER_IP variable:
err: contains: "Host: $$SERVER_IP"The assert.matches field of a gdt-kube test Spec allows a test author to specify expected fields and those field contents in a resource that was returned by the Kubernetes API server from the result of a kube.get call.
Suppose you have a Deployment resource and you want to write a test that checks that a Deployment resource's Status.ReadyReplicas field is 2.
You do not need to specify all other Deployment.Status fields like Status.Replicas in order to match the Status.ReadyReplicas field value. You only need to include the Status.ReadyReplicas field in the Matches value as these examples demonstrate:
tests: - name: check deployment's ready replicas is 2 kube: get: deployments/my-deployment assert: matches: | kind: Deployment metadata: name: my-deployment status: readyReplicas: 2you don't even need to include the kind and metadata in assert.matches. If missing, no kind and name matching will be performed.
tests: - name: check deployment's ready replicas is 2 kube: get: deployments/my-deployment assert: matches: | status: readyReplicas: 2In fact, you don't need to use an inline multiline YAML string. You can use a map[string]interface{} as well:
tests: - name: check deployment's ready replicas is 2 kube: get: deployments/my-deployment assert: matches: status: readyReplicas: 2assertion.conditions contains the assertions to make about a resource's Status.Conditions collection. It is a map, keyed by the ConditionType (matched case-insensitively), of assertions to make about that Condition. The assertions can be:
- a string which is the ConditionStatus that should be found for that Condition
- a list of strings containing ConditionStatuses, any of which should be found for that Condition
- an object of type
ConditionExpectthat contains more fine-grained assertions about that Condition's Status and Reason
A simple example that asserts that a Pod's Ready Condition has a status of True. Note that both the condition type ("Ready") and the status ("True") are matched case-insensitively, which means you can just use lowercase strings:
tests: - kube: get: pods/nginx assert: conditions: ready: trueIf we wanted to assert that the ContainersReady Condition had a status of either False or Unknown, we could write the test like this:
tests: - kube: get: pods/nginx assert: conditions: containersReady: - false - unknownFinally, if we wanted to assert that a Deployment's Progressing Condition had a Reason field with a value "NewReplicaSetAvailable" (matched case-sensitively), we could do the following:
tests: - kube: get: deployments/nginx assert: conditions: progressing: status: true reason: NewReplicaSetAvailableThe assert.placement field of a gdt-kube test Spec allows a test author to specify the expected scheduling outcome for a set of Pods returned by the Kubernetes API server from the result of a kube.get call.
Suppose you have a Deployment resource with a TopologySpreadConstraints that specifies the Pods in the Deployment must land on different hosts:
apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment labels: app: nginx spec: replicas: 3 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:latest ports: - containerPort: 80 topologySpreadConstraints: - maxSkew: 1 topologyKey: kubernetes.io/hostname whenUnsatisfiable: DoNotSchedule labelSelector: matchLabels: app: nginxYou can create a gdt-kube test case that verifies that your nginx Deployment's Pods are evenly spread across all available hosts:
tests: - kube: get: deployments/nginx assert: placement: spread: kubernetes.io/hostnameIf there are more hosts than the spec.replicas in the Deployment, gdt-kube will ensure that each Pod landed on a unique host. If there are fewer hosts than the spec.replicas in the Deployment, gdt-kube will ensure that there is an even spread of Pods to hosts, with any host having no more than one more Pod than any other.
Suppose you have configured your Kubernetes scheduler to bin-pack Pods onto hosts by scheduling Pods to hosts with the most allocated CPU resources:
apiVersion: kubescheduler.config.k8s.io/v1 kind: KubeSchedulerConfiguration profiles: - pluginConfig: - args: scoringStrategy: resources: - name: cpu weight: 100 type: MostAllocated name: NodeResourcesFitYou can create a gdt-kube test case that verifies that your nginx Deployment's Pods are packed onto the fewest unique hosts:
tests: - kube: get: deployments/nginx assert: placement: pack: kubernetes.io/hostnamegdt-kube will examine the total number of hosts that meet the nginx Deployment's scheduling and resource constraints and then assert that the number of hosts the Deployment's Pods landed on is the minimum number that would fit the total requested resources.
The assert.json field of a gdt-kube test Spec allows a test author to specify expected fields, the value of those fields as well as the format of field values in a resource that was returned by the Kubernetes API server from the result of a kube.get call.
Suppose you have a Deployment resource and you want to write a test that checks that a Deployment resource's Status.ReadyReplicas field is 2.
You can specify this expectation using the assert.json.paths field, which is a map[string]interface{} that takes map keys that are JSONPath expressions and map values of what the field at that JSONPath expression should contain:
tests: - name: check deployment's ready replicas is 2 kube: get: deployments/my-deployment assert: json: paths: $.status.readyReplicas: 2 JSONPath expressions can be fairly complex, allowing the test author to, for example, assert the value of a nested map field with a particular key, as this example shows:
tests: - name: check deployment's pod template "app" label is "nginx" kube: get: deployments/my-deployment assert: json: paths: $.spec.template.labels["app"]: nginxYou can check that the value of a particular field at a JSONPath is formatted in a particular fashion using assert.json.path-formats. This is a map, keyed by JSONPath expression, of the data format the value of the field at that JSONPath expression should have. Valid data formats are:
datedate-timeemailhostnameidn-emailipv4ipv6iriiri-referencejson-pointerregexrelative-json-pointertimeuriuri-referenceuri-templateuuiduuid4
Read more about JSONSchema formats.
For example, suppose we wanted to verify that a Deployment's metadata.uid field was a UUID-4 and that its metadata.creationTimestamp field was a date-time timestamp:
tests: - kube: get: deployments/nginx assert: json: path-formats: $.metadata.uid: uuid4 $.metadata.creationTimestamp: date-timeHere is an example of creating a Deployment with an initial spec.replicas count of 2, then applying a change to spec.replicas of 1, then asserting that the status.readyReplicas gets updated to 1.
file testdata/manifests/nginx-deployment.yaml:
apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: selector: matchLabels: app: nginx replicas: 2 template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx ports: - containerPort: 80file testdata/apply-deployment.yaml:
name: apply-deployment description: create, get, apply a change, get, delete a Deployment fixtures: - kind tests: - name: create-deployment kube: create: testdata/manifests/nginx-deployment.yaml - name: deployment-has-2-replicas timeout: after: 20s kube: get: deployments/nginx assert: matches: status: readyReplicas: 2 - name: apply-deployment-change kube: apply: | apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: replicas: 1 - name: deployment-has-1-replica timeout: after: 20s kube: get: deployments/nginx assert: matches: status: readyReplicas: 1 - name: delete-deployment kube: delete: deployments/nginxWhen evaluating how to construct a Kubernetes client gdt-kube uses the following precedence to determine the kubeconfig and kube context:
- The individual test spec's
configorcontextvalue - Any
gdtFixture that exposes agdt.kube.configorgdt.kube.contextstate key (e.g. [KindFixture][kind-fixture]). - The test file's
defaults.kubeconfigorcontextvalue.
For the kubeconfig file path, if none of the above yielded a value, the following precedence is used to determine the kubeconfig:
- A non-empty
KUBECONFIGenvironment variable pointing at a file. - In-cluster config if running in cluster.
$HOME/.kube/configif it exists.
gdt Fixtures are objects that help set up and tear down a testing environment. The gdt-kube library has some utility fixtures to make testing with Kubernetes easier.
The KindFixture eases integration of gdt-kube tests with the KinD local Kubernetes development system.
To use it, import the gdt-kube/fixtures/kind package:
import ( "github.com/gdt-dev/gdt" gdtkube "github.com/gdt-dev/kube" gdtkind "github.com/gdt-dev/kube/fixtures/kind" )and then register the fixture with your gdt Context, like so:
func TestExample(t *testing.T) { s, err := gdt.From("path/to/test.yaml") if err != nil { t.Fatalf("failed to load tests: %s", err) } ctx := context.Background() ctx = gdt.RegisterFixture(ctx, "kind", gdtkind.New()) err = s.Run(ctx, t) if err != nil { t.Fatalf("failed to run tests: %s", err) } }In your test file, you would list the "kind" fixture in the fixtures list:
name: example-using-kind fixtures: - kind tests: - kube.get: pods/nginxThe default behaviour of the KindFixture is to delete the KinD cluster when the Fixture's Stop() method is called, but only if the KinD cluster did not previously exist before the Fixture's Start() method was called.
If you want to always ensure that a KinD cluster is deleted when the KindFixture is stopped, use the fixtures.kind.WithDeleteOnStop() function:
import ( "github.com/gdt-dev/gdt" gdtkube "github.com/gdt-dev/kube" gdtkind "github.com/gdt-dev/kube/fixtures/kind" ) func TestExample(t *testing.T) { s, err := gdt.From("path/to/test.yaml") if err != nil { t.Fatalf("failed to load tests: %s", err) } ctx := context.Background() ctx = gdt.RegisterFixture( ctx, "kind", gdtkind.New(), gdtkind.WithDeleteOnStop(), ) err = s.Run(ctx, t) if err != nil { t.Fatalf("failed to run tests: %s", err) } }Likewise, the default behaviour of the KindFixture is to retain the KinD cluster when the Fixture's Stop() method is called but only if the KinD cluster previously existed before the Fixture's Start() method was called.
If you want to always ensure a KinD cluster is retained, even if the KindFixture created the KinD cluster, use the fixtures.kind.WithRetainOnStop() function:
import ( "github.com/gdt-dev/gdt" gdtkube "github.com/gdt-dev/kube" gdtkind "github.com/gdt-dev/kube/fixtures/kind" ) func TestExample(t *testing.T) { s, err := gdt.From("path/to/test.yaml") if err != nil { t.Fatalf("failed to load tests: %s", err) } ctx := context.Background() ctx = gdt.RegisterFixture( ctx, "kind", gdtkind.New(), gdtkind.WithRetainOnStop(), ) err = s.Run(ctx, t) if err != nil { t.Fatalf("failed to run tests: %s", err) } }You may want to pass a custom KinD configuration resource by using the fixtures.kind.WithConfigPath() modifier:
import ( "github.com/gdt-dev/gdt" gdtkube "github.com/gdt-dev/kube" gdtkind "github.com/gdt-dev/kube/fixtures/kind" ) func TestExample(t *testing.T) { s, err := gdt.From("path/to/test.yaml") if err != nil { t.Fatalf("failed to load tests: %s", err) } configPath := filepath.Join("testdata", "my-kind-config.yaml") ctx := context.Background() ctx = gdt.RegisterFixture( ctx, "kind", gdtkind.New(), gdtkind.WithConfigPath(configPath), ) err = s.Run(ctx, t) if err != nil { t.Fatalf("failed to run tests: %s", err) } }gdt was inspired by Gabbi, the excellent Python declarative testing framework. gdt tries to bring the same clear, concise test definitions to the world of Go functional testing.
The Go gopher logo, from which gdt's logo was derived, was created by Renee French.
Contributions to gdt-kube are welcomed! Feel free to open a Github issue or submit a pull request.
