import testing package

This commit is contained in:
Matthew Rich 2024-05-08 13:28:36 -07:00
parent e5e216708a
commit 81d96f305f
26 changed files with 816 additions and 2 deletions

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 rosskeenhouse
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,3 +1,39 @@
# testing
# Test Fixtures in Go
Test Fixtures
The purpose of this package is to provide a simple tool for creating test fixtures for go tests.
## What is a fixture?
A test fixture is generally used as a mechanism to provide consistent, repeatable state when testing software.
## Adding Fixtures
A fixture can be as simple as a function that returns a static value or it can leverage parameters to run the same test with different values. You can also provide a matching result set to make it easier to verify the expected results.
```
// A fixture returns some data, often based on some input parameter
func FixtureExample(p fixture.Param) interface{} {
return fmt.Sprintf("data %s", p)
}
func TestExample(t *testing.T) {
// you may define a result set for verification
r := fixture.R([]fixture.Result{"I do not like your data here","I do not like your data there","I do not like your data anywhere"})
// creating a parameterized fixture will cause the test to be executed for parameter
p := fixture.P([]fixture.Param{"here","there","anywhere"})
f := fixture.New(t, FixtureExample, p, r)
// The test body will be run 3 times (once for each parameter)
f.RunWith(
func (t *testing.T) {
fv := f.Fixture()
test_result := fmt.Sprintf("I do not like your %s", fv)
// Verify the test a result that matches the result set
f.AssertStrEq(test_result)
})
}
```

139
fixture/fixture.go Normal file
View File

@ -0,0 +1,139 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package fixture
import (
"testing"
"strconv"
"reflect"
)
type Test func(t *testing.T)
type F func(p Param) interface{}
type Ft struct {
*testing.T
params ParamReader
fixture F
data []interface{}
results ResultReader
}
func New(t *testing.T, f F, p ParamReader, r ResultReader) *Ft {
return &Ft{ T: t, params: p, fixture: f, results: r }
}
func (f *Ft) Fixture() []interface{} {
return f.data
}
func (f *Ft) Value() interface{} {
return f.data[len(f.data) - 1]
}
func (f *Ft) Result() interface{} {
return f.results.Values()[len(f.data) - 1]
}
func (f *Ft) RunWith(t Test) {
f.data = nil
for i, r := range(f.params.Values()) {
f.data = append(f.data, f.fixture(r))
f.Run(strconv.Itoa(i), t)
}
}
func (f *Ft) assertOp(value interface{}, o func (a interface{}, b interface{}) bool) {
r := f.Result()
if ! o(r, value) {
f.Errorf("Failure: value does not match expected result: [%s] != [%s]", r, value)
}
}
func (f *Ft) AssertStrEq(value string) {
f.assertOp(value, func (a interface{}, b interface{}) bool { return a.(string) == b.(string) })
}
func (f *Ft) AssertEq(value interface{}) {
cmpOp := func (a, b interface{}) bool {
rType := reflect.TypeOf(a)
switch rType.Name() {
case "float64", "float32":
return reflect.ValueOf(a).Convert(rType).Float() == reflect.ValueOf(b).Convert(rType).Float()
default:
return reflect.ValueOf(a).Convert(rType).Int() == reflect.ValueOf(b).Convert(rType).Int()
}
}
f.assertOp(value, cmpOp)
}
func (f *Ft) AssertGt(value interface{}) {
cmpOp := func (a, b interface{}) bool {
rType := reflect.TypeOf(a)
switch rType.Name() {
case "float64", "float32":
return reflect.ValueOf(a).Convert(rType).Float() > reflect.ValueOf(b).Convert(rType).Float()
default:
return reflect.ValueOf(a).Convert(rType).Int() > reflect.ValueOf(b).Convert(rType).Int()
}
}
f.assertOp(value, cmpOp)
}
func (f *Ft) AssertGe(value interface{}) {
cmpOp := func (a, b interface{}) bool {
rType := reflect.TypeOf(a)
switch rType.Name() {
case "float64", "float32":
return reflect.ValueOf(a).Convert(rType).Float() >= reflect.ValueOf(b).Convert(rType).Float()
default:
return reflect.ValueOf(a).Convert(rType).Int() >= reflect.ValueOf(b).Convert(rType).Int()
}
}
f.assertOp(value, cmpOp)
}
func (f *Ft) AssertLt(value interface{}) {
cmpOp := func (a, b interface{}) bool {
rType := reflect.TypeOf(a)
switch rType.Name() {
case "float64", "float32":
return reflect.ValueOf(a).Convert(rType).Float() < reflect.ValueOf(b).Convert(rType).Float()
default:
return reflect.ValueOf(a).Convert(rType).Int() < reflect.ValueOf(b).Convert(rType).Int()
}
}
f.assertOp(value, cmpOp)
}
func (f *Ft) AssertLe(value interface{}) {
cmpOp := func (a, b interface{}) bool {
rType := reflect.TypeOf(a)
switch rType.Name() {
case "float64", "float32":
return reflect.ValueOf(a).Convert(rType).Float() <= reflect.ValueOf(b).Convert(rType).Float()
default:
return reflect.ValueOf(a).Convert(rType).Int() <= reflect.ValueOf(b).Convert(rType).Int()
}
}
f.assertOp(value, cmpOp)
}
func (f *Ft) Assert() {
r := f.results.Values()
i := len(f.data) - 1
data := f.data[i]
res := r[i]
switch r[i].(type) {
case string:
data = string(f.data[i].([]byte))
res = string(r[i].(string))
}
if data != res {
f.Errorf("Failed value does not match expected result: [%s] != [%s]", f.data[i], r[i])
}
}

51
fixture/fixture_test.go Normal file
View File

@ -0,0 +1,51 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package fixture_test
import (
"testing"
"math"
"gitea.cv.mazarbul.net/rosskeen.house/testing/fixture"
)
func Fixture2Pow(p fixture.Param) interface{} {
return math.Exp2(p.(float64))
}
func TestNewFixture(t *testing.T) {
f := fixture.New(t, Fixture2Pow, fixture.P([]fixture.Param{1.0,2.0,3.0}), fixture.R([]fixture.Result{2.0,4.0,8.0}))
f.RunWith(
func (t *testing.T) {
f.Fixture()
f.Assert()
})
}
func TargetMul2(input float64) float64 {
return input * 2
}
func TestAssertGe(t *testing.T) {
f := fixture.New(t, Fixture2Pow, fixture.P([]fixture.Param{1.0,2.0,3.0}), fixture.R([]fixture.Result{4.0,8.0,16.0}))
f.RunWith(
func (t *testing.T) {
pow := f.Value()
result := TargetMul2(pow.(float64))
f.AssertGe(float32(result))
})
}
func TestAssertEq(t *testing.T) {
f := fixture.New(t, Fixture2Pow, fixture.P([]fixture.Param{1.0,2.0,3.0}), fixture.R([]fixture.Result{4.0,8.0,16.0}))
f.RunWith(
func (t *testing.T) {
pow := f.Value()
result := TargetMul2(pow.(float64))
f.AssertEq(float32(result))
})
}

32
fixture/params.go Normal file
View File

@ -0,0 +1,32 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package fixture
import (
)
// Fixture params are a list of values used to initialize a fixture
type Param interface{}
type ParamReader interface {
Values() []Param
Read() <-chan Param
}
type Parameters struct {
values []Param
}
// initialize parameters from a slice
func P(v []Param) *Parameters { return &Parameters{ values: v } }
func (p *Parameters) Values() []Param {
return p.values
}
func (p *Parameters) Read() <-chan Param {
rc := make(chan Param, len(p.values))
for i := range(p.Values()) {
rc <- i
}
return rc
}

30
fixture/results.go Normal file
View File

@ -0,0 +1,30 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package fixture
import (
)
type Result interface{}
type ResultReader interface {
Values() []Result
Read() <-chan Result
}
type Results struct {
values []Result
}
func R(v []Result) *Results { return &Results{ values: v } }
func (r *Results) Values() []Result {
return r.values
}
func (r *Results) Read() <-chan Result {
rc := make(chan Result, len(r.values))
for i := range(r.Values()) {
rc <- i
}
return rc
}

17
fixtures/README.md Normal file
View File

@ -0,0 +1,17 @@
Ex.
func TestSample(t *testing.T) {
f := fixtures.New(t, fixtures.TestDataInit{})
f.RunWith(Test(func (t *testing.T) {
d := f.GetData('')
f.Successful
r,e := MySample(d)
})
}

18
fixtures/fixture_test.go Normal file
View File

@ -0,0 +1,18 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package fixtures_test
import (
"testing"
)
func Fixture16bit(t *testing.T) interface{} {
return 65536
}
func TestFixture(t *testing.T) {
var f Ft = Fixture16bit
var t Test {
}
t.RunWith(f,
}

39
fixtures/fixtures.go Normal file
View File

@ -0,0 +1,39 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package fixtures
import (
"testing"
)
// fixture set type
type Ft func(t *testing.T, input interface{}) []interface{}
// Defines a fixture which can return a slice of values
type F struct {
name string
func []Getter
}
func (f *F) AddGetter(ft Getter) {
append(f.func, ft)
}
type Ft func(t *testing.T) interface{}
func (f *Ft) Get(t *testing.T) {
return f(t)
}
// Getter interface
type Getter interface {
Get() interface{}
}
type F struct {
*testing.T
}
func (f *F) RunWith(t *testing.T, data interface{}) {
}

41
fixtures/testdata.go Normal file
View File

@ -0,0 +1,41 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package fixtures
import (
"testing"
"io/ioutil"
"path/filepath"
"log"
"fmt"
"strings"
"strconv"
)
func (f *F) FixtureTestDataBasePath() string {
g := fmt.Sprintf("fixture_%s_", f.Name())
p := filepath.Join("./testdata", g)
d := filepath.Join(g,p)
return d
}
func (f *F) FixtureTestDataPaths() []string {
p := f.FixtureTestDataBasePath()
df, e := filepath.Glob(p + "*")
if e != nil {
log.Fatal(e)
}
return df
}
func (f *F) FixtureTestData() {
bp := f.FixtureTestDataBasePath()
df := f.FixtureTestDataPaths()
for i, d := range(df) {
subtestname := strings.TrimPrefix(d, bp)
if subtestname == d {
subtestname = strconv.Itoa(i)
}
data,_ := ioutil.ReadFile(d)
f.Run(f.Name() + "." + subtestname, func (t *testing.T) { f.RunWith(f.T, data) })
}
}

10
fixtures/testdata_test.go Normal file
View File

@ -0,0 +1,10 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package fixtures_test
import (
"testing"
)
func TestFixtureTestData(t *testing.T) {
}

24
fixtures/tester.go Normal file
View File

@ -0,0 +1,24 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package fixtures
import (
"testing"
)
type Test func(t *testing.T)
type Tester interface {
Test(t *testing.T)
RunWith(t *testing.T, f []Ft)
}
func (tf Test) Test(t *testing.T) {
tf(t)
}
func (tf Test) RunWith(t *testing.T, f []Ft) {
for i, fixture := range(f) {
t.Run(t.Name() + strconv.Itoa(i), tf)
}
}

5
go.mod Normal file
View File

@ -0,0 +1,5 @@
module testing
go 1.15
require gopkg.in/yaml.v2 v2.3.0

3
go.sum Normal file
View File

@ -0,0 +1,3 @@
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

25
md/mockdata.go Normal file
View File

@ -0,0 +1,25 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package mockdata
import (
// "testing"
// "io/ioutil"
// "path/filepath"
// "log"
// "fmt"
// "strings"
// "strconv"
"gitea.cv.mazarbul.net/rosskeen.house/testing/fixture"
)
// Raw file fixture data
func FixtureMockFile(p fixture.Param) interface{} {
mp := p.(MockParam)
return Read(mp.param.(string), mp.mock())
}
func ResultMockFile(p fixture.Result) interface{} {
mp := p.(MockParam)
return Read(mp.param.(string), mp.mock())
}

33
md/mockdata_test.go Normal file
View File

@ -0,0 +1,33 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package mockdata_test
import (
"testing"
"gitea.cv.mazarbul.net/rosskeen.house/testing/fixture"
"gitea.cv.mazarbul.net/rosskeen.house/testing/md"
)
type MockDataTest struct {
foo int
bar int
}
func NewMock() interface{} {
return &MockDataTest{}
}
func TestMDFixture(t *testing.T) {
//r := fixture.R([]fixture.Result{"empty"})
//"./testdat/fixture_TestTDFixture_empty.yml"})
f := fixture.New(t, mockdata.FixtureMockFile, mockdata.P(t.Name(), NewMock), nil)
f.RunWith(
func (t *testing.T) {
o := f.Value()
var d *MockDataTest = o.(*MockDataTest)
if d.foo != 3 {
t.Errorf("Failed to load mock object")
}
})
}

78
md/params.go Normal file
View File

@ -0,0 +1,78 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package mockdata
import (
"io/ioutil"
"path/filepath"
"gopkg.in/yaml.v2"
"log"
"fmt"
"gitea.cv.mazarbul.net/rosskeen.house/testing/fixture"
)
// Return a structure to load the mock data into
type MockObjectFn func() interface{}
type MockParam struct {
param fixture.Param
mock MockObjectFn
}
type Parameters struct {
*fixture.Parameters
name string
basepath string
mockdata MockObjectFn
}
func P(name string, mockdata MockObjectFn) *Parameters { return &Parameters{ name: name, mockdata: mockdata } }
// mockdata.BasePath('./testdata/mock_{testname}_')
func (p *Parameters) BasePath() string {
if p.basepath == "" {
g := fmt.Sprintf("mock_%s_", p.name)
p.basepath = filepath.Join("./testdata", g)
}
return p.basepath
}
// mockdata.Paths(base string)
func (p *Parameters) Paths() []string {
df, e := filepath.Glob(p.BasePath() + "*")
if e != nil {
log.Fatal(e)
}
return df
}
func (p *Parameters) Values() []fixture.Param {
// return TestData files
paths := p.Paths()
r := make([]fixture.Param, len(paths))
for i := range(paths) {
r[i] = MockParam { param: paths[i], mock: p.mockdata }
}
return r
}
func Read(path string, mockdata interface{}) interface{} {
d,e := ioutil.ReadFile(path)
if e != nil {
log.Fatalf("Error reading fixture data from %s: %s", path, e)
}
err := yaml.Unmarshal([]byte(d), mockdata)
if err != nil {
log.Printf("%s", err)
}
return mockdata
}
func ReadRaw(path string) interface{} {
d,e := ioutil.ReadFile(path)
if e != nil {
log.Fatalf("Error reading fixture data from %s: %s", path, e)
}
return d
}

43
md/results.go Normal file
View File

@ -0,0 +1,43 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package mockdata
import (
"path/filepath"
"log"
"fmt"
"gitea.cv.mazarbul.net/rosskeen.house/testing/fixture"
)
type Results struct {
*fixture.Results
name string
basepath string
}
func R(name string) *Results { return &Results{ name: name } }
func (r *Results) BasePath() string {
if r.basepath == "" {
g := fmt.Sprintf("result_%s_", r.name)
r.basepath = filepath.Join("./testdata", g)
}
return r.basepath
}
func (r *Results) Paths() []string {
df, e := filepath.Glob(r.BasePath() + "*")
if e != nil {
log.Fatal(e)
}
return df
}
func (r *Results) Values() []fixture.Result {
// return MockData files
paths := r.Paths()
res := make([]fixture.Result, len(paths))
for i := range(paths) {
res[i] = ReadRaw(paths[i])
}
return res
}

2
md/testdata/mock_MDFixture_simple.yml vendored Normal file
View File

@ -0,0 +1,2 @@
foo: 3
bar: 5

57
td/params.go Normal file
View File

@ -0,0 +1,57 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package testdata
import (
"io/ioutil"
"path/filepath"
"log"
"fmt"
"gitea.cv.mazarbul.net/rosskeen.house/testing/fixture"
)
type Parameters struct {
*fixture.Parameters
name string
basepath string
}
func P(name string) *Parameters { return &Parameters{ name: name } }
// testdata.BasePath('./testdata/fixture_{testname}_')
func (p *Parameters) BasePath() string {
if p.basepath == "" {
g := fmt.Sprintf("fixture_%s_", p.name)
p.basepath = filepath.Join("./testdata", g)
}
return p.basepath
}
// testdata.Paths(base string)
func (p *Parameters) Paths() []string {
df, e := filepath.Glob(p.BasePath() + "*")
if e != nil {
log.Fatal(e)
}
return df
}
func (p *Parameters) Values() []fixture.Param {
// return TestData files
paths := p.Paths()
r := make([]fixture.Param, len(paths))
for i := range(paths) {
r[i] = paths[i]
}
return r
}
func Read(path string) []byte {
d,e := ioutil.ReadFile(path)
if e != nil {
log.Fatalf("Error reading fixture data from %s: %s", path, e)
}
return d
}

48
td/results.go Normal file
View File

@ -0,0 +1,48 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package testdata
import (
"path/filepath"
"log"
"fmt"
"gitea.cv.mazarbul.net/rosskeen.house/testing/fixture"
)
type TypeConversion func(value interface{}) interface{}
type Results struct {
*fixture.Results
name string
basepath string
convert TypeConversion
}
func NoConversion(value interface{}) interface{} { return value }
func R(name string) *Results { return &Results{ name: name, convert: NoConversion } }
func Rt(name string, convert TypeConversion) *Results { return &Results{ name: name, convert: convert } }
func (r *Results) BasePath() string {
if r.basepath == "" {
g := fmt.Sprintf("result_%s_", r.name)
r.basepath = filepath.Join("./testdata", g)
}
return r.basepath
}
func (r *Results) Paths() []string {
df, e := filepath.Glob(r.BasePath() + "*")
if e != nil {
log.Fatal(e)
}
return df
}
func (r *Results) Values() []fixture.Result {
// return TestData files
paths := r.Paths()
res := make([]fixture.Result, len(paths))
for i := range(paths) {
res[i] = r.convert(string(Read(paths[i])))
}
return res
}

23
td/testdata.go Normal file
View File

@ -0,0 +1,23 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package testdata
import (
// "testing"
// "io/ioutil"
// "path/filepath"
// "log"
// "fmt"
// "strings"
// "strconv"
"gitea.cv.mazarbul.net/rosskeen.house/testing/fixture"
)
// Raw file fixture data
func FixtureRawFile(p fixture.Param) interface{} {
return Read(p.(string))
}
func ResultRawFile(p fixture.Result) interface{} {
return Read(p.(string))
}

View File

@ -0,0 +1 @@
199

View File

@ -0,0 +1 @@
empty

View File

@ -0,0 +1 @@
199

36
td/testdata_test.go Normal file
View File

@ -0,0 +1,36 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package testdata_test
import (
"testing"
"strconv"
"strings"
"gitea.cv.mazarbul.net/rosskeen.house/testing/fixture"
"gitea.cv.mazarbul.net/rosskeen.house/testing/td"
)
func TestTDFixture(t *testing.T) {
r := fixture.R([]fixture.Result{"empty"})
//"./testdat/fixture_TestTDFixture_empty.yml"})
f := fixture.New(t, testdata.FixtureRawFile, testdata.P(t.Name()), r)
f.RunWith(
func (t *testing.T) {
f.Fixture()
f.Assert()
})
}
func TestTDFixtureTypeConversion(t *testing.T) {
c := func(v interface{}) interface{} { i,_ := strconv.Atoi(strings.TrimSuffix(v.(string), "\n")); return i }
r := testdata.Rt(t.Name(), c)
f := fixture.New(t, testdata.FixtureRawFile, testdata.P(t.Name()), r)
f.RunWith(
func (t *testing.T) {
f.Fixture()
f.AssertEq(199)
})
}