概念: 測試構想型錄
「測試構想型錄」會列出 最有可能用來找出大部分可能發生的軟體錯誤之測試構想。本準則說明如何建立測試構想。
關係
相關元素
主要說明

簡介

大部分的程式設計都會不斷重複採用已經用過的東西,並且在不同的環境定義中又再重新使用。那些東西通常都具有特定的類別資料結構(例如鏈結清單、雜湊表或是關聯式 資料庫)或作 業(例如搜尋、排序、建立暫存檔或蹦現瀏覽器視窗)。例如,兩個客戶關聯式資料庫都會有許多舊有的特性存在。

有關這些老生常談的有趣事情是,它們都有千篇一律的錯 誤存在。人們不會創造一些想像的新方式,將不正確的東西加入雙重鏈結的清單中。他們都傾向於犯自己和別人都已經犯過的相同錯誤。開發蹦現瀏覽器視窗的程式設計師,很可能會犯下列其中一個老生常談的錯誤:

  • 在應該重複使用已經開啟的視窗時,建立新的視窗
  • 無法使被遮掩或最小化的瀏覽器視窗清楚可見
  • 在使用者已經選擇要使用別的預設瀏覽器時使用 Internet Explorer
  • 未檢查是否已啟用 JavaScript

由於錯誤都是千篇一律,因此用來尋找錯誤的測試 構想也都是千篇一律。請將這些測試構想加入您的測試構想型錄中,以便重複使用。

測試構想型錄如何尋找錯誤

型錄的優點之一是同一個測試構想可以用來找出多個基礎錯誤。以下是一個構想可以找出兩個錯誤的範例。

第一個錯誤是在 C 編譯器中。這個編譯器會採用諸如 "-table"、"-trace" 或 "-nolink" 等指令行選項。選項可以縮寫為其最小的唯一形式。例如,"-ta" 等於 "-table"。不過,"-t" 則不被接受,因為極不明確:它可以代表 "-table" 或是 "-trace"。

在內部作業中,指令行選項會儲存在類似如下的表格內:

-table
-trace
-nolink

當在指令行中遇到選項時,程式就會在表格中尋找該選項。如果該選項是任意表格項目的字首時,就是符合項目;亦即,"-t" 符合 "-table"。在找到相符項目之後,會搜尋表格的其餘部分,看看是否有其他相符項目存在。如果有其他相符項目就是錯誤,因為這代表語義不明確。

執行搜尋的程式碼類似如下:

for (first=0; first < size; first++)
{
  if (matches(entry[first], thing_sought))
  { /* at least one match */
    for(dup=first+1; dup < size; dup++) /* search for another */
    if (matches(entry[dup], thing_sought)) /* extra match */
      break; /* error out */
    return first;
  }
}
return -1; /* Not found or ambiguity */

您看得出來問題嗎?這很微妙。

問題出在 break 陳述式。這個陳述式的目的是要在發現重複項目時,脫離到包含迴圈的最外層,但是它卻只脫離內層的迴圈而已。這就和沒有找到第二個相符項目一樣:傳回第一個相符項目的索引。

請注意,如果要尋找相符項目的選項在表格中出現兩個相符項目時,才可能找到這個錯誤,如 "-t"。

現在我們來看看第二個完全不同的錯誤。

這個程式碼會採用字串。它的本意是要將字串中的最後一個 '=' 取代為 '+'。如果沒有 '=' 存在,就什麼事都不用做。此程式碼使用標準的 C 程式庫常式 strchr. 以下是此程式碼:

    ptr = strchr(string, '=');  /* Find last = */ if (ptr != NULL_CHAR)     *ptr = '+';

這個問題也相當微妙。

函數 strchr 會傳回字串中的第一個相符項目,而不是最後一個。正確的函數是 strrchr. 這個問題最可能出在打字錯誤 (事實上,最根本的基礎問題是在標準程式庫中儲存兩個只有拼字不同的函數,並不是很明智的事情)。

如果輸入中有兩個以上的等號時,才有可能發現這個錯誤。亦即:

  • "a=b" 會傳回正確的結果 "a+b"。
  • "noequals" 會傳回正確的結果 "noequals"。
  • "a=b=c" 會傳回不正確的 "a+b=c",而非正確的 "a=b+c"

這裡的有趣和有用現象是我們可以用相同的測試構想(尋找發生兩次的項目),來找出兩個具有完全不同的主要原因(打字錯誤,誤解 C 的結構),以及程式碼中的不同表示方式(呼叫錯誤的函數,誤用 break 陳述式)的錯誤。

優良的測試構想型錄

什麼是優良的型錄?

  • 其中包含少量的測試構想,可以用來找出大量的基礎錯誤。
  • 可以快速閱讀(瞄過)。您可以略過和您的情況無關的測試構想。
  • 其中只包含您會使用的測試構想。例如,不會處理 Web 瀏覽器的人,就不需要老是要略過適用於使用 Web 瀏覽器的程式之測試構想。處理遊戲軟體的人員需要的型錄,要比處理與安全攸關的軟體人員的型錄短。遊戲人員可以只專注於最有可能會找出錯誤的測試構想。

基於以上這些規則的考量,最好是有多套型錄。某些資料和作業適用於所有程式設計,因此最好將它們的測試構想都放在所有程式設計師可以使用的型錄中。但有些資料和作業 則只適用於特定的領域,因此它們的測試構想就可以放在領域專用的測試構想型錄中。

在以下範例中使用的範例型錄取得 Adobe Reader) 是一個理想的開始點。混合 AND 和 OR 的測試構想則提供另一個範例。

使用測試構想型錄範例

以下是範例型錄的可能用法。假設您要實作此方法: 

    void applyToCommonFiles(Directory d1,         Directory d2,         Operation op);

applyToCommonFiles 使用兩個目錄作為引數。當第一個目錄中的檔案和第二個目錄中的檔案同名時,applyToCommonFiles 就會針對那兩個檔案執行一些作業。它會降級子目錄。

型錄的使用方法是掃描其中,以找出和您的情況相符的主要標頭。看看每個標題下的測試構想是否相關,並將相關項目寫入測試構想清單

注意:本逐步說明可能會使使用型錄看起來好像很費事。實際建立核對清單的時間,要比閱讀關於如何建立核對清單快許多。

因此,以 applyToCommonFiles 而言,您可以依據本節的其餘部分說明的方式,來套用型錄。

第一個項目是針對任何物件。其中的任意引數可以是空值指標嗎? 這是介於 applyToCommonFiles 及其呼叫者之間的契約關係。其合約可能是呼叫端不會傳入空值指標。如果呼叫端傳入空值指標時,就不能依賴預期的行為:applyToCommonFiles 可能會執行任何動作。在此情況下,任何測試都不適當,因為不論 applyToCommonFiles 做什麼都是錯誤的。不過,applyToCommonFiles 卻需要檢查空值指標,這時測試構想就可以派上用場。我們假設是後面的情況,因此我們就可以有以下的起始「測試構想清單」:

  • d1 是空值(錯誤情況)
  • d2 是空值(錯誤情況)
  • op 是空值(錯誤情況)

下一個型錄項目是字串。檔案名稱是字串,並且會比對檔案名稱,看看是否相符。測試構想為空字串 ("") 時,沒有多大用途。假設將會使用一些標準字串比較常式,並且那些常式會正確處理空字串。

不過,等等... 如果要比較字串,那要比較大小寫嗎?假設 d1 包含一支檔案,其檔名為 "File"。d2 也包含一個檔名為 "File" 的檔案。這裡要比較這些檔案嗎?在 UNIX 上,是絕不需要。但在 Microsoft® Windows® 上時,就一定要比較。這卻是另一個測試構想:

  • 在兩個目錄中的檔案相符,但是檔案名稱大小寫不同。

請注意,這個測試構想並非直接來自型錄。不過,型錄會將我們的注意力移到 特定的程式層面(檔案名稱為字串),因此我們的創造力就讓我們產生一個額外的構想。很重要的一點是型錄的用途不要設得太窄,請將型錄當作是集體構思的技術,一種啟發新構想的方式。

下一個型錄項目是集合。目錄是檔案的集合。許多處理集合的程式都在集合是空白時失敗。少數可以處理空集合,或具有許多元素的集合的程式,則是在集合中只有一個元素時失敗。因此下列構想會很有幫助:

  • d1 空白
  • d2 空白
  • d1 只有一支檔案
  • d2 只有一支檔案

下一個構想是使用一個具有最大可能大小的集合。applyToCommonFiles 通常會用在較小的目錄。然後會有一些使用者出現,並且將它們套用在兩個具有數以千計檔案的巨大目錄樹中,然後才發現程式出現嚴重的記憶體不足,因此無法處理該實際情況。

這個時候測試目錄的絕對大小上限並不是最重要的事情;目錄大小只需要和使用者要嘗試的大小相當即可。不過,至少要在目錄中至少有 3 個以上檔案的情況下,進行一些測試。

  • d1 包含大量檔案
  • d2 包含大量檔案

最後的測試構想(重複元素)不適用於檔案目錄。也就是說,如果您的目錄中有兩個同名的檔案存在,您的問題就與 applyToCommonFiles 無關,因為這是您的檔案系統毀損了。

下一個型錄項目是搜尋。這些構想可以轉換為類似如下的 applyToCommonFiles 術語:

  • d1 和 d2 沒有相同的檔案(所有名稱都各不相同)
  • d1 和 d2 只有一個相同的檔案(這是目錄中按字母順序的最後一個元素)
  • d1 和 d2 具有多個相同的檔案

最後的測試構想會檢查 applyToCommonFiles. 程式在找到第一個相符項目時,是否立刻傳回結果?在測試構想的這個陳述式之前的括弧註解中,假設程式會使用傳回結果的檔案庫常式,提取目錄中的檔案清單,並依字母順序排列。否則,則最好是使用最後一個項目作為相符項目。在您花上大把時間去瞭解檔案如何排序之前,先問問自己如果將相符元素擺在最後時,是否會有助於找出問題。如果程式碼明確地使用索引來搜尋集合時,將元素擺在集合的最後會很有幫助。如果是使用反覆元,次序就比較無關緊要。

現在我們再來看看範例型錄中的另一個項目。鍊結結構項目指出我們是 在比較目錄樹,而不只是比較檔案的平面集合。決定如何測試 applyToCommonFiles 使我們正視其說明的不完整。

如果目錄結構是類似如下:

目錄結構圖

圖 1:目錄結構

applyToCommonFiles 降級為目錄 Cdir? 這樣做似乎沒有任何意義。其他目錄樹中可能沒有相符的項目存在。事實上,情況似乎是如果子目錄名稱相符時,子目錄中的檔案才會相符。亦即,假設我們有如下的目錄結構:

次要目錄結構圖

圖 2:次要目錄結構

名稱為 "File" 的檔案不相符,因為那些檔案是在不同的子目錄中。 若在兩個位置 (d1 和 d2) d1 d2. 這會帶出下列測試構想:

  • 在 d1 中的某些子目錄不存在 d2 中(不降級)
  • 在 d2 中的某些子目錄不存在 d1 中(不降級)
  • 某些子目錄同時出現在 d1 和 d2 中(降級)

不過,這出現了另一個疑問。作業 (op) 要套用至相符的子目錄,或只要套用至相符的檔案?如果套用至子目錄,這是要在降級之前或之後套用? 這兩者會有差異存在,例如,如果作業會刪除相符的檔案或目錄。因此,可以容許作業修改目錄結構嗎? 更明確地說:什麼是 applyToCommonFiles 的正確行為?(這是和反覆元會出現的問題相同)。

這些問題通常是在您仔細閱讀建立測試構想的方法說明時,一一浮現。不過,我們暫且將其放在一邊。不論回答為何,一定都會有最適合的測試構想,來檢查程式碼是否正確地實作回答。

現在讓我們回到型錄。我們到目前並沒有考慮到它的所有測試構想。 第一個測試構想 - 空白(結構是空的),詢問空目錄。我們已經從集合項目取得這個答案。我們也取得最小非空白結構,這是具有單一元素的目錄。這種重複現象並非不尋常,但卻很容易被忽略。

循環結構呢?目錄結構不可能是循環的吧,目錄不會位在它的後代或在其本身內吧,可能嗎?那捷徑(Windows)或符號鏈結(UNIX)呢?如果有捷徑在 d1 的目錄樹中往回指向 d1,則 applyToCommonFiles 應該不斷遞減嗎?這個問題的回答會導向一或多個新的測試構想:

  • d1 因為捷徑或符號鏈結而變成循環的
  • d2 因為捷徑或符號鏈結而變成循環的

視正確的行為為何,可能會有比上面更多的測試構想存在。

最後,如果深度超過一層呢?之前的測試構想會確保我們的測試會進入子目錄的下一個層次,但是我們也應該檢查 applyToCommonFiles 是否會繼續降級:

  • 降級 d1 子目錄的數個層次 (>1)
  • 降級 d2 子目錄的數個層次 (>1)

建立及維護您自己的測試構想型錄

如先前所提到的,通用型錄不會包含您需要的所有測試構想。 但是建立領 域特有型錄的公司,卻沒有將其對公司以外發佈。因此如果您需要這些型錄,您就需要自己建立。以下是一些建議。

  • 不要在型錄中填入您自己認為有助於尋找問題的揣測構想。請記得,您在型錄中加入的每一個測試構想都會耗費時間和金錢:
    • 您用於維護型錄的時間
    • 其他程式設計人員花在瞭解您的測試構想的時間
    • 其他程式設計人員可能用於進行測試的時間

    只加入已經有實際追蹤記錄的構想。您要能指出測試構想至少會擷取到一個實際的錯誤。理想的情況是,所找出的問題應該是其他測試遺漏的問題,也就是說,從現場回報的問題。建立型錄的一個有用方法,是先瀏覽貴公司的錯誤資料庫,並探詢如何及早發現每一項錯誤的各種問題。


  • 如果您只能利用閒暇時間來建立和維護測試構想型錄,這項工將很難行得通。您需要分配一些專門的時間給這項作業,就像任何其他重要工作一樣。 我們建議您要利用活動: 改進測試資產期間,建立及維護您的測試構想型錄。