专栏文章 实战 Hrp:手把手教你增加接口协议 -- 用例类型

扬帆自动化测试平台 · 2023年08月17日 · 2759 次阅读

源码

官网

demo 账号:admin,密码:123456
hrp 最重要的就是用例组成,所以这部分我们会分几节来讲解,其中会涉及到一些比较基础的内容,如果你基础比较扎实,可以略过比较基础部分,直接看源码相关部分。

在 Go 中,我们可以通过config来进行测试用例的设置。在response_test.go文件的assertRunTestCases函数中,我们可以通过NewStep方法来创建一个步骤对象,并且使用链式调用的方式设置不同的参数。

通过链式调用,我们可以依次使用GETValidateAssertEqual等方法来设置相应的参数。例如,使用GET方法设置请求方法、URL 和 Headers,使用Validate方法设置响应的校验规则,使用AssertEqual方法设置预期结果与实际结果的比较。

除了上述方法外,我们还可以使用ExtractTeardownHook等方法来设置一些额外的参数,用于进一步处理响应数据或执行一些清理操作。

这种链式调用的方式使得配置测试用例变得非常简洁和直观,同时也提高了代码的可读性和可维护性。通过深入理解这些调用方法,我们能够更好地掌握 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"),
        },
    }
}

从上述代码可以看出,一个测试用例主要由三部分组成:TestCaseConfigTestSteps

其中,Config部分在上一期中已经详细介绍过,并对链式调用进行了简单介绍,它包含了测试用例的配置信息,比如请求头、请求方法等。

现在,我们着重看一下TestCase部分。TestCase是一个结构体,并且实现了ITestCase接口。这个接口包含了两个方法:GetPath用于获取用例的路径,ToTestCase用于将文件内容转换成TestCase格式的用例对象。

通过实现GetPathToTestCase这两个方法,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 语言的优势之一,它允许我们根据具体的业务需求,选择更适合的设计和实现方式,从而让我们的代码更加简洁、高效、可靠。在开发过程中,我们应该善于利用这种灵活性,选择最佳的方法来实现我们的需求。

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
暂无回复。
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册