• 还在篮子里

    《Cloud Native Go》 · 语雀

    《Cloud Native Go》

    测试

    • 为应用程序的整个生命周期内产生红利,特别是在生产环境中。
    • 创建失败测试 可 GitHub 搜索 FailPoint

    迭代测试-壮丽的蒙太奇

    1. TDD Pass 我们创建了测试 HTTP 务器所需的初始配置 该服务器调用
      HTTP 处理程序方法(被测方法) 由于被测方法尚不存在,测试开始时会编译失败。
      将测试资源代码添加到 createMatchHandler 方法中可以使测试通过。
    2. TDD Pass 添加断 ,判断 HTTP 返回值中是否包含 Location header
      初测试失败,因此在 location header 中添加了 个占位符
    3. TDD Pass 添加断 Location header 个正确格式的 URL ,指向由
      GUID 标识的 match 最初测试失败,随后生成 个新的 GUID 和设置正确的 location
      header
    4. TDD Pass 添加断言,判断 HTTP 返回值中 match ID location header
      中的 GUID 是否相等 最初测试失败,为了通过测试,需要在测试端添加能解析返
      回数据的代码 这意味着必须创建 个可以在服务器端返回有效数据的结构体。因
      此不在处理程序中返回“this is a test ”,而是返回 个真正的响应对象。
      68 Cloud Native Go
    5. TDD Pass 。添加断言,判断被处理函数使用的存储库是否已经包括了新创
      建的 tch 。为 ,必须 个存储库接口并实现 个内存存储库
    6. TDD Pass 。添加断言,判断服务返回的 大小和存储库中的是否相同。
      这使得我们必须为响应创建 个新的结构体,井进行更新。除此之外,我们还更新
      了另一个 gogo-engi 库,它实现了 游戏需要的最小分辨 的业务逻辑 应该
      在最大程度上与 GoGo 服务解祸。
    7. TDD Pass 添加断言 判断创建 请求时包含的游戏玩家和服务端的
      JSO 返回中的值是否相同,并且是否也相应地保存在存储库中
    8. TDD Pass 。添加断言,测试如果发送除 格式以外的数据,或者没有
      为创建 请求发送合理的值,服务器是否会返回“Bad Request 飞这些断言会失
      败,因此需要为处理程序添加检验 JSON 格式和无效请求对象的代码 Go 可以很好
      地支持 JSON 反序列化,所以通过检查反序列结构体中缺少的变量或默认值可以捕获
      大多数“ eq es ”输入。

    这是测试驱动开发的实例。作为一种敏捷开发方法,可以借鉴使用。

    服务发现和动态服务发现

    资料更新、多版本灰度发布。

    关于数据库编写

    • 测试驱动开发 编写数据库
    • 集成测试自动化测试 

    事件溯源

    事件溯源在我看来是一种非常棒的伴随开发的方法论。它注重调试,修改,迭代。我认为这也是软件开发中必要的部分。

    1. 幂等

    注重操作幂等,可以减少很多操作失败带来的损失

    1. 隔离

    单个事件独立,对于出错操作定位来说易于分析

    1. 可测试

    如果不可测试,都无法知道是否正确,就要用线上更大的成本检验。这是和开发本意不符合的。

    1. 可再现,可恢复

    通过这个原则,让出错,或者故意出错的环境能够再次出现,而不是在线上因为巧合崩溃,却不知道怎么修复,重启之后莫名其妙又好了这样的稀里糊涂的维护。

    1. 大数据

    我认为这里大数据指的是量化分析事件,能够屏蔽自己无法关注到的信息,直接找到被这些信息掩藏的因果关系。

    最后和现实最终一致

    事实上,这里有一个理念就是,编程出得程序,或者工具,它应该反应与现实一致。我认为这是约定大于配置的理念。

    借用 HackNew 里的一个人所说。

    当我发现,如果我只使用 Vim 的默认配置的话。我能够在所有的环境下使用 Vim 。

    而不是只能在我的定制化 vim 上操作。

    安全是一直考虑的问题

    另外要注意的是,安全是贯彻始终的一个问题。越早维护他,之后受到的损失越小。对待开发的软件一开始就认真对待,除了安全之外,质量也会有所保证。

    实践学科

    无论是编程,还是单纯云原生,都是如此。

    吾生也有涯,而知也无涯。以有涯随无涯,殆已。实践印证前进,而不是去了解所有的东西再着手。

    来源: 《Cloud Native Go》 · 语雀

  • 还在篮子里

    使用Go进行集成测试的MySQL Docker容器 · 语雀

    使用Go进行集成测试的MySQL Docker容器

    原文链接                                                                                     作者:Mitesh

    翻译整理                                                                                     翻译整理:Abser

    Overview

    Bug 在实际生产中常常代价高昂。我们可以使用测试用例来在开发过程中捕获它们,以降低我们的成本。测试在所有软件中都非常重要。这有助于确保代码的正确性并有助于防止恶化。单元测试有助于隔离测试组件,而无需任何外部依赖。但是单元测试不足以确保我们能够拥有经过良好测试的稳定系统。实际上,在集成不同组件的过程中会发生故障。如果我们不在真实的环境上测试数据库后端的应用程序将面临的问题,我们可能永远不会注意到由于事务未提交,数据库的错误版本等问题。集成测试在端到端测试中扮演了重要角色。

    在当今世界,我们编写了许多软件应用程序,其中包含数据库作为存储后端。模拟这些数据库调用以进行单元测试可能很麻烦。在纲要中进行小的更改可能会导致重写部分或全部。因为查询不会连接到实际的数据库引擎,因此不会验证查询的语法或约束。模拟每个查询都可能导致重复工作。为避免这种情况,我们应该测试一个真正的数据库,在测试完成后可以将其销毁。Docker 非常适合运行测试用例,因为我们可以在几秒钟内运行容器并在完成后终止它们。

    安装docker

    让我们了解如何启动 MySQL docker 容器并使用它来使用 go 代码进行测试。我们首先需要确保运行我们的测试用例的系统安装了 docker,可以通过运行命令“ docker ps ” 来检查。如果未安装docker,请从此处安装 docker 。

    func(d * Docker)isInstalled()bool { 
      command:= exec.Command(“docker”,“ps”)
      err:= command.Run()
      if err!= nil { 
        return false 
      } 
      return true 
    }

    运行容器

    安装 docker 之后,我们需要使用用户和密码运行 MySQL 容器,该用户和密码可用于连接 MySQL 服务器。

    docker run --name our-mysql-container -e MYSQL_ROOT_PASSWORD = root -e MYSQL_USER = gouser -e MYSQL_PASSWORD = gopassword -e MYSQL_DATABASE = godb -p 3306:3306 --tmpfs / var / lib / mysql mysql:5.7

    这将运行 MySQL 版本 5.7 的 docker 镜像,其容器名称为 “our-mysql-container”。“-e” 指定我们需要为 MySQL docker 容器设置的运行时变量。我们将 root 设置为 root 密码。使用密码“gopassword” 创建用户 “gouser”,我们用它来连接到我们的应用程序中的 MySQL 服务器。我们正在暴露 Docker 容器的 3306 端口,所以我们可以连接到在 docker 容器内运行的 mysql 服务器。我们使用的是 tmpfs mount,它只将数据存储在主机的内存中。当容器停止时,将删除 tmpfs 挂载。因为我们只是进行测试,所以不需要永久存。

    type ContainerOption struct {
      Name              string
      ContainerFileName string
      Options           map[string]string
      MountVolumePath   string
      PortExpose        string
    }
    
    func (d *Docker) getDockerRunOptions(c ContainerOption) []string {
      portExpose := fmt.Sprintf("%s:%s", c.PortExpose, c.PortExpose)
      var args []string
      for key, value := range c.Options {
        args = append(args, []string{"-e", fmt.Sprintf("%s=%s", key, value)}...)
      }
      
      args = append(args, []string{"--tmpfs", c.MountVolumePath, c.ContainerFileName}...)
      
      dockerArgs := append([]string{"run", "-d", "--name", c.Name, "-p", portExpose}, args...)
      return dockerArgs
    }
    
    func (d *Docker) Start(c ContainerOption) (string, error) {
      dockerArgs := d.getDockerRunOptions(c)
      command := exec.Command("docker", dockerArgs...)
      command.Stderr = os.Stderr
      
      result, err := command.Output()
      if err != nil {
        return "", err
      }
      
      d.ContainerID = strings.TrimSpace(string(result))
      d.ContainerName = c.Name
      
      command = exec.Command("docker", "inspect", d.ContainerID)
      result, err = command.Output()
      if err != nil {
        d.Stop()
        return "", err
      }
      return string(result), nil
    }
    
    func (m *MysqlDocker) StartMysqlDocker() {
      mysqlOptions := map[string]string{
        "MYSQL_ROOT_PASSWORD": "root",
        "MYSQL_USER":          "gouser",
        "MYSQL_PASSWORD":      "gopassword",
        "MYSQL_DATABASE":      "godb",
      }
      containerOption := ContainerOption{
        Name:              "our-mysql-container",
        Options:           mysqlOptions,
        MountVolumePath:   "/var/lib/mysql",
        PortExpose:        "3306",
        ContainerFileName: "mysql:5.7",
      }
      
      m.Docker = Docker{}
      m.Docker.Start(containerOption)
    }

    我们可以通过 containerId 检查容器以获取容器的详细信息。

    docker inspect containerId

    一旦我们运行 Docker 容器,我们需要等到我们的 docker 容器启动并运行。我们可以使用以下命令检查这个。

    docker ps -a

    使用实例

    一旦 docker 启动并运行,我们就可以开始在我们的应用程序中使用它来运行真实数据库的集成测试用例。

    func (d *Docker) WaitForStartOrKill(timeout int) error {
      for tick := 0; tick < timeout; tick++ {
        containerStatus := d.getContainerStatus()
        if containerStatus == dockerStatusRunning {
         return nil
        }
        
        if containerStatus == dockerStatusExited {
         return nil
        }
        time.Sleep(time.Second)
      }
      
      d.Stop()
      return errors.New("Docker faile to start in given time period so stopped")
    }
    
    func (d *Docker) getContainerStatus() string {
      command := exec.Command("docker", "ps", "-a", "--format", "{{.ID}}|{{.Status}}|{{.Ports}}|{{.Names}}")
      output, err := command.CombinedOutput()
      if err != nil {
        d.Stop()
        return dockerStatusExited
      }
      
      outputString := string(output)
      outputString = strings.TrimSpace(outputString)
      dockerPsResponse := strings.Split(outputString, "\n")
      
      for _, response := range dockerPsResponse {
        containerStatusData := strings.Split(response, "|")
        containerStatus := containerStatusData[1]
        containerName := containerStatusData[3]
        
        if containerName == d.ContainerName {
          if strings.HasPrefix(containerStatus, "Up ") {
            return dockerStatusRunning
          }
        }
      }
      return dockerStatusStarting
    }

    我们可以使用下面的连接字符串从 go 代码连接到 docker 中运行的 MySQL服 务器。

    gouser:gopassword@tcp(localhost:3306)/godb?charset=utf8&parseTime=True&loc=Local

    结束

    这些可以在每次运行时重新创建来模拟使用真实数据库运行集成测试。这有助于确保我们的应用程序已准备好进行生产发布。

    完整的代码可以在这个git存储库中找到:https//github.com/MiteshSharma/DockerMysqlGo

    来源: 使用Go进行集成测试的MySQL Docker容器 · 语雀