百忙中抽空上了兩節講組合測試 (Combinatorial Testing) 的課。這應該是一種比較新潮的軟件測試方法,嘗試用組合數學來測試軟件。主要是透過對測試對象的輸入建模,列出不同的輸入組合,以作為測試程序的數據。以下一例︰
這是在 Windows 7 中 mspaint 的 control panel。我們先著眼於 Shapes 的部份。這裏有多於 21 個不同的圖形,每個圖形還有 7 種不同的 outline 以及 7 種填滿的方法。如果要完整測試 (Complete Testing) 這個控制項,那就要有 21 x 7 x 7 = 1029 個組合需要測試。看來不算太多,也許花費的測試時間也不是很多。假如以上每一個控制項都要接受相同待遇的話,這個小小的 control panel 也就要有上萬個 test cases 才能叫完整測試,哪來這麼多的時間?﹗組合測試就是計對這個問題的其中一個解決方案。組合測試是嘗試用最少的輸入組合,來覆蓋盡可能多的不同輸入子組合。
用一個比較生活化的例子說︰現在有一群男女參加 Speed Dating,他們分做了 n 個小組。主辦單位每次會從每個小組中抽出一位參與宴會,而且希望每一位小組成員跟 n-1 組中的每位小組成員見面最少一次,但為了節省開支,主辦單位希望用最少的宴會次數來達成這個目標。可以怎麼解決呢?
這裏所講的是 two pair-wise testing,要求任兩個函數可能輸入最少要在解的其中一個組合中出現。這個情況下是很容易解決的,但當問題改為任三個函數的可能輸入時,問題就好像變得棘手了。也許是太久沒有接觸 Algorithm 的關係,一下子想不到很好的 Algorithm 去解決這個問題。而且現實情況下還會加上一些 constraints。套用 mspaint 的例子,當用戶選擇了 select 的時候,他就不可以用圖形功能。又例如,我在做的一個 feature,一封 message 的 recipients + attachments + contents 不能大於某個 size (eg. 25 MB),不然就告訴你我不能把它寄出。這裏 recipient, attachment, content 的 size 就是函數,在計算組合時就會有這個 constraint 在了。
組合測試最顯著的優點就是用最少的測試組合,達到理想的 Code coverage (又一次證明高 code coverage 不代表甚麼)。個人覺得最理想的應用是在測試 API, UI 上。通常可以在 Unit 或 Component level 的測試上運用。不過 Combinatorial Testing 真的是很有趣,善用組合數學來提高測試質素,一定要再深入研究一下這個範疇。
參考︰
- http://en.wikipedia.org/wiki/All-pairs_testing
- http://www.combinatorialtesting.com/
- http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.41.373&rep=rep1&type=pdf
後記︰忽然又湧起了那份解難的興趣出來,也許我是一個喜歡想解決方法,但又不喜歡做應用的人。簡稱懶人。XD

個人偏好 behavioral testing 那套,大約是 test, spec 合一,這就不去直接考慮 coverage 的問題
對 behavioral testing 比較陌生,可以簡介一下嗎?
其實 coverage 的主要用途只是讓你 revisit 所有的 test cases,
看看是否有些 scenario 遺漏了, 不必太著緊高低.
很多時候都是很低 coverage 的,因為有太多的 error/exception handler
但沒有太大的意義去 test 這些 handlers
我會再多講一些 combinatorial testing 的變化, 也許你會愛上它
哦, Behavior Testing 都係 Test Driven Development 一支,都係寫 code 先寫 test, 而 test 亦等同 spec。所以出發點放係段 code ge behavior 度 (咪即係 scenario..),而唔係 test 特定 ge method。
例如我想整個 url parser 去 parse google code 段 url, support SVN 但又唔 support Mercurial。可能寫一堆 test 先:
-it_should_able_to_obtain_the_repo_username_when_a_normal_svn_url_is_provided(){ #test_code_here }
-it_should_able_to_obtain_the_repo_username_when_a_normal_svn_url_is_provided_even_there_is_a_trailing_slash_in_the_url(){ #test_code_here }
-it_should_throw_an_exception_when_a_mercurial_url_is_provided() { #test_code_here }
…
大約就係咁