Gonkey-微服务测试工具

Gonkey正在Lamoda中测试我们的微服务,我们认为他也可以测试您的微服务,因此我们将其发布在开源中 。 如果您的服务功能主要通过API实现,并且使用JSON进行数据交换,那么Gonkey几乎肯定适合您。


图片


下面,我将更详细地讨论它,并通过具体示例展示如何使用它。


刚奇是如何出生的


我们有一百多个微服务,每个微服务都可以解决特定的任务。 所有服务都有一个API。 当然,它们中的一些也是用户界面,但是,尽管如此,它们的主要作用是成为站点,移动应用程序或其他内部服务的数据源,并因此提供软件界面


当我们意识到有很多服务,然后会有更多的服务时,我们开发了一个内部文档来描述API设计的标准方法,并将Swagger用作描述工具(甚至编写了用于基于swagger规范生成代码的实用程序)。 如果您想了解更多有关此的信息,请参阅Andrew关于Highload ++的演讲。


API设计的标准方法自然导致了标准测试方法的想法。 这是我想要实现的目标:


  1. 通过API测试服务 ,因为服务的几乎所有功能都是通过API实现的
  2. 能够自动启动测试以将其集成到我们的CI / CD流程中的能力,正如他们所说的,“按按钮运行”
  3. 编写测试应该是可移植的 ,也就是说,不仅程序员可以编写测试,理想情况下还可以是不熟悉编程的人。

于是贡奇就出生了。


那是什么


Gonkey是一个 (适用于Golang上的项目)和一个控制台实用程序 (适用于任何语言和技术的项目),您可以通过该工具通过根据预定义的脚本访问服务的API来进行服务功能和回归测试 。 测试脚本在YAML文件中进行了描述。


简而言之,Gonkey可以:


  • 使用HTTP请求轰炸您的服务,并确保其响应符合预期。 它假定在​​请求和响应中使用了JSON,但是最有可能的是,它将在具有不同格式的答案的简单情况下使用。
  • 通过使用来自夹具的数据(也在YAML文件中指定)填充数据库来为测试做准备;
  • 使用模拟模拟外部服务的响应(仅当您将Gonkey作为库连接时,此功能才可用);
  • 将测试结果提供给控制台或生成“魅力”报告。

项目资料库
Docker镜像


Gonkey的服务测试示例


为了不给您增加文本负担,我想从文字到事迹,然后在此处测试一些API,并告诉并说明如何编写测试脚本。


让我们在Go上画一个小服务,它将模拟交通信号灯的工作。 它存储当前信号的颜色:红色,黄色或绿色。 您可以通过API获取当前的信号颜色或设置新的信号颜色。


//    const ( lightRed = "red" lightYellow = "yellow" lightGreen = "green" ) //      type trafficLights struct { currentLight string `json:"currentLight"` mutex sync.RWMutex `json:"-"` } //   var lights = trafficLights{ currentLight: lightRed, } func main() { //       http.HandleFunc("/light/get", func(w http.ResponseWriter, r *http.Request) { lights.mutex.RLock() defer lights.mutex.RUnlock() resp, err := json.Marshal(lights) if err != nil { log.Fatal(err) } w.Write(resp) }) //       http.HandleFunc("/light/set", func(w http.ResponseWriter, r *http.Request) { lights.mutex.Lock() defer lights.mutex.Unlock() request, err := ioutil.ReadAll(r.Body) if err != nil { log.Fatal(err) } var newTrafficLights trafficLights if err := json.Unmarshal(request, &newTrafficLights); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } if err := validateRequest(&newTrafficLights); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } lights = newTrafficLights }) //   () log.Fatal(http.ListenAndServe(":8080", nil)) } func validateRequest(lights *trafficLights) error { if lights.currentLight != lightRed && lights.currentLight != lightYellow && lights.currentLight != lightGreen { return fmt.Errorf("incorrect current light: %s", lights.currentLight) } return nil } 

main.go的完整源代码在这里


运行程序:


 go run . 

15分钟内即可快速绘制草图! 当然,他在某个地方弄错了,所以我们将进行测试并检查。


下载并运行Gonkey:


 mkdir -p tests/cases docker run -it -v $(pwd)/tests:/tests lamoda/gonkey -tests tests/cases -host host.docker.internal:8080 

该命令通过docker通过gonkey启动映像,在容器内安装tests / cases目录,并使用-tests tests / cases -host参数启动gonkey。


如果您不喜欢docker方法,则可以使用以下命令替代方法:


 go get github.com/lamoda/gonkey go run github.com/lamoda/gonkey -tests tests/cases -host localhost:8080 

启动并得到结果:


 Failed tests: 0/0 

没有测试-无需检查。 让我们编写第一个测试。 用最少的内容创建一个文件tests / cases / light_get.yaml:


 - name: WHEN currentLight is requested MUST return red method: GET path: /light/get response: 200: > { "currentLight": "red" } 

在第一层是一个列表。 这意味着我们已经描述了一个测试用例,但是文件中可能有很多。 他们共同组成了测试方案。 因此,一个文件-一个脚本。 您可以使用测试脚本创建任意数量的文件,如果方便的话,可以将它们安排在子目录中-gonkey从传输的目录中读取所有yaml和yml文件,并且具有更深的递归性。


以下文件描述了将发送到服务器的请求的详细信息:方法,路径。 甚至更低的是我们期望服务器提供的响应代码(200)和响应主体。


完整文件格式在README中描述。


再次运行:


 docker run -it -v $(pwd)/tests:/tests lamoda/gonkey -tests tests/cases -host host.docker.internal:8080 

结果:


  Name: WHEN currentlight is requested MUST return red Request: Method: GET Path: /light/get Query: Body: <no body> Response: Status: 200 OK Body: {} Result: ERRORS! Errors: 1) at path $ values do not match: expected: { "currentLight": "red" } actual: {} Failed tests: 1/1 

错了! 预期具有currentLight字段的结构,并返回空结构。 不好 第一个问题是结果被解释为字符串,这一点可以通过以下事实得到证明:gonkey在没有问题的情况下突出了整个答案:


  expected: { "currentLight": "red" } 

原因很简单:我忘记写了,以便响应中的服务指示内容类型为application / json。 我们修复:


 //       http.HandleFunc("/light/get", func(w http.ResponseWriter, r *http.Request) { lights.mutex.RLock() defer lights.mutex.RUnlock() resp, err := json.Marshal(lights) if err != nil { log.Fatal(err) } w.Header().Add("Content-Type", "application/json") // <--  w.Write(resp) }) 

我们重新启动服务,然后再次运行测试:


  Name: WHEN currentlight is requested MUST return red Request: Method: GET Path: /light/get Query: Body: <no body> Response: Status: 200 OK Body: {} Result: ERRORS! Errors: 1) at path $ key is missing: expected: currentLight actual: <missing> 

太好了,有进步。 Gonkey现在可以识别该结构,但是仍然不正确:答案为空。 原因是我在类型定义中使用了不可导出的currentLight字段:


 //      type trafficLights struct { currentLight string `json:"currentLight"` mutex sync.RWMutex `json:"-"` } 

在Go中,以小写字母命名的结构字段被认为是不可导出的,即无法从其他包中访问。 JSON序列化器看不到它,并且不能将其包括在响应中。 我们纠正:我们用大写字母表示该字段,这意味着它已导出:


 //      type trafficLights struct { urrentLight string `json:"currentLight"` // <--   mutex sync.RWMutex `json:"-"` } 

重新启动服务。 再次运行测试。


 Failed tests: 0/1 

测试通过了!


我们将编写另一个脚本来测试set方法。 用以下内容填充文件tests / cases / light_set.yaml:


 - name: WHEN set is requested MUST return no response method: POST path: /light/set request: > { "currentLight": "green" } response: 200: '' - name: WHEN get is requested MUST return green method: GET path: /light/get response: 200: > { "currentLight": "green" } 

第一个测试为交通信号灯设置一个新值,第二个测试检查状态以确保它已更改。


使用相同的命令运行测试:


 docker run -it -v $(pwd)/tests:/tests lamoda/gonkey -tests tests/cases -host host.docker.internal:8080 

结果:


 Failed tests: 0/3 

成功的结果,但是我们很幸运,脚本以所需的顺序执行:首先是light_get,然后是light_set。 如果他们做相反的事情会怎样? 让我们重命名:


 mv tests/cases/light_set.yaml tests/cases/_light_set.yaml 

并再次运行:


 Errors: 1) at path $.currentLight values do not match: expected: red actual: green Failed tests: 1/3 

首先,执行设置,交通信号灯处于绿色状态,因此接下来的get test运行发现一个错误-他正在等待红色。


摆脱测试依赖于上下文这一事实的一种方法是在脚本的开头(即文件的开头)初始化服务,这通常是在set测试中进行的-首先,我们设置已知值,这应该会产生已知的效果,然后检查效果是否有效。


如果服务使用数据库,则准备执行上下文的另一种方法是使用带有在脚本开始时已加载到数据库中的数据的夹具,从而形成可以检查的服务的可预测状态。 我想在另一篇文章中介绍使用gonkey中的夹具的描述和示例。


同时,我提出以下解决方案。 由于在set脚本中我们实际上正在测试light / set方法和light / get方法,因此我们根本不需要上下文相关的light_get脚本。 我将其删除,然后重命名剩余的脚本,以便该名称反映本质。


 rm tests/cases/light_get.yaml mv tests/cases/_light_set.yaml tests/cases/light_set_get.yaml 

下一步,我想检查一些使用我们服务的负面情况,例如,如果我发送不正确的信号颜色,它将正常工作吗? 还是根本不发送颜色?


创建一个新的测试/案例/light_set_get_negative.yaml脚本:


 - name: WHEN set is requested MUST return no response method: POST path: /light/set request: > { "currentLight": "green" } response: 200: '' - name: WHEN incorrect color is passed MUST return error method: POST path: /light/set request: > { "currentLight": "blue" } response: 400: > incorrect current light: blue - name: WHEN color is missing MUST return error method: POST path: /light/set request: > {} response: 400: > incorrect current light: - name: WHEN get is requested MUST have color untouched method: GET path: /light/get response: 200: > { "currentLight": "green" } 

他检查:


  • 传输错误的颜色时,会发生错误;
  • 当不传输颜色时,发生错误;
  • 错误的颜色传输不会改变交通灯的内部状态。

运行:


 Failed tests: 0/6 

一切都很好:)


将Gonkey连接为图书馆


如您所见,我们正在测试服务API,完全从编写该语言的语言和技术中进行抽象。 以同样的方式,我们可以测试任何我们无法访问源代码的公共API-足以发送请求和接收答案。


但是对于用go编写的我们自己的应用程序,有一种更便捷的方式来运行gonkey-将其作为库连接到项目。 这样,无需预先编译任何内容(无论是gonkey还是项目本身),都可以通过简单地运行go test来运行go test


通过这种方法,我们似乎开始编写单元测试,并且在测试主体中执行以下操作:


  • 以与服务启动时相同的方式初始化Web服务器;
  • 在本地主机和随机端口上运行测试应用程序服务器;
  • 我们从gonkey库中调用该函数,并向其传递测试服务器的地址和其他参数。 下面我将说明这一点。

为此,我们的应用程序将需要一些重构。 它的关键是使服务器的创建成为一个单独的功能,因为我们现在需要在两个地方使用此功能:服务启动时以及运行gonkey测试时。


我将以下代码放在单独的函数中:


 func initServer() { //       http.HandleFunc("/light/get", func(w http.ResponseWriter, r *http.Request) { //   }) //       http.HandleFunc("/light/set", func(w http.ResponseWriter, r *http.Request) { //   }) } 

主要功能如下:


 func main() { initServer() //   () log.Fatal(http.ListenAndServe(":8080", nil)) } 

修改后的main go文件完全


这解放了我们的双手,让我们开始编写测试。 我创建一个func_test.go文件:


 func Test_API(t *testing.T) { initServer() srv := httptest.NewServer(nil) runner.RunWithTesting(t, &runner.RunWithTestingParams{ Server: srv, TestsDir: "tests/cases", }) } 

这是完整的func_test.go文件


仅此而已! 我们检查:


 go test ./... 

结果:


 ok github.com/lamoda/gonkey/examples/traffic-lights-demo 0.018s 

测试通过了。 如果我同时拥有单元测试和gonkey测试,它们将一起运行-非常方便。


生成魅力报告


魅力是一种测试报告格式,用于以清晰美观的方式显示结果。 Gonkey可以以此格式记录测试结果。 激活魅力非常简单:


 docker run -it -v $(pwd)/tests:/tests -w /tests lamoda/gonkey -tests cases/ -host host.docker.internal:8080 -allure 

该报告将放置在当前工作目录的allure-results子目录中(这就是我指定-w / tests的原因)。


当将gonkey连接为库时,通过设置其他环境变量GONKEY_ALLURE_DIR激活“魅力”报告:


 GONKEY_ALLURE_DIR="tests/allure-results" go test ./… 

记录在文件中的测试结果通过以下命令转换为交互式报告:


 allure generate allure serve 

该报告如下所示:
图片


结论


在以下文章中,我将详细介绍gonkey中的fixtures的使用以及使用模拟模仿其他服务的响应。


我邀请您在项目中尝试使用gonkey ,参与其开发(欢迎池请求!),或者如果该项目将来对您有用,请在github上用星号标记。

Source: https://habr.com/ru/post/zh-CN463301/


All Articles