使用Docker和Golang進行便捷的MongoDB測試
Docker的使用場景之一就是測試,在測試中,我們有時候會由于超時或者僅僅因為兩個開發版本使用相同的數據庫在同時運行而導致測試出錯。本文以Golang和MongoDB為例,介紹了如何使用Docker來簡化和改進單元測試。
背景
我們正在不斷尋找新技術來解決開發中遇到的問題。我們一直在使用Java+Spring,然而Java 8和Spring Boot為我們帶來了新的生機,并改變了單一的Java應用為微服務模式(譯者注:monolithic Java applications)。而當你有API的時候,你只需一個合適的前端框架就可以替代jsp和jQuery:在我們的案例中我們選擇 AngularJS。兩年前我們第一次使用了Angular,現在所有的項目都引入了AngularJS。
超過10年的Java 在你的靈魂深處留下了深刻的印記
這兩三年來,我一直在尋找更好的東西。在這個行業中,最好的事情是你會有各種選擇。我們曾經使用NodeJS構建了幾個項目,并學習了Ruby的服務配置 管理框架Chef。當然我們也有一些Scala項目,并了解過Clojure、Haskell和Rust語言,后來我們發現了 Go。雖然我們只使用Go語言編寫了幾個小服務,但是卻對與Go相關的語言、標準庫、工具和社區而震驚。有大量的博客文章解釋了為什么不同的公司選擇了Go,本文不再贅述。同時如果你想學習如何編寫Go,可以查閱 A tour of Go,如果你喜歡閱讀請查看 Effective Go或觀看 A tour of Go視頻。
負載測試
可能我需要相當長的篇幅來介紹負載測試,所有的編程語言都需要編寫單元測試代碼,另外還有一些需要使用TDD方法和達到100%測試覆蓋率的目標的方法。 動態語言需要安排更多類型的測試,可能當你經過了上百次的測試,你的應用才能達到一個穩定的狀態。痛苦的是,由于有不同的開發語言,所以你的測試需要很多 的準備工作:曾經幾秒鐘就可以完成的事情,那現在可能會需要幾分鐘,甚至是幾十分鐘才能完成。因此,你要開始倉庫(數據庫)的調用,并建立集成測試的數據 庫開發的預載和清除方法。有時候集成測試可能會失敗,而這可能是由于超時或者僅僅因為兩個開發版本使用相同的數據庫在同時運行。
使用Golang和Docker進行的測試
Golang不會有類似的問題,有了Golang的快速構建、測試周期和一些Docker魔法的支持,你能夠在幾秒內啟動MongoDB Docker容器并運行所有的測試。這個真的是從開始到結束只需要幾秒的時間,但是第一次運行除外,因為第一次運行的時候需要下載和提供MongoDB Docker容器。
我從這里得到真正的靈感,即使一直在尋找借口來確認這是否是正確的:
讓我們做一些可以進行Docker實驗的nice的事情
我已經研究Golang+AngularJS一段時間了,而且現在是最佳時間來證明Docker是否如宣傳的一樣神奇。對于OS X用戶來說,涉及到Docker時會有個小煩惱:它只在Linux上面運行。是的,你可以運用Boot2Docker來安裝到OS X上,而Boot2Docker將在虛擬化的Linux上運行Docker。我已經通過Ubuntu來使用Vagrant作為開發環境,因此我剛剛在這上 面安裝了Docker。
首先,我要熟悉Camlistore的實施環境并且復制它。特別感謝 Brad Fitzpartick,你通過Camlistore和Golang標準程序庫來完成了出色的工作。 Thanks!
可以通過 story_test.go來找到實際測試。對于那些看不懂Golang的用戶,我已經在最重要的代碼部分添加了額外的注釋。
Setup test environment func TestStoryCreateAndGet(t *testing.T) {// Start MongoDB Docker container // // One of the most powerful features in Golang // is the ability to return multiple values from functions. // In this we get: // - containerID (type=ContainerID struct) // - ip (type=string) containerID, ip := dockertest.SetupMongoContainer(t)
// defer schedules KillRemove(t) function call to run immediatelly // when TestStoryCreateAndGet(t) function is done, // so you can place resource clenup code close to resource allocation defer containerID.KillRemove(t)
app := AppContext{}
// Connect to Dockerized MongoDB mongoSession, err := mgo.Dial(ip)
// Golang favors visible first hand error handling. // Main idea is that Errors are not exceptional so you should handle them if err != nil { Error.Printf("MongoDB connection failed, with address '%s'.", Configuration.MongoUrl) }
// close MongoDB connections when we're finished defer mongoSession.Close()
app.mongoSession = mongoSession
// create test http server with applications route configuration ts := httptest.NewServer(app.createRoutes()) defer ts.Close()
storyId := testCreate(ts, t) // run create test testGet(ts, storyId, t) // run get test for created story }</pre>
Post json document to http handler func testCreate(ts httptest.Server, t testing.T) string {postData := strings.NewReader("{\"text\":\"teksti?\",\"subjectId\":\"k2j34\",\"subjectUrl\":\"www.fi/k2j34\"}")
// create http POST with postData JSON res, err := http.Post(ts.URL+"/story", applicationJSON, postData)
// read http response body data data, err := ioutil.ReadAll(res.Body) res.Body.Close() if err != nil { t.Error(err) }
id := string(data)
// verify that we got correct http status code if res.StatusCode != http.StatusCreated { t.Fatalf("Non-expected status code: %v\n\tbody: %v, data:%s\n", http.StatusCreated, res.StatusCode, id) }
// verify that we got valid lenght response data if res.ContentLength != 5 { t.Fatalf("Non-expected content length: %v != %v\n", res.ContentLength, 5) } return id }</pre>
Test that previously created story exists func testGet(ts httptest.Server, storyId string, t testing.T) {// create http GET request with correct path res, err := http.Get(ts.URL + "/story/" + storyId) data, err := ioutil.ReadAll(res.Body) res.Body.Close() if err != nil { t.Error(err) }
body := string(data)
// validate status code if res.StatusCode != http.StatusOK { t.Fatalf("Non-expected status code: %v\n\tbody: %v, data:%s\n", http.StatusCreated, res.StatusCode, body) }
// validate that response has correct storyId if !strings.Contains(body, "{\"storyId\":\""+storyId+"\",") { t.Fatalf("Non-expected body content: %v", body) }
// validate that content leght is what is should be if res.ContentLength < 163 && res.ContentLength > 165 { t.Fatalf("Non-expected content length: %v < %v, content:\n%v\n", res.ContentLength, 160, body) }
}</pre>
眼見為實
因此,啟動MongoDB Docker容器,將它配置到應用程序,然后用內置的測試直至創建HTTP服務器。然后,我們設置同樣的路由給服務器,并對測試服務器運行兩個請求,第一 個來創建故事評論,另外一個來獲取它。所有的數據都被存儲了,并且從MongoDB中獲取。那么所有這一切需要多久時間呢?
僅兩秒以下!
即使你運行一些條件選擇器它仍只需要不到3秒 \o/
Docker是針對所有的用戶,而不僅僅是Golang用戶
對于那些可以使用Golang的用戶,Docker也可以幫助你。它當然沒有Golang那么快速,但是和使用外部的MongoDB服務器一樣的 快,而且沒有額外的清理麻煩。毫無疑問,Docker是虛擬化業務中的游戲變化者,并且這些炒作也得到了很好的回報。這樣就沒有借口來針對MongoDB 功能編寫任何模擬測試。
原文鏈接:Painless MongoDB testing with Docker and Golang(翻譯:吳錦晟 校對:李穎杰)
來自:http://dockerone.com/article/141