进行单元测试结构化的REST API项目

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

GO Unit testing structured REST API projects

问题

我正在尝试为我已经创建的REST API编写良好的单元测试。我有这样一个简单的结构:

ROOT/
    config/
    handlers/
    lib/
    models/
    router/
    main.go

config 目录包含 JSON 格式的配置文件和一个简单的 config.go 文件,用于读取和解析 JSON 文件,并填充 Config 结构体。handlers 目录包含 控制器(即在 router/routes.go 中描述的 METHOD+URL 对应的处理程序)。lib 目录包含一些数据库、请求响应和日志记录的逻辑。models 目录包含结构体及其用于将其与 JSON 和数据库进行映射的函数。最后,router 目录包含路由器和路由定义。

现在,我在搜索和阅读关于如何在 GO 中进行单元测试 REST API 的文章,找到了一些比较满意的文章,介绍了如何设置测试服务器、定义路由和测试请求。一切都很好。但是只适用于测试单个文件!

我的问题是如何为所有处理程序设置测试环境(服务器、路由、数据库连接)?使用在这里找到的方法(我认为非常容易理解和实现),我有一个问题:要么我必须为每个处理程序单独运行测试,要么我必须在一个测试文件中为所有处理程序编写测试套件。我相信你明白这两种情况都不是很理想(第一种情况是因为我需要保证运行 go test 时运行所有成功的测试,第二种情况是因为只有一个测试文件来覆盖所有处理程序函数会变得难以维护)。

到目前为止,我已经成功(根据链接的文章)只是将所有测试和初始化代码放入每个 XYZhandler_test.go 文件中的一个函数中,但我也不喜欢这种方法。

我想要实现的是一种类似于 setUp()init() 的方法,它在第一次触发测试时运行一次,使得所有必需的变量在全局范围内可见和初始化,以便所有后续的测试可以直接使用它们,而无需再次实例化它们,同时确保此设置文件仅为测试编译...

我不确定这是否完全清楚,或者是否需要一些代码示例来解答这种问题(除了文章中已经链接的内容之外),但如果你认为需要,我会添加任何你认为必要的内容,请告诉我!

英文:

I am trying to write nice unit tests for my already created REST API. I have this simple structure:

ROOT/
    config/
    handlers/
    lib/
    models/
    router/
    main.go

config contains configuration in JSON and one simple config.go that reads and parses JSON file and fills the Config struct. handlers contains controllers (i.e. handlers of respective METHOD+URL described in router/routes.go). lib contains some DB, request responder and logger logic. models contains structs and their funcs to be mapped from-to JSON and DB. Finally router contains the router and routes definition.

Now I was searching and reading a lot about unit testing REST APIs in GO and found more or less satisfying articles about how to set up a testing server, define routes and test my requests. All fine. BUT only if you want to test a single file!

My problem is now how to set up the testing environment (server, routes, DB connection) for all handlers? With the approach found here (which I find very easy to understand and implement) I have one problem: either I have to run tests separately for each handler or I have to write test suites for all handlers in just one test file. I believe you understand that both cases are not very happy (1st because I need to preserve that running go test runs all tests that succeed and 2nd because having one test file to cover all handler funcs would become unmaintainable).

By now I have succeeded (according to the linked article) only if I put all testing and initializing code into just one func per XYZhandler_test.go file but I don't like this approach as well.

What I would like to achieve is kind of setUp() or init() that runs once with first triggered test making all required variables globally visible and initialized so that all next tests could use them already without the need of instantiating them again while making sure that this setup file is compiled only for tests...

I am not sure if this is completely clear or if some code example is required for this kind of question (other than what is already linked in the article but I will add anything that you think is required, just tell me!

答案1

得分: 1

也许你可以将你想要从多个单元测试文件中使用的设置代码放入一个单独的包中,只供单元测试使用。

或者你可以将设置代码放入正常的包中,并且只在单元测试中使用它。

这个问题以前已经被提出过,但Go的作者选择不提供一个隐式的测试标签,该标签可以用于在正常的包文件中选择性地启用函数编译。

英文:

Perhaps you could put the setup code that you want to use from multiple unit test files into a separate package that only the unit tests use?

Or you could put the setup code into the normal package and just use it from the unit tests.

It's been asked before but the Go authors have chosen not to implicitly supply a test tag that could be used to selectively enable function compiles within the normal package files.

答案2

得分: 1

测试包,而不是文件!

由于您正在测试处理程序/端点,因此将所有_test文件放在处理程序或路由器包中是有意义的(例如,每个端点/处理程序一个文件)。

此外,不要使用init()来设置您的测试。testing包指定了一个具有以下签名的函数:

func TestMain(m *testing.M)

生成的测试将调用TestMain(m)而不是直接运行测试。TestMain在主goroutine中运行,并且可以在调用m.Run周围执行任何设置和拆卸操作。然后,它应该使用m.Run的结果调用os.Exit。

在TestMain函数中,您可以执行所需的任何设置以运行测试。如果有全局变量,这是声明和初始化它们的地方。您只需要对每个包执行一次此操作,因此将TestMain代码放在单独的_test文件中是有意义的。例如:

package router

import (
    "testing"
    "net/http/httptest"
)

var (
    testServer *httptest.Server
)

func TestMain(m *testing.M)  {
    // 设置测试服务器
    router := ConfigureRouter()
    testServer = httptest.NewServer(router)

    // 运行测试
    os.Exit(m.Run())
}

最后,使用go test my/package/router运行测试。

英文:

Test packages, not files!

Since you're testing handlers/endpoints it would make sense to put all your _test files in either the handlers or the router package. (e.g. one file per endpoint/handler).

Also, don't use init() to setup your tests. The testing package specifies a function with the following signature:

func TestMain(m *testing.M) 

> The generated test will call TestMain(m) instead of running the tests
> directly. TestMain runs in the main goroutine and can do whatever
> setup and teardown is necessary around a call to m.Run. It should then
> call os.Exit with the result of m.Run

Inside the TestMain function you can do whatever setup you need in order to run your tests. If you have global variables, this is the place to declare and initialize them. You only need to do this once per package, so it makes sense to put the TestMain code in a seperate _test file. For example:

package router

import (
	"testing"
	"net/http/httptest"
) 

var (
	testServer *httptest.Server
)

func TestMain(m *testing.M)  {
    // setup the test server
	router := ConfigureRouter()
	testServer = httptest.NewServer(router)

	// run tests
	os.Exit(m.Run())
}

Finally run the tests with go test my/package/router.

huangapple
  • 本文由 发表于 2016年3月23日 20:00:07
  • 转载请务必保留本文链接:https://go.coder-hub.com/36177614.html
匿名

发表评论

匿名网友

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

确定