測試驅動開發(TDD)—— 資料庫查詢篇

lxping發表於2019-09-03

在用 TDD 方式進行程式設計了一段時間後,我想應該分享一些使用心得體會。

查詢

資料庫查詢,是我們從資料庫中篩選出一些符合特徵記錄的標準。這就相當於,我們搭垂直電梯,在告知電梯我們要前往哪個樓層後,它就會將我們載到對應的樓層。但是同樣的,我們其實也應該給一些反面的例子,比如超重了的話,它就不會正確的工作,甚至會出現意外的情況,這就很像生產事故了。

那麼對於 TDD 來說,當我們校驗這個查詢是否有效的時候,我們應該新增一些反面的例子。否則你是不會留意到 select * fromselect id from where 1 = 1 的區別。所以基本上利用 TDD 開發模式的話,都是首先提出一個簡單的例項,再提出一個反例,再通過反例來完善查詢本身。

就比如說我們希望獲取一個文章列表。那麼我們從最簡單的查詢開始:

INSERT INTO posts (id) VALUES (1);

同樣,我們利用 PDO 或者 ORM 編寫查詢:

App\Posts::all();

在測試中,你可以驗證這個查詢是否確實載入了這篇文章。這時我往資料庫中新增另一條記錄,以證明查詢能夠返回多個物件(即沒有 limit 或者任何其他的條件)。

INSERT INTO posts (id) VALUES (1);
INSERT INTO posts (id) VALUES (2);

實現第一個業務

對於這個業務來說,我們需要一些釋出的文章,然而我們尚未考慮這個的實現,所以為了證明這條查詢並未實現要求,我們新增一個反例,一篇未釋出的文章:

-- 釋出的文章
INSERT INTO posts (id, status) VALUES (1, 'published');
INSERT INTO posts (id, status) VALUES (2, 'published');

-- 草稿文章
INSERT INTO posts (id, status) VALUES (3, 'draft');

第一次 Red

這個時候再次執行測試,會發現這次是失敗的,因為它返回來了一個草稿狀態的文章。這個就是 TDD 的 Red 階段。

接著修改我們的查詢,給它新增 where

App\Posts::where('status', 'published')->get();

重新執行測試,再次進入 Green 階段。

循序漸進

對於一個資訊流來說,我們往往希望文章是根據釋出時間逆序排列的,所以新增個釋出時間。

-- 釋出的文章
INSERT INTO posts (id, status, published_at) VALUES (1, 'published', '2019-09-02');
INSERT INTO posts (id, status, published_at) VALUES (2, 'published', '2019-09-03');

-- 草稿文章
INSERT INTO posts (id, status, published_at) VALUES (3, 'draft', '');

很顯然,執行測試必然是失敗的,所以我們修改查詢:

App\Posts::where('status', 'published')
    ->orderBy('published_at', 'desc')
    ->get();

如此重複, 對於每個新的需求,首先提出一個反例推翻現階段所實現的,從而促使整個業務開發是遵循 Green - Red - (Refactor) 的 TDD 迴圈。

繁瑣嗎?

這種開發模式繁瑣嗎?很繁瑣,筆者一開始也很不適應,但是它能很有效地幫助我們建立一個安全可靠的查詢,因為這個查詢是你一步一步羅列下來的。

就像單元測試一樣,斷言每個結果,確保每個功能能正確的輸出。

有用嗎?

筆者運維過十年前的程式碼,當一個複雜的業務邏輯,你不知道為什麼這樣查詢的時候,你也就無法確保在修改查詢後,會引發另一種問題,當然這個也經常為什麼出現生產事故的原因。

同樣,TDD 的過程後,其實你有一個很好的起點可以進行修改,清楚地瞭解其中的內容以及反例。

相關文章