Golang持续集成与自动化测试和部署

发布于:2025-05-31 ⋅ 阅读:(20) ⋅ 点赞:(0)

概述

在这里插入图片描述

Golang是一门性能优异的静态类型语言,但因其奇快的编译速度,结合DevOps, 使得它也非常适合快速开发和迭代。

本文讲述如何使用Golang, 进行持续集成与自动化测试和部署。主要使用了以下相关技术:

  • dep: 进行包的依赖管理
  • gin: 搭建 api 服务
  • gorm:ORM, 数据CRUD
  • mysql: 存储数据
  • testfixtures: 测试夹具,在自动化测试时,自动向数据库填充用于测试的数据
  • httpexpect: HTTP 测试包,用于API测试
  • GoDotEnv: 环境变量处理
  • go test: 使用test命令进行单元测试, 基准测试和 HTTP 测试
  • GitLabCI: DevOps 工具
  • golint: Golang 静态检查工具
  • migrate: 数据库迁移工具
  • Docker: 使用 zacksleo/golang 镜像, 该镜像默认安装了 curl,git,build-base,dep 和 golint
  • db2struct: 将数据库表结构一键生成为 struct(gorm的model)
  • apig: 基于 gorm 和 gin 一键生成 CRUD API

开发流程

  • 使用 apig 脚手架工具初始化项目结构和目录
  • 使用 dep 安装相关依赖
  • 使用 migrate 编写数据库迁移方法,并执行迁移创建数据表
  • 使用 db2struct 生成 models
  • 使用 apig 生成 crud 代码
  • 使用 httpexpect 编写 api 测试代码,并通过 testfixtures 实现数据的自动填充
  • 编写 GitLabCI 脚本进行持续集成

在上述过程中,如需连接数据库时,可通过 GoDotEnv 来实现环境变量的使用

相关CI脚本

# golang-devops-and-auto-deploy
image: zacksleo/golang
stages:
    - test
    - build
    - deploy

variables:
    GOPATH: /root

before_script:
    - mkdir -p ~/src/github.com/zacksleo/projectname
    - cp -r . ~/src/github.com/zacksleo/projectname
    - cd ~/src/github.com/zacksleo/projectname
lint:
    stage: test
    script:
        - golint -set_exit_status
unit-tests:
    stage: test
    services:
        - mysql:5.6
    variables:
        MYSQL_ROOT_PASSWORD: root
        MYSQL_DATABASE: web
        MYSQL_USER: web
        MYSQL_PASSWORD: web
    script:
        - dep ensure
        - cp tests/.env .env
        - migrate -database "mysql://web:web@tcp(mysql:3306)/web" -path "./db/migrations/" up
        - go test -tags=unit_tests $(go list ./... | grep -v /vendor/ ./tests/api) -v -coverprofile .testCoverage.txt
    coverage: '/^coverage:\s(\d+(?:\.\d+)?%)/'
integration-tests:
    stage: test
    services:
        - mysql:5.6
    variables:
        MYSQL_ROOT_PASSWORD: root
        MYSQL_DATABASE: web
        MYSQL_USER: web
        MYSQL_PASSWORD: web
    script:
        - dep ensure
        - cp tests/.env .env
        - migrate -database "mysql://web:web@tcp(mysql:3306)/web" -path "./db/migrations/" up
        - go test -tags=integration $(go list ./tests/... | grep -v /vendor/) -v
build-bin:
    stage: test
    script:
        - dep ensure
        - env GOOS=linux GOARCH=386 go build -o $CI_PROJECT_DIR/debug
    artifacts:
      expire_in: 60 mins
      untracked: true
      name: "app"
      paths:
        - debug
      when: on_success
build-image:
    image: docker
    stage: build
    dependencies:
        - build-bin
    script:
        - docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
        - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG .
        - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG
        - docker rmi $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG
    only:
        - tags
    tags:
        - go
deploy:
    image: zacksleo/node
    stage: deploy
    before_script:
        - eval $(ssh-agent -s)
        - echo "$SSH_PRIVATE_KEY" > ~/deploy.key
        - chmod 0600 ~/deploy.key
        - ssh-add ~/deploy.key
        - mkdir -p ~/.ssh
        - '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config'
    script:
        - cd deploy/production
        - rsync -rtvhze ssh . root@$DEPLOY_SERVER:/data/gitlab/projectname --stats
        - ssh root@$DEPLOY_SERVER "docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY"
        - ssh root@$DEPLOY_SERVER "cd /data/gitlab/projectname && echo -e '\nTAG=$CI_COMMIT_TAG' >> .env && docker-compose pull app && docker-compose stop app && docker-compose rm -f app && docker-compose up -d app"
    only:
        - tags

集成测试

// +build integration

package api

import (
	"log"
	"os"
	"testing"

	"github.com/gin-gonic/gin"
	"github.com/jinzhu/gorm"
	"github.com/joho/godotenv"
	"gitlab.com/moguyun/api/debug/db"
	"gitlab.com/moguyun/api/debug/server"
	testfixtures "gopkg.in/testfixtures.v2"
)

var (
	fixtures *testfixtures.Context
	s        *gin.Engine
)

// PrepareTestDatabase for test
func PrepareTestDatabase(db *gorm.DB) {
	var fixtures *testfixtures.Context
	var err error
	testfixtures.SkipDatabaseNameCheck(true)
	fixtures, err = testfixtures.NewFolder(db.DB(), &testfixtures.MySQL{}, "../fixtures")
	if err != nil {
		log.Fatal(err)
	}
	if err = fixtures.Load(); err != nil {
		log.Fatal(err)
	}
}

// TestMain setup database
func TestMain(m *testing.M) {
	// Open connection with the test database.
	godotenv.Load("../.env")
	database := db.Connect()
	s = server.Setup(database)
	PrepareTestDatabase(database)
	os.Exit(m.Run())
}

func TestToken(t *testing.T) {
	t.Run("CreateToken", TestCreateToken)
}

func TestCustomer(t *testing.T) {
	t.Run("GetCustomers", TestGetCustomers)
	t.Run("GetCustomer", TestGetCustomer)
	t.Run("CreateCustomer", TestCreateCustomer)
	t.Run("UpdateCustomer", TestUpdateCustomer)
	t.Run("DeleteCustomer", TestDeleteCustomer)
}

注意事项

在测试中,如果需要区分单元测试和集成测试,可以使用 build tags 实现,如在文件头部中添加 // +build integration, 运行测试使用 - go test -tags=integration $(go list ./tests/... | grep -v /vendor/) -v 可以只执行集成测试

参考文档