golang行为驱动开发(BDD)测试框架插件testcase的使用

Golang 行为驱动开发(BDD)测试框架插件 testcase 的使用

testcase 是一个用于应用 BDD 测试约定的 Golang 测试工具包。

特性

  • 轻量级,无依赖
  • 支持多种测试风格:经典扁平测试风格、表格测试风格和嵌套测试风格
  • 基于领域驱动设计(DDD)的测试组合
  • 安全的并行测试
  • 可重复的伪随机测试输入生成
  • 可重复的伪随机测试顺序打乱

快速开始/示例

基本示例

func Test_withTestcaseT(t *testing.T) {
    tc := testcase.NewT(t, nil)
    
    tc.Must.True(true)
    _ = tc.Random.String()
}

func Test_withTestcaseSpec(t *testing.T) {
    s := testcase.NewSpec(t)
    
    s.Test("", func(t *testcase.T) {
        t.Must.True(true)
        _ = tc.Random.String()
    })
}

带作用域测试变量的示例

package main

import (
    "testing"

    "go.llib.dev/testcase"
)

func TestMyFunc(t *testing.T) {
    s := testcase.NewSpec(t)
    s.NoSideEffect()

    var (
        input = testcase.Let[string](s, nil)
    )
    act := func(t *testcase.T) string {
        return MyFunc(input.Get(t))
    }

    s.When("input is all lowercase", func(s *testcase.Spec) {
        // 没有定义输入的测试作用域会警告开发者缺少规范
        input.LetValue(s, "all lowercase")

        s.Then("it is expected to ...", func(t *testcase.T) {
            t.Must.Equal("all lowercase", act(t))
        })
    })

    s.When("input is all upcase", func(s *testcase.Spec) {
        input.LetValue(s, "ALL UPCASE")

        s.Then("it is expected to ...", func(t *testcase.T) {
            t.Must.Equal("all upcase", act(t))
        })
    })
}

模块化

你可以将常见的场景设置模块化为辅助函数,这有助于提高测试的可读性,同时将模拟的需求降到最低。

package mypkg_test

import (
    "testing"

    "my/project/mypkg"

    "go.llib.dev/testcase"

    . "my/project/testing/pkg"
)

func TestMyTypeMyFunc(t *testing.T) {
    s := testcase.NewSpec(t)
    
    myUser := GivenWeHaveUser(s)
    // .. 其他给定条件

    myType := testcase.Let(s, func(t *testcase.T) *mypkg.MyType {
        return &mypkg.MyType{}
    })

    s.Describe(`.MyFunc`, func(s *testcase.Spec) {
        act := func(t *testcase.T) { myType.Get(t).MyFunc(myUser.Get(t)) }

        s.Then(`edge case description`, func(t *testcase.T) {
            act(t)
        })
    })
}

稳定性

  • 该包被认为是稳定的
  • 使用滚动发布约定
  • 没有计划对包的导出API进行破坏性更改
  • 该包用于生产开发
  • 只有在实际用例证明其必要性时才会扩展包API

总结

testcase 提供了一种遵循DRY原则的方式来表达常见的Arrange、Act部分和Asserts。它通过嵌套隐式地可视化你的生产代码所需的心智模型,并允许你将常见的场景设置模块化为辅助函数。


更多关于golang行为驱动开发(BDD)测试框架插件testcase的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于golang行为驱动开发(BDD)测试框架插件testcase的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


Golang 行为驱动开发(BDD)测试框架 testcase 使用指南

testcase 是一个轻量级的 Golang BDD 测试框架,它提供了行为驱动开发的测试风格,同时保持了 Go 测试工具链的兼容性。

安装 testcase

go get github.com/adamluzsi/testcase

基本使用示例

简单测试案例

package example

import (
	"testing"
	"github.com/adamluzsi/testcase"
)

func TestAddition(t *testing.T) {
	s := testcase.NewSpec(t)

	s.Describe("Addition", func(s *testcase.Spec) {
		var (
			a = s.Let("a", func(t *testcase.T) interface{} { return 0 })
			b = s.Let("b", func(t *testcase.T) interface{} { return 0 })
		)

		act := func(t *testcase.T) int {
			return a.Get(t).(int) + b.Get(t).(int)
		}

		s.When("a is 1 and b is 2", func(s *testcase.Spec) {
			a.Let(s, func(t *testcase.T) interface{} { return 1 })
			b.Let(s, func(t *testcase.T) interface{} { return 2 })

			s.Then("it should return 3", func(t *testcase.T) {
				t.Must.Equal(3, act(t))
			})
		})

		s.When("a is 0 and b is 0", func(s *testcase.Spec) {
			s.Then("it should return 0", func(t *testcase.T) {
				t.Must.Equal(0, act(t))
			})
		})
	})
}

更复杂的 BDD 示例

package user_test

import (
	"testing"
	"github.com/adamluzsi/testcase"
)

type User struct {
	Name string
	Age  int
}

func TestUserValidation(t *testing.T) {
	s := testcase.NewSpec(t)

	s.Describe("User validation", func(s *testcase.Spec) {
		var (
			user = s.Let("user", func(t *testcase.T) interface{} {
				return &User{
					Name: t.Random.String(),
					Age:  t.Random.IntBetween(18, 99),
				}
			})
			getUser = func(t *testcase.T) *User {
				return user.Get(t).(*User)
			}
		)

		validate := func(t *testcase.T) error {
			u := getUser(t)
			if u.Name == "" {
				return errors.New("name cannot be empty")
			}
			if u.Age < 18 {
				return errors.New("age must be at least 18")
			}
			return nil
		}

		s.When("user is valid", func(s *testcase.Spec) {
			s.Then("it should pass validation", func(t *testcase.T) {
				t.Must.Nil(validate(t))
			})
		})

		s.When("name is empty", func(s *testcase.Spec) {
			user.Let(s, func(t *testcase.T) interface{} {
				u := getUser(t)
				u.Name = ""
				return u
			})

			s.Then("it should fail validation", func(t *testcase.T) {
				t.Must.NotNil(validate(t))
				t.Must.Contain(validate(t).Error(), "name cannot be empty")
			})
		})

		s.When("age is under 18", func(s *testcase.Spec) {
			user.Let(s, func(t *testcase.T) interface{} {
				u := getUser(t)
				u.Age = 17
				return u
			})

			s.Then("it should fail validation", func(t *testcase.T) {
				t.Must.NotNil(validate(t))
				t.Must.Contain(validate(t).Error(), "age must be at least 18")
			})
		})
	})
}

testcase 主要特性

  1. Let 和 LetValue:用于定义测试变量,支持惰性求值

    var count = s.Let("count", func(t *testcase.T) interface{} { return 0 })
    var name = s.LetValue("name", "Alice")
    
  2. Before 和 After 钩子:测试前后的准备和清理工作

    s.Before(func(t *testcase.T) {
        // 测试前准备工作
    })
    
    s.After(func(t *testcase.T) {
        // 测试后清理工作
    })
    
  3. 随机测试数据生成

    t.Random.String() // 随机字符串
    t.Random.Int()    // 随机整数
    t.Random.ElementFromSlice([]string{"a", "b", "c"}) // 从切片中随机选择
    
  4. 并行测试支持

    s := testcase.NewSpec(t)
    s.Test("parallel test", func(t *testcase.T) {
        t.Parallel()
        // 测试代码
    })
    
  5. 表格驱动测试

    s.TestTable("addition cases", func(t *testcase.T) {
        type TestCase struct {
            A, B, Expected int
        }
        return []TestCase{
            {A: 1, B: 2, Expected: 3},
            {A: 0, B: 0, Expected: 0},
            {A: -1, B: 1, Expected: 0},
        }
    }, func(t *testcase.T, tc TestCase) {
        t.Must.Equal(tc.Expected, tc.A+tc.B)
    })
    

最佳实践

  1. 使用 Describe/When/Then 结构组织测试,使测试意图更清晰
  2. 使用 Let 定义测试变量,避免测试间的状态污染
  3. 为测试案例和断言编写清晰的描述
  4. 利用随机测试数据提高测试覆盖率
  5. 合理使用 Before/After 钩子管理测试资源

testcase 框架结合了 BDD 的清晰表达能力和 Go 测试工具链的高效性,是 Go 项目中实施行为驱动开发的优秀选择。

回到顶部