使用dockertest在Golang中使用migrate

huangapple go评论73阅读模式
英文:

Golang using migrate with dockertest

问题

目标是在数据库上测试逻辑。似乎dockertest可能对此有用,并且他们提供了示例来设置测试。然而,这些临时数据库是空的(没有迁移操作)。在使用dockertest时如何进行数据库迁移?

英文:

The goal is to test logic on database. It seems that dockertest might be useful for it and they provide examples for how to setup the test. However, these ephemeral databases are empty (no migration done).

How to migrate database when using dockertest?

答案1

得分: 2

不确定这是否是最佳方法,但我连接了几个示例,并通过[golang-migrate]库在测试设置中完成了完整的迁移。

下面是TestMain设置函数,它设置了带有postgres数据库连接的Dockertest。在运行测试的m.Run()之前,有一个runMigrations函数,它使用golang-migrate从本地目录中获取所有脚本并将它们应用到数据库中。它可以工作,但速度相对较慢。

func TestMain(m *testing.M) {
	// 在Windows上使用合理的默认值(tcp/http),在Linux/OSX上使用socket
	pool, err := dockertest.NewPool("")
	if err != nil {
		log.Fatalf("无法连接到Docker:%s", err)
	}

	// 拉取镜像,基于该镜像创建容器并运行
	resource, err := pool.RunWithOptions(&dockertest.RunOptions{
		Repository: "postgres",
		Tag:        "11",
		Env: []string{
			"POSTGRES_PASSWORD=secret",
			"POSTGRES_USER=user_name",
			"POSTGRES_DB=dbname",
			"listen_addresses = '*'",
		},
	}, func(config *docker.HostConfig) {
		// 将AutoRemove设置为true,以便停止的容器自行消失
		config.AutoRemove = true
		config.RestartPolicy = docker.RestartPolicy{Name: "no"}
	})
	if err != nil {
		log.Fatalf("无法启动资源:%s", err)
	}

	hostAndPort := resource.GetHostPort("5432/tcp")
	databaseUrl := fmt.Sprintf("postgres://user_name:secret@%s/dbname?sslmode=disable", hostAndPort)

	log.Println("连接到数据库的URL:", databaseUrl)

	resource.Expire(120) // 告诉Docker在120秒内强制终止容器

	// 指数退避重试,因为容器中的应用程序可能还没有准备好接受连接
	pool.MaxWait = 120 * time.Second
	if err = pool.Retry(func() error {
		db, err = sql.Open("postgres", databaseUrl)
		if err != nil {
			return err
		}
		return db.Ping()
	}); err != nil {
		log.Fatalf("无法连接到Docker:%s", err)
	}

	// 迁移数据库
	if err := runMigrations("../migrations", db); err != nil {
		log.Fatalf("无法迁移数据库:%s", err)
	}

	// 运行测试
	code := m.Run()

	// 不能使用defer,因为os.Exit不会等待defer语句执行
	if err := pool.Purge(resource); err != nil {
		log.Fatalf("无法清除资源:%s", err)
	}

	os.Exit(code)
}

func runMigrations(migrationsPath string, db *sql.DB) error {
	if migrationsPath == "" {
		return errors.New("缺少迁移路径")
	}
	driver, err := postgres.WithInstance(db, &postgres.Config{})
	if err != nil {
		return err
	}
	m, err := migrate.NewWithDatabaseInstance("file://"+migrationsPath, "postgres", driver)
	if err != nil {
		return err
	}
	err = m.Up()
	if err != nil && err != migrate.ErrNoChange {
		return err
	}
	return nil
}
英文:

Not sure that's the best approach but I've connected a few examples and came up with a full migration via [golang-migrate] lib on test setup.

Below the TestMain is setup func that sets Dockertest with postgres DB connection. Just before m.Run(), that runs tests, there's runMigrations which uses golang-migrate to pull all scripts in local dir and apply them to db. It works but it's rather slow.

func TestMain(m *testing.M) {
// uses a sensible default on windows (tcp/http) and linux/osx (socket)
pool, err := dockertest.NewPool("")
if err != nil {
log.Fatalf("Could not connect to docker: %s", err)
}
// pulls an image, creates a container based on it and runs it
resource, err := pool.RunWithOptions(&dockertest.RunOptions{
Repository: "postgres",
Tag:        "11",
Env: []string{
"POSTGRES_PASSWORD=secret",
"POSTGRES_USER=user_name",
"POSTGRES_DB=dbname",
"listen_addresses = '*'",
},
}, func(config *docker.HostConfig) {
// set AutoRemove to true so that stopped container goes away by itself
config.AutoRemove = true
config.RestartPolicy = docker.RestartPolicy{Name: "no"}
})
if err != nil {
log.Fatalf("Could not start resource: %s", err)
}
hostAndPort := resource.GetHostPort("5432/tcp")
databaseUrl := fmt.Sprintf("postgres://user_name:secret@%s/dbname?sslmode=disable", hostAndPort)
log.Println("Connecting to database on url: ", databaseUrl)
resource.Expire(120) // Tell docker to hard kill the container in 120 seconds
// exponential backoff-retry, because the application in the container might not be ready to accept connections yet
pool.MaxWait = 120 * time.Second
if err = pool.Retry(func() error {
db, err = sql.Open("postgres", databaseUrl)
if err != nil {
return err
}
return db.Ping()
}); err != nil {
log.Fatalf("Could not connect to docker: %s", err)
}
// Migrating DB
if err := runMigrations("../migrations", db); err != nil {
log.Fatalf("Could not migrate db: %s", err)
}
//Run tests
code := m.Run()
// You can't defer this because os.Exit doesn't care for defer
if err := pool.Purge(resource); err != nil {
log.Fatalf("Could not purge resource: %s", err)
}
os.Exit(code)
}
func runMigrations(migrationsPath string, db *sql.DB) error {
if migrationsPath == "" {
return errors.New("missing migrations path")
}
driver, err := postgres.WithInstance(db, &postgres.Config{})
if err != nil {
return err
}
m, err := migrate.NewWithDatabaseInstance("file://"+migrationsPath, "postgres", driver)
if err != nil {
return err
}
err = m.Up()
if err != nil && err != migrate.ErrNoChange {
return err
}
return nil
}

huangapple
  • 本文由 发表于 2022年8月27日 23:21:22
  • 转载请务必保留本文链接:https://go.coder-hub.com/73512227.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定