From c4a197edfe56db3143c578bd18d40b1abc52a314 Mon Sep 17 00:00:00 2001 From: Vladimir Vivien Date: Fri, 17 Sep 2021 09:46:17 -0400 Subject: [PATCH] Support for table-driven tests This patch introduces support for table-driven tests. It provives a simplified way to express feature tests as collection test functions that can be used to test different functionalities for the same feature. Signed-off-by: Vladimir Vivien --- .gitignore | 1 + examples/table/README.md | 2 + examples/table/table_test.go | 95 ++++++++++++++++++++++++++++++++++++ pkg/env/env.go | 24 ++++++--- pkg/features/table.go | 49 +++++++++++++++++++ 5 files changed, 164 insertions(+), 7 deletions(-) create mode 100644 examples/table/README.md create mode 100644 examples/table/table_test.go create mode 100644 pkg/features/table.go diff --git a/.gitignore b/.gitignore index 9bb32890..add2d5a5 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ /coverage.html /coverage.out .DS_Store +.vscode diff --git a/examples/table/README.md b/examples/table/README.md new file mode 100644 index 00000000..b7f2f4e0 --- /dev/null +++ b/examples/table/README.md @@ -0,0 +1,2 @@ +# Table-Driven Tests +This directory contains examples that show how the test framework can be used to define table-driven tests. diff --git a/examples/table/table_test.go b/examples/table/table_test.go new file mode 100644 index 00000000..91374717 --- /dev/null +++ b/examples/table/table_test.go @@ -0,0 +1,95 @@ +/* +Copyright 2021 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package table + +import ( + "context" + "math/rand" + "os" + "testing" + "time" + + "sigs.k8s.io/e2e-framework/pkg/env" + "sigs.k8s.io/e2e-framework/pkg/envconf" + "sigs.k8s.io/e2e-framework/pkg/features" +) + +var test = env.New() +func TestMain(m *testing.M){ + // Setup the rand number source and a limit + test.Setup(func(ctx context.Context, config *envconf.Config) (context.Context, error) { + rnd := rand.New(rand.NewSource(time.Now().UnixNano())) + return context.WithValue(context.WithValue(ctx, "limit", rand.Int31n(255)), "randsrc", rnd), nil + }) + + // Don't forget to launch the package test + os.Exit(test.Run(m)) +} + +func TestTableDriven(t *testing.T) { + // feature 1 + table0 := features.Table{ + { + Name: "less than equal 64", + Assessment: func(ctx context.Context, t *testing.T, config *envconf.Config) context.Context { + rnd := ctx.Value("randsrc").(*rand.Rand) // in real test, check asserted type + lim := ctx.Value("limit").(int32) // check type assertion + if rnd.Int31n(lim) > 64 { + t.Log("limit should be less than 64") + } + return ctx + }, + }, + { + Name: "more than than equal 128", + Assessment: func(ctx context.Context, t *testing.T, config *envconf.Config) context.Context { + rnd := ctx.Value("randsrc").(*rand.Rand) // in real test, check asserted type + lim := ctx.Value("limit").(int32) // check type assertion + if rnd.Int31n(lim) > 128 { + t.Log("limit should be less than 128") + } + return ctx + }, + }, + { + Assessment: func(ctx context.Context, t *testing.T, config *envconf.Config) context.Context { + rnd := ctx.Value("randsrc").(*rand.Rand) // in real test, check asserted type + lim := ctx.Value("limit").(int32) // check type assertion + if rnd.Int31n(lim) > 256 { + t.Log("limit should be less than 256") + } + return ctx + }, + }, + }.Build("Random numbers").Feature() + + // feature 2 + table1 := features.Table{ + { + Name: "A simple feature", + Assessment: func(ctx context.Context, t *testing.T, config *envconf.Config) context.Context { + rnd := ctx.Value("randsrc").(*rand.Rand) + if rnd.Int() > 100 { + t.Log("this is a great number") + } + return ctx + }, + }, + } + + test.Test(t, table0, table1.Build().Feature() ) +} \ No newline at end of file diff --git a/pkg/env/env.go b/pkg/env/env.go index 39339fd2..4323b4da 100644 --- a/pkg/env/env.go +++ b/pkg/env/env.go @@ -21,7 +21,9 @@ package env import ( "context" "fmt" + "math/rand" "testing" + "time" log "k8s.io/klog/v2" @@ -42,6 +44,7 @@ type testEnv struct { ctx context.Context cfg *envconf.Config actions []action + rnd rand.Source } // New creates a test environment with no config attached. @@ -89,6 +92,7 @@ func newTestEnv() *testEnv { return &testEnv{ ctx: context.Background(), cfg: envconf.New(), + rnd: rand.NewSource(time.Now().UnixNano()), } } @@ -190,7 +194,7 @@ func (e *testEnv) Test(t *testing.T, testFeatures ...types.Feature) { // execute each feature beforeFeatureActions := e.getBeforeFeatureActions() afterFeatureActions := e.getAfterFeatureActions() - for _, feature := range testFeatures { + for i, feature := range testFeatures { // execute beforeFeature actions for _, action := range beforeFeatureActions { if e.ctx, err = action.runWithFeature(e.ctx, e.cfg, deepCopyFeature(feature)); err != nil { @@ -199,7 +203,11 @@ func (e *testEnv) Test(t *testing.T, testFeatures ...types.Feature) { } // execute feature test - e.ctx = e.execFeature(e.ctx, t, feature) + featName := feature.Name() + if featName == "" { + featName = fmt.Sprintf("Feature-%d", i+1) + } + e.ctx = e.execFeature(e.ctx, t, featName, feature) // execute beforeFeature actions for _, action := range afterFeatureActions { @@ -304,9 +312,7 @@ func (e *testEnv) getFinishActions() []action { return e.getActionsByRole(roleFinish) } -func (e *testEnv) execFeature(ctx context.Context, t *testing.T, f types.Feature) context.Context { - featName := f.Name() - +func (e *testEnv) execFeature(ctx context.Context, t *testing.T, featName string, f types.Feature) context.Context { // feature-level subtest t.Run(featName, func(t *testing.T) { // skip feature which matches with --skip-feature @@ -343,8 +349,12 @@ func (e *testEnv) execFeature(ctx context.Context, t *testing.T, f types.Feature // assessments run as feature/assessment sub level assessments := features.GetStepsByLevel(f.Steps(), types.LevelAssess) - for _, assess := range assessments { - t.Run(assess.Name(), func(t *testing.T) { + for i, assess := range assessments { + assessName := assess.Name() + if assessName == "" { + assessName = fmt.Sprintf("Assessment-%d", i+1) + } + t.Run(assessName, func(t *testing.T) { // skip assessments which matches with --skip-assessments if e.cfg.SkipAssessmentRegex() != nil && e.cfg.SkipAssessmentRegex().MatchString(assess.Name()) { t.Skipf(`Skipping assessment "%s": name matched`, assess.Name()) diff --git a/pkg/features/table.go b/pkg/features/table.go new file mode 100644 index 00000000..d2afed90 --- /dev/null +++ b/pkg/features/table.go @@ -0,0 +1,49 @@ +/* +Copyright 2021 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package features + +import ( + "fmt" +) + +// Table provides a structure for table-driven tests. +// Each entry in the table represents an executable assessment. +type Table []struct { + Name string + Assessment Func +} + +// Build converts the defined test steps in the table +// into a FeatureBuilder which can be used to add additional attributes +// to the feature before it's exercised. Build takes an optional feature name +// if omitted will be generated. +func (table Table) Build(featureName ...string) *FeatureBuilder { + var name string + if len(featureName) > 0 { + name = featureName[0] + } + f := New(name) + for i, test := range table { + if test.Name == "" { + test.Name = fmt.Sprintf("Assessment-%d", i) + } + if test.Assessment != nil { + f.Assess(test.Name, test.Assessment) + } + } + return f +}