zoukankan      html  css  js  c++  java
  • Go项目的测试代码2(项目运用)

    上一篇文章介绍了最基本的测试代码的写法。
    Go项目的测试代码(基础)

    这里简单的共享一下我在项目中使用的方式。

    项目结构

    我们实际项目中, 结构简单地分了控制层controllers模块层models

    因为现在都已微服务的形式开发,没必要太复杂的结构。
    分控制层和模块层已经满足我们的需求不需要再细分了

    |___config          ||==> 配置文件
    | |___config.qa.go
    | |___config.production.go
    ... ...
    |___controllers     ||==> 控制层,只做参数的有效性和简单的逻辑处理
    | |___app_api_test.go
    | |___app_api.go
    | |___init_test.go
    | |___tenant_api.go
    ... ...
    |___models          ||==> 模块层,所有的业务都是在模块层里实现的
    | |___app.go
    | |___app_test.go
    | |___init_test.go
    | |___tenant.go
    ... ...
    |___main.go         ||==> 系统的入口
    |___factory         |||
    |___kit             ||| ==> 按需求自定义建立一个package方便使用
    |___filters         |||
    ... ...

    看项目结构的话,可以发现测试方法直接写在对应的package里面

    有些人喜欢把测试方法分另一个package里写,也可以~不同人有不同的喜好。

    我也试过这种方式,但是还是喜欢写在同一个package里面,这么写简单方便

    控制层controllers和模块层models的不同方式

    控制层controllers
    控制层是提供接口api的直接入口,所以那些重要的api都需要写测试方法。
    我喜欢在这里写参数验证业务验证等等主要功能的测试

    模块层models
    因为控制层controllers里已经做了业务验证了,所以再次做业务验证感觉是多余的。
    我喜欢在这里写方法的逻辑测试,一般和数据没太多关系。
    或很复杂的业务处理的话也会在这里写测试,和控制层做双重确认

    控制层controllers的测试代码

    init() 函数

    每个package里有 init_test.go文件
    这里写 init()方法 执行 go test的时候的初始化方法

    package controllers
    
    var (
        appEnv           = flag.String("app-env", os.Getenv("APP_ENV"), "app env")
        ctx              context.Context
        echoApp          *echo.Echo
        xormEngine       *xorm.Engine
    )
    
    func init() {
        //执行1个线程
        runtime.GOMAXPROCS(1)
        var err error
        //测试数据的链接是内存里的sqlite ==> 每次执行测试代码的时候用到的是内存的sqlite 所以不用管理数据库
        xormEngine, err = xorm.NewEngine("sqlite3", ":memory:")
        if err != nil {
            panic(err)
        }
        //读取配置
        var c configutil.Config
        if err := configutil.Read("", &c, configutil.TestMode); err != nil {
            panic(err)
        }
    
        //建立Redis链接 没有用到redis可以跳过
        models.SetRedisConn(os.Getenv("TEST_SERVICE_REDIS_CONNECTION"))
        //登记struct到xormEngine
        models.SetXormEngineSync(xormEngine)
        //初始化数据
        models.Seed(xormEngine, configutil.TestMode)
    
        //echo架构
        echoApp = echo.New()
        echoApp.Validator = &filters.Validator{}
    
        //为了测试先模拟一个context
        ctx = context.WithValue(context.Background(), echomiddleware.ContextDBName, xormEngine.NewSession())
    }

    Seed() 函数

    我觉得写测试代码的时候,制作测试数据的时间比会占50%以上
    让人最头疼的事情。

    刚开始的时候我觉得可以直接写。
    如下:

    var (
        tenants = []Tenant{
            {Id: 1, Code: "eland", Name: "上海衣恋", Enable: true},
            {Id: 1, Code: "T02", Name: "Ice cream", Enable: true},
        }
    
        brands = []Brand{
            {Id: 1, Code: "EE", Name: "Eland", Enable: true},
            {Id: 2, Code: "EK", Name: "Eland Accessory", Enable: true},
            {Id: 3, Code: "IC", Name: "Haagen-Dazs", Enable: true},
        }
        ... ...
    )
    func Seed(xormEngine *xorm.Engine) {
        for _, u := range tenants {
            xormEngine.Insert(&u)
        }
        for _, u := range brands {
            xormEngine.Insert(&u)
        }
        ... ...
    }

    但是当有一些mapping表的话这么加数据会是个噩梦…ㅠㅠ

    这时候可以先把数据添加到mysql数据库(使用流行的工具应该还不错吧?)。
    sqlite的可视化工具太难用果断放弃。

    func Seed(xormEngine *xorm.Engine, executeMode configutil.ExecuteMode) {
        //建立原始数据的连接
        driverName := os.Getenv("TEST_SERVICE_DRIVER")
        dataSourceName := os.Getenv("TEST_SERVICE_CONNECTION")
        testXormEngine, err := xorm.NewEngine(driverName, dataSourceName)
        if err != nil {
            panic(err)
        }
    
        //登记struct
        SetXormEngineSync(testXormEngine)
    
        //数据保存到内存的sqlite
        Tenant_SetTestData(testXormEngine, xormEngine)
        Brand_SetTestData(testXormEngine, xormEngine)
        ... ...
    }
    
    func SetXormEngineSync(xormEngine *xorm.Engine) {
        xormEngine.Sync(new(Tenant))
        xormEngine.Sync(new(Brand))
        ... ...
    }
    
    func Tenant_SetTestData(testXormEngine *xorm.Engine, xormEngine *xorm.Engine) {
        var tenants []Tenant
        if err := testXormEngine.Find(&tenants); err != nil {
            fmt.Println(err)
        }
        for _, u := range tenants {
            xormEngine.Insert(&u)
        }
    }
    ... ...

    多人做的项目如何管理原始数据?

    我是这么做的

    • 准备一个公用的mysql数据库(没有的话可以让下班最晚的员工开数据库共享…^^)
    • 在公共的数据库里添加原始数据。
    • 每个组员都可以灵活利用Seed方法,把数据导入到自己本地数据库。
      不嫌慢的话直接调用公用数据库。(也可以使用docker镜像)
      (Seed方法的原始数据库地址和目标数据库地址可以灵活地去修改,导数据非常easy)
    • 如果原始数据有变化,组员之间相互共享,从新导入。

    接口api测试代码

    繁琐的准备工作已经做完,终于可以写测试代码了…^^

    func Test_ColleagueApiController_GetColleagues(t *testing.T) {
        //需要测试的api
        req := httptest.NewRequest(echo.GET, "/api/v1/colleagues/:id", nil)
    
        //添加token和context
        c, rec := SetContextWithToken(req)
    
        //执行完测试代码后需要回滚
        defer factory.DB(c.Request().Context()).Close()
        defer factory.DB(c.Request().Context()).Rollback()
        factory.DB(c.Request().Context()).Begin()
    
        //参数设定
        c.SetParamNames("id")
        c.SetParamValues("1")
    
        //调用api方法
        test.Ok(t, ColleagueApiController{}.GetBrandShops(c))
        test.Equals(t, http.StatusOK, rec.Code)
    
        //api返回结果的结构
        var v struct {
            Result struct {
                ColleagueId int64  `json:"colleagueId"`
                ColleagueNo string `json:"colleagueNo"`
                Name        string `json:"name"`
            } `json:"result"`
            Success bool `json:"success"`
        }
    
        //验证结果
        test.Ok(t, json.Unmarshal(rec.Body.Bytes(), &v))
        test.Equals(t, v.Result.ColleagueId, int64(1))
        test.Equals(t, v.Result.ColleagueNo, "C000001")
        test.Equals(t, v.Result.Name, "测试人员")
    }

    模块层models的测试代码

    init() 函数

    和controllers 的init()方法没什么区别

    var (
        appEnv     = flag.String("app-env", os.Getenv("APP_ENV"), "app env")
        xormEngine *xorm.Engine
    )
    
    func init() {
        runtime.GOMAXPROCS(1)
        var err error
        xormEngine, err = xorm.NewEngine("sqlite3", ":memory:")
        if err != nil {
            panic(err)
        }
        var c configutil.Config
        if err := configutil.Read("", &c, configutil.TestMode); err != nil {
            panic(err)
        }
        SetModelConfig(&ModelConfig{ValidTimeout: c.ValidTimeout, AppEnv: *appEnv})
        SetRedisConn(os.Getenv("TEST_SERVICE_REDIS_CONNECTION"))
        SetXormEngineSync(xormEngine)
    
        Seed(xormEngine, configutil.TestMode)
    }

    多写了一个GetContext()方法 如果需要数据连接先调用。

    func GetContextForTest() context.Context {
        return context.WithValue(context.Background(), echomiddleware.ContextDBName, xormEngine.NewSession())
    }

    测试代码

    模块层models的测试代码如下:

    func Test_GetTenantAppInfosFromAppContainers(t *testing.T) {
        // 因为这个方法不需要连数据库所以也不需要这些操作
        // ctx := GetContextForTest()
        // factory.DB(ctx).Begin()
        // defer factory.DB(ctx).Close()
        // defer factory.DB(ctx).Rollback()
    
        appContainers := []TenantAppContainer{
            {TenantAppId: 1, RoleCode: "admin", RoleName: "admin"},
            {TenantAppId: 1, RoleCode: "dev", RoleName: "dev"},
            {TenantAppId: 1, RoleCode: "plan", RoleName: "plan"},
            {TenantAppId: 2, RoleCode: "admin", RoleName: "admin"},
            {TenantAppId: 2, RoleCode: "plan", RoleName: "plan"},
            {TenantAppId: 3, RoleCode: "dev", RoleName: "dev"},
        }
    
        //测试该方法的功能
        appInfos := GetTenantAppInfosFromAppContainers(appContainers)
    
        test.Equals(t, len(appInfos), 3)
        test.Equals(t, len(appInfos[0].Roles), 3)
        test.Equals(t, len(appInfos[1].Roles), 2)
        test.Equals(t, len(appInfos[2].Roles), 1)
        test.Equals(t, appInfos[0].Roles[0].RoleCode, "admin")
        test.Equals(t, appInfos[0].Roles[0].RoleName, "admin")
        test.Equals(t, appInfos[0].Roles[2].RoleCode, "plan")
        test.Equals(t, appInfos[0].Roles[2].RoleName, "plan")
        test.Equals(t, appInfos[1].TenantAppId, int64(2))
    }

    你们准备好应对如下的场景吗?

    产品经理:因为客户的需求我们需要赶紧发布最新版本。
    我:稍等,让我确认一下~

    cd models 
    go test
    cd ..
    cd controllers
    go test
    

    我:没问题,现在可以发布...^^

    下篇文章介绍项目中我是怎么使用测试替身的…^^


    Go项目的测试代码3(测试替身Test Double)

    未完继续

    ----------------------------------------------
    欢迎大家的意见和交流
    email: li_mingxie@163.com
    博客:https://limingxie.github.io/
  • 相关阅读:
    php后门隐藏技巧
    给你的PHP大马添加后门(黑吃黑)
    分析 PHP大马-php_mof SHELL
    过WAF菜刀制作
    eval与php一句话的关系
    php一句话转发脚本(可能过狗= =)
    wmic与mimikatz技巧
    LPK后门
    grunt常用插件的使用
    jquery实现后台系统左侧菜单的点击展开/收缩二级菜单效果
  • 原文地址:https://www.cnblogs.com/limingxie/p/9131907.html
Copyright © 2011-2022 走看看