为Go语言web项目做集成测试

之前的工作中使用 rails 及其生态中的工具写web项目,而如今使用Go语言开发,最怀念的当数 ActiveRecord的灵活性和 RSpec的语义化测试代码。

在Go语言中,为了保证项目质量,必然要写测试,简单的单元测试还好,一旦涉及到大量的HTTP请求编码解码,外加数不清的SQL查询,就会让人不想写测试。为了解决上述两个问题,我做了如下尝试:

  1. Go语言中的JSON编码解码,使用 map[string]interface{} 代替预定义的 struct,如此虽可以少定义一些 struct,但各种 type assertion 会让代码变得丑陋不堪。

  2. ORM 可以帮我们方便做数据库SQL,ActiveRecord 是纯OO模型,很多其他语言的ORM也从中做了很多借鉴。由于从语法层面上的限制,Go语言的ORM库基本上都是通过 reflect 拿到表结构,整个写下来看,最多算是提供了一些链式调用的工具吧。

  3. Go语言设计之初注定不会提供像动态语言式一样的语法糖和灵活性,所以我尝试了 qlang,用它写了一些类似于 rspec 功能的代码,没能达到预期效果,虽然能够和Go语言代码互调,但是其灵活性和ruby差太多,这么比有点过分,毕竟人家 qlang 主要是为Go嵌入式脚本语言设计的。

  4. 好了,那咱能不能在go语言中用ruby的语法呢?也是有的,goby,但还在茁壮成长,换句话说就是还不成熟,而且人家并不是要写一个ruby编译器,而是想借鉴ruby的语法然后利用Go语言的并发优势以构建高性能微服务。虽然和qlang一样可以与Go语言交互,但并非适合直接运行ActiveRecord/RSpec 库,也不适合造轮子,那它造,就像拿着一个勺子捞丸子,能捞是能捞,但是费劲。

做了以上尝试和思考,我发现既想使用ActiveRecord/RSpec式的工具,又能和Go语言交互,基本不可能。那就放弃和Go语言交互,只做集成测试。若只想测试HTTP,大可不必使用 ActiveRecord/RSpec,Postman 就够用了,但要是还想检查一下数据库的数据,这就得用 ActiveRecord 了。取舍之后,重新定义我们的目标:

  1. 基于ActiveRecord构建Model。

  2. 最终要测试的是整个逻辑处理层HTTP接口的正确性,包括参数的解析,数据的处理与入库,返回的数据。

  3. 测试代码运行的过程中生成可读化的文档,已达到对接口及其对数据库的影响如实的体现出来。

经过一番调研,基于ActiveRecord构建Model完全可以用工具实现。下面就是如何在尽可能少干扰原rspec测试代码的情况下生成文档了。

最终做出了一个 rspec-doc gem

看个例子:

测试目标 POST http://localhost:6666/dog -d '{"name": "foo", "age": 2}'

require 'mysql2'
require 'active_record'
require 'rspec-doc'

ActiveRecord::Base.establish_connection(
  adapter: 'mysql2',
  host: 'http://127.0.0.1',
  port: 3306,
  database: 'my_app',
  username: 'root',
  password: 'secret'
)

class Dog < ActiveRecord::Base
end

RSpec.describe Dog do
  describe 'create' do
    it 'find the inserted dog' do |example|
      resp = RSpecDoc::RestClient.post(example,
        'http://localhost:6666/dog',
        {
          "name": "foo",
          "age": 2  
        }.to_json,
        {
          content_type: :json
        })
      expect(resp.code).to eq 200
 
      dog = Dog.find_by_name 'foo'
      RSpecDoc::ActiveRecord.describe(example, dog) do
        expect(dog.name).to eq 'foo'
        expect(dog.age).to eq 2        
      end
    end
  end
end

然后运行 rspec dog_spec.rb --require 'rspec-doc' --format RSpecDoc::MarkdownFormatter

如果服务正常,则会输出如下:

## Dog

### create
  - find the inserted dog

#### Api Request

    POST http://localhost:6666/dog -d '{"name": "foo", "age": 2}'
    
    **Request Headers**

    | Key | Value |
    | :--- |:--- |
    | Content-Type | json |
       
    **Request Body**

    ```json
    {
        "name": "foo",
        "age": 2
    }
    ```

    **Response Headers**

    | Key | Value |
    | :--- |:--- |
    | Access-Control-Allow-Origin | * |
    | Content-Type | application/json; charset=UTF-8 |
    | Vary | Origin |
    | Date | Tue, 10 Oct 2017 09:22:26 GMT |
    | Content-Length | 105 |

    **Response Body**

    ```json
    {
        "resultCode": "OK",
        "resultDescription": "成功添加",
        "data": null
    }
    ```

    #### Database Assertion

    **dogs**[id=1]

    | Column | Assertion | Value |
    | :--- |:--- |:--- |
    | name | == | "foo" |

Finished in 0.01727 seconds (files took 0.76396 seconds to load)

这个例子比较简单,却可以看到:

  1. RSpecDoc::RestClient 包装了 rest-client,提供了 HTTP 请求服务,在请求前后生成文档。

  2. RSpecDoc::ActiveRecorddescribe方法,只要把想要expect的代码原封不动的包进去即可生成文档。

  3. RSpecDoc::MarkdownFormatterRSpec结果转化为Markdown。

虽然看起来没什么,但规划良好的测试代码可以做到:

  1. 被测试项目不需要改动。
  2. 由于ruby语言的灵活性,代码本身有很高的可读性。
  3. 以业务为主轴,结构清晰,各部门同事都能看懂。
  4. 真实记录HTTP请求,不存在代码与文档不一致问题。
  5. 数据库真实测试,却能利用 ActiveRecord 不需要写任何 SQL,甚至不需要定义相关方法。

当然,不同于普通的rails项目,集成测试要的可读性,所以尽可能用直观的 HashSymbol、ActiveRecord 链式调用,减少抽象方法的使用。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容