demo 账号:admin,密码:123456
hrp
最重要的就是用例组成,所以这部分我们会分几节来讲解,其中会涉及到一些比较基础的内容,如果你基础比较扎实,可以略过比较基础部分,直接看源码相关部分。
在 Go 中,我们可以通过config
来进行测试用例的设置。在response_test.go
文件的assertRunTestCases
函数中,我们可以通过NewStep
方法来创建一个步骤对象,并且使用链式调用的方式设置不同的参数。
通过链式调用,我们可以依次使用GET
、Validate
、AssertEqual
等方法来设置相应的参数。例如,使用GET
方法设置请求方法、URL 和 Headers,使用Validate
方法设置响应的校验规则,使用AssertEqual
方法设置预期结果与实际结果的比较。
除了上述方法外,我们还可以使用Extract
、TeardownHook
等方法来设置一些额外的参数,用于进一步处理响应数据或执行一些清理操作。
这种链式调用的方式使得配置测试用例变得非常简洁和直观,同时也提高了代码的可读性和可维护性。通过深入理解这些调用方法,我们能够更好地掌握 Go 语言中的测试框架,并有效地进行用例的设置和管理。如果你还不太明白链式调用的概念,可以回顾上一期的内容,相信这会加深你对这部分内容的理解。
func assertRunTestCases(t *testing.T) {
testcase1 := &TestCase{
Config: NewConfig("TestCase1").
SetBaseURL("https://httpbin.org"),
TestSteps: []IStep{
NewStep("testcase1-step1").
GET("/headers").
Validate().
AssertEqual("status_code", 200, "check status code").
AssertEqual("headers.\"Content-Type\"", "application/json", "check http response Content-Type"),
},
}
}
从上述代码可以看出,一个测试用例主要由三部分组成:TestCase
、Config
和TestSteps
。
其中,Config
部分在上一期中已经详细介绍过,并对链式调用进行了简单介绍,它包含了测试用例的配置信息,比如请求头、请求方法等。
现在,我们着重看一下TestCase
部分。TestCase
是一个结构体,并且实现了ITestCase
接口。这个接口包含了两个方法:GetPath
用于获取用例的路径,ToTestCase
用于将文件内容转换成TestCase
格式的用例对象。
通过实现GetPath
和ToTestCase
这两个方法,TestCase
结构体就可以被视为一个符合ITestCase
接口的对象。在用例的读取和转换过程中,我们会利用这两个方法来处理不同类型的测试用例文件,并将它们转换成可执行的TestCase
对象。
通过这种设计,我们可以实现用例的统一管理和执行,使得整个测试框架更加灵活和易于扩展。同时,通过理解TestCase
的结构和接口的实现,我们能更好地把握用例读取和转换的流程,为进一步的测试执行和二次开发打下坚实的基础。
// ITestCase represents interface for testcases,
// includes TestCase and TestCasePath.
type ITestCase interface {
GetPath() string
ToTestCase() (*TestCase, error)
}
// TestCase is a container for one testcase, which is used for testcase runner.
// TestCase implements ITestCase interface.
type TestCase struct {
Config *TConfig
TestSteps []IStep
}
func (tc *TestCase) GetPath() string {
return tc.Config.Path
}
func (tc *TestCase) ToTestCase() (*TestCase, error) {
return tc, nil
}
在 Go 语言中,接口的实现是隐式的,这与 Python 的鸭子类型非常相似。只要一个结构体包含了接口所需的方法,它就被视为实现了该接口,无需显式声明实现关系。
让我们通过一个例子来加深理解。假设有一个手机的接口,其中包含拨打电话和发送短信的功能,还有设备编码、电池和屏幕等属性。我们可以使用该接口来调用手机的功能和获取其属性,并且为其实现华为和小米品牌的手机。
在 Go 中,我们只需要定义一个手机接口,然后为华为和小米品牌的手机分别定义对应的结构体,并在这些结构体中实现手机接口中的方法。由于这些结构体都包含了接口所需的方法,它们会自动被视为实现了手机接口,无需额外声明实现关系。
这种设计模式使得我们可以非常灵活地对不同类型的手机进行操作,而无需关心具体的品牌。这也体现了 Go 语言的灵活性和简洁性,使得接口的实现变得非常简单和直观。
package main
import "fmt"
// Phone 接口表示具有打电话和发送短信功能的手机。
type Phone interface {
Call(number string, message string) error
GetDeviceCode() string
GetBattery() int
GetScreenSize() float64
Brand() string
}
// HuaweiPhone 表示华为品牌的手机。
type HuaweiPhone struct {
DeviceCode string // 设备编码
Battery int // 电池
ScreenSize float64 // 屏幕尺寸
}
// Call 实现了 HuaweiPhone 的打电话方法。
func (h *HuaweiPhone) Call(number string, message string) error {
// 实现华为手机打电话的逻辑。
fmt.Printf("手机拨打电话至: %s,内容: %s\n", number, message)
return nil
}
// GetDeviceCode 返回华为手机的设备编码。
func (h *HuaweiPhone) GetDeviceCode() string {
return h.DeviceCode
}
// GetBattery 返回华为手机的电池电量。
func (h *HuaweiPhone) GetBattery() int {
return h.Battery
}
// GetScreenSize 返回华为手机的屏幕尺寸。
func (h *HuaweiPhone) GetScreenSize() float64 {
return h.ScreenSize
}
// Brand 返回华为手机的品牌信息。
func (h *HuaweiPhone) Brand() string {
return "华为"
}
// XiaomiPhone 表示小米品牌的手机。
type XiaomiPhone struct {
DeviceCode string // 设备编码
Battery int // 电池
ScreenSize float64 // 屏幕尺寸
}
// Call 实现了 XiaomiPhone 的打电话方法。
func (x *XiaomiPhone) Call(number string, message string) error {
// 实现小米手机打电话的逻辑。
fmt.Printf("手机拨打电话至: %s,内容: %s\n", number, message)
return nil
}
// GetDeviceCode 返回小米手机的设备编码。
func (x *XiaomiPhone) GetDeviceCode() string {
return x.DeviceCode
}
// GetBattery 返回小米手机的电池电量。
func (x *XiaomiPhone) GetBattery() int {
return x.Battery
}
// GetScreenSize 返回小米手机的屏幕尺寸。
func (x *XiaomiPhone) GetScreenSize() float64 {
return x.ScreenSize
}
// Brand 返回小米手机的品牌信息。
func (x *XiaomiPhone) Brand() string {
return "小米"
}
// CreatePhone 是一个工厂函数,根据传入的品牌名返回对应的手机品牌实例。
func CreatePhone(brand string) Phone {
switch brand {
case "华为":
return &HuaweiPhone{
DeviceCode: "HW123",
Battery: 80,
ScreenSize: 6.5,
}
case "小米":
return &XiaomiPhone{
DeviceCode: "XM456",
Battery: 90,
ScreenSize: 6.0,
}
default:
return nil
}
}
// PrintPhoneInfo 打印手机的相关信息。
func PrintPhoneInfo(p Phone, number, message string) {
p.Call(number, message)
fmt.Println(p.Brand(), "设备编码:", p.GetDeviceCode())
fmt.Println(p.Brand(), "电池电量:", p.GetBattery(), "%")
fmt.Println(p.Brand(), "屏幕尺寸:", p.GetScreenSize())
fmt.Println(p.Brand(), "手机品牌:", p.Brand())
fmt.Println()
}
func main() {
huawei := CreatePhone("华为")
if huawei != nil {
PrintPhoneInfo(huawei, "123456789", "你好!")
} else {
fmt.Println("未知品牌手机")
}
xiaomi := CreatePhone("小米")
if xiaomi != nil {
PrintPhoneInfo(xiaomi, "987654321", "Hi!")
} else {
fmt.Println("未知品牌手机")
}
}
在上述代码中,我们通过实现了华为和小米手机的具体类型,并且使用CreatePhone
函数来创建出对应的对象。同时,我们还使用PrintPhoneInfo
函数来打印出具体手机的信息。可以看到华为和小米手机没有本质上的关联,但是通过Phone
接口,让他们可以在同个方法内进行相同操作。
这种设计模式使得我们可以根据不同的需求来读取用例内容。例如,可以读取json
格式的字符串作为用例,也可以直接将数据库模型设置为用例类型,在读取用例时直接查询数据库。
实现测试用例读取的方式有很多种,可以根据项目的具体代码结构和需求来选择最合适的方法。通过灵活地选择不同的实现方式,我们可以更好地适应项目的特点,提高代码的可读性和可维护性。
这种灵活性和可扩展性是 Go 语言的优势之一,它允许我们根据具体的业务需求,选择更适合的设计和实现方式,从而让我们的代码更加简洁、高效、可靠。在开发过程中,我们应该善于利用这种灵活性,选择最佳的方法来实现我们的需求。