準則: 布林和界限的測試觀念
測試觀念的基礎是表面上看起來可信的軟體錯誤,以及如何以最佳方式來彰顯這些錯誤。這個準則說明如何開發布林表示式和界限條件的測試觀念。
關係
相關元素
主要說明

簡介

測試觀念是以錯誤模型為基礎,也就是哪些錯誤在軟體中是表面可信的,以及彰顯這些錯誤的最佳方式是什麼。這個準則說明如何從布林和關聯表示式建立測試觀念。它首先是查看程式碼來引發技術,之後,再說明如果程式碼尚未寫好或無法使用,要如何套用這些技術。

布林表示式

請設想下列程式碼片段,它取自想像中的系統,用來管理炸彈的引爆。它是安全系統的一部分,會控制是否按照「引爆炸彈」按鈕行動。

if (publicIsClear || technicianClear) {
    bomb.detonate();
}

程式碼錯誤。|| 應該是 &&. 這個錯誤會有不當影響。它不是在清除炸彈專家和大眾之後引爆炸彈,而是只要清除了其中任何一項,系統就會引爆。

什麼測試可以找出這個錯誤?

請設想在清除了專家和大眾之時按下按鈕的測試。程式碼可讓炸彈引爆。不過,這正是正確的程式碼(使用 && 的程式碼)將執行的動作。因此,在找出這項錯誤上,這個測試沒用。

同樣地,當專家和大眾都在炸彈旁時,這個不正確的程式碼也會有正確的行為:不引爆炸彈。

如果要找出這個錯誤,您必須有一個案例,以不同於應該寫成之程式碼的方式來評估已寫成的程式碼。例如,必須已清除大眾,但炸彈專家仍在炸彈旁。以下是所有測試的表格形式:

publicIsClear

technicianClear

寫成的程式碼...

正確的程式碼將是...

 

true

true

引爆

已引爆

測試沒用(對這項錯誤而言)

true

false

引爆

未引爆

測試有用

false

true

引爆

未引爆

測試有用

false

false

不引爆

未引爆

測試沒用(對這項錯誤而言)


在尋找這個特定錯誤時,這兩個中間測試很有幫助。不過,請注意,它們是多餘的:因為任何一項都能找到錯誤,您不需要同時執行兩者。

表示式還有其他可能的錯誤方式。以下是兩份布林表示式中的常見錯誤清單。左側的錯誤全由這裡所討論的技術來擷取。右側的錯誤就不一定。因此,這項技術不會擷取到我們想擷取的所有錯誤,不過,它仍有用。

偵測到的錯誤

可能不會偵測到的錯誤

使用錯誤的運算子:a || b 應該是 a&& b 使用錯誤的變數:a&&b&&c 應該是 a&& x&&d
否定句省略或不正確:a||b 應該是 !a||b 或 ! a||b 應該是 a||b 表示式太簡單:a&&b 應該是 a&&b&&c
括弧中的表示式未正確配置:a&&b||c 應該是 a&&(b||c) 左側直欄的表示式含有多項錯誤
表示式太複雜:a&&b&&c 應該是 a&&b
(這個錯誤比較不會出現,但其他原因所能使用的測試很容易找到它。)
 

如何使用這些觀念?假設您有一個 Boolean 表示式,例如 a&&!b. 您可以建構類似下列中的真值表:

a

b

a&&!b
(寫成的程式碼)

它可能應該是
a||!b

它可能應該是
!a&&!b

它可能應該是
a&&b

...

true

true

false

true

false

true

...

true

false

true

true

false

false

...

false

true

false

false

false

false

...

false

false

false

true

true

false

...


如果您辛苦走過所有可能性,您會發現第一、第二和第四個可能性便是所需要的全部。第三個表示式不會找到其他表示式所無法找到的錯誤,因此,您可以忽略它。(當這些表示式越來越複雜時,因案例並非必要而省略的情況也會快速增加。)

當然,任何心智正常的人都不會建立這樣一份表格。很幸運地,您也不需要這麼做。簡單表示式所需要的案例很容易記住。(請參閱下一節。) 至於更複雜的表示式,例如 A&&B||C,請參閱 AND 和 OR 混合的測試構想,其中針對含有兩個或三個運算子的表示式提出測試構想。 如果是更複雜的表示式,便可以利用程式來產生測試觀念。

簡單布林表示式的表格

如果表示式是 A&&B,測試方式如下:

A

B

true

true

true

false

false

true


如果表示式是 A||B,測試方式如下:

A

B

true

false

false

true

false

false


如果表示式是 A1 && A2 && ... && An,測試方式如下:

A1、A2、... 和 An 全部是 true

A1 是 false,其餘全部是 true

A2 是 false,其餘全部是 true

...

An 是 false,其餘全部是 true


如果表示式是 A1 || A2 || ... || An,測試方式如下:

A1、A2、... 和 An 全部是 false

A1 是 true,其餘全部是 false

A2 是 true,其餘全部是 false

...

An 是 true,其餘全部是 false


如果表示式是 A ,測試方式如下:

A

true

false


因此,在需要測試 a&&!b 時,您可以應用上述第一個表格,反轉 b 的意義(因為被否定),即可得到下列的測試構想

  • A true, B false
  • A true, B true
  • A false, B false

關聯表示式

以下是另一個含有錯誤的程式碼範例:

if (finished < required) {
    siren.sound();
}

< 應該是 a <=. 這些錯誤很常見。如同布林表示式,您可以建構一份測試值表格,查看哪些偵測到錯誤:

finished

required

寫成的程式碼...

正確的程式碼將是...

1

5

發出警報

已發出警報

5

5

不發出警報

已發出警報

5

1

不發出警報

未發出警報


一般而言,每當 finished=required 時,都能偵測到錯誤。分析表面可信的錯誤,我們可以得到這些測試觀念的規則:

如果表示式是 A<B 或 A>=B,測試方式如下:

A=B

A 稍微小於 B


如果表示式是 A>B 或 A<=B,測試方式如下:

A=B

A 稍微大於 B


「稍微」是什麼意思?如果 A 和 B 都是整數,A 應該比 B 大 1 或小 1。如果它們是浮點數,A 應該是非常接近 B 的數字。(它不必然是最接近 B 的浮點數。)

組合布林和關聯表示式的規則

大部分關係運算子都如同這個範例,發生在布林表示式內:

if (finished < required) {
    siren.sound();
}

關聯表示式的規則會導出下列測試觀念:

  1. finished 等於 required
  2. finished 稍微小於這個表示式:required

布林表示式的規則會導出下列結果:

  1. finished < required 應該是 true
  2. finished < required 應該是 false

也許這個表示式:finished 稍微小於這個表示式:required, finished < required 是 true,因此,後者沒有意義。

如果這個表示式是 false,則沒有意義:finished 等於 required, finished < required .

如果關聯表示式並未包含任何布林運算子(&&||),請忽略它也是一個布林表示式的事實。

當布林運算子和關係運算子結合起來時,事情會變成有點複雜,例如:

if (count<5 || always) {
   siren.sound();
}

從關聯表示式,您會取得:

  • count 稍微小於 5
  • count 等於 5

從布林表示式,您會取得:

  • count<5 true, always false
  • count<5 false, always true
  • count<5 false, always false

這些可以組合成三個更明確的測試觀念。(在這裡,請注意 count 是整數。)

  1. count=4, always false
  2. count=5, always true
  3. count=5, always false

請注意,count=5 使用兩次。最好使用一次,以允許使用其他值 - 畢竟,這個表示式為何測試 5 次: count ? 用 5 試一次,再用其他值試一次,來得出 false 結果,會不會比較好? (範例:count<5 這是比較好,但危險。因為很容易出錯。假設您試過下列各項:

  1. count=4, always false
  2. count=5, always true
  3. count<5 false, always false

假設下列值只能捕捉到一個錯誤:count=5. 這表示 5 在表示式中會產生 "false"。 count<5,而正確的程式碼會產生 true。 不過,此 false 值立即以 always 的值 (true) 進行 OR 運算。這表示整個表示式的值正確,即使關聯式子表示式的值錯誤,也是如此。這個錯誤會繼續存在,不被彰顯。

如果是另一個較不明確的 count=5,這個錯誤就不會避開彰顯而繼續存在。

當關聯表示式在布林運算子的右側時,也會出現類似的問題。

由於很難知道是哪些子表示式必須確切,哪些子表示式可以籠統,因此,它們最好全都確切。替代方式是使用上面提及的布林表示式程式。它會針對任意混合的布林和關聯表示式產生正確的測試觀念。

不含程式碼的測試觀念

概念:測試先於設計所說明,在實作程式碼之前,最好先設計測試。因此,雖然這些技術是程式碼範例所激發的,但通常是在沒有程式碼的情況下套用它們。怎麼做?

特定設計構件(如狀態圖和序列圖)會利用布林表示式來作為警戒。 這些情況都很簡單,只要將布林表示式的測試觀念加到構件的測試觀念核對清單中就行了。請參閱工作成果準則:狀態圖和活動圖的測試觀念

隱含而不明確的布林表示式比較不好處理。在 API 的說明中,便往往如此。以下是範例。請設想這個方法:

List matchList(Directory d1, Directory d1,
       FilenameFilter excluder);

這個方法的行為說明可能如下:

傳回出現在兩個目錄中的所有檔案的絕對路徑名稱清單。子目錄是遞降的。[...] 傳回的清單中會排除符合 excluder 的檔案名稱。excluder 只適用於最上層目錄,不適用於子目錄中的檔案名稱。

"and"、"or" 等字不會出現。但檔案名稱何時併入傳回清單呢? 當它出現在第一個目錄中,出現在第二個目錄中,它是在較低層的目錄中,並未明確排除它之時。就在下列程式碼中:

if (appearsInFirst && appearsInSecond &&
    (inLowerLevel || !excluded)) {
  add to list
}

以下是這個表示式的測試觀念,以表格形式來呈現:

appearsInFirst

appearsInSecond

inLower

excluded

true

true

false

true

true

true

false

false

true

true

true

true

true

false

false

false

false

true

false

false


如果要從文字中發現隱含的布林表示式,一般方法是先列出所說明的動作(如「傳回相符的名稱」)。之後,再撰寫一個布林表示式來說明採取動作的情況。請從所有表示式中衍生測試觀念。

這個流程有可能不一致。例如,一個人可能會寫下上面所用的布林表示式。另一人可能會說實際上有兩個不同的動作:首先是程式探查相符的名稱,之後,再過濾它們。因此,並不只有一個表示式,應該有兩個:

探查相符項目:
發生於檔案是在第一個目錄第二個目錄也有同名檔案之時
過濾相符項目:
發生在相符檔案是在最上層名稱符合 excluder 之時

這些不同的方法可導出不同的測試觀念,因而也會有不同的測試。但這些差異很可能並不特別重要。也就是說,浪費時間來擔心哪個表示式正確,再嘗試替代項目,不如將這些時間用在其他技術上,產生更多測試。如果您對差異的種類感到好奇,請繼續閱讀。

第二人會取得兩組測試觀念。

關於探查相符項目的測試觀念:

  • 檔案在第一個目錄中,檔案不在第二個目錄中 (true, true)
  • 檔案在第一個目錄中,檔案不在第二個目錄中 (true, false)
  • 檔案不在第一個目錄中,檔案在第二個目錄中 (false, true)

關於過濾相符項目的測試觀念(探查相符名稱之後):

  • 相符檔案在最上層,名稱符合 excluder (true, true)
  • 相符檔案在最上層,名稱不符合 excluder (true, false)
  • 相符檔案在較下層,名稱符合 excluder (false, true)

假設這兩組測試觀念結合起來。只有當檔案同時在兩個目錄時,第二組的觀念才有意義,因此,它們只能結合第一組的第一個觀念。這為我們帶來下列各項:

第一個目錄中的檔案

第二個目錄中的檔案

最上層

符合 excluder

true

true

true

true

true

true

true

false

true

true

false

true


那份表格中並沒有關於探查相符項目的兩個測試觀念。我們可以依照下列方式來加入它們:

第一個目錄中的檔案

第二個目錄中的檔案

最上層

符合 excluder

true

true

true

true

true

true

true

false

true

true

false

true

true

false

-

-

false

true

-

-


空白的資料格表示這些直欄不相干。

現在,這份表格看起來很像第一人的表格。使用相同的專有名詞可以強調這種類似性。第一人的表格有一個稱為 "inLower" 的直欄,第二人的表格有一個稱為 "in top level" 的直欄。翻轉值的意義可以轉換它們。這麼做,便得出第二份表格的這個版本:

appearsInFirst

appearsInSecond

inLower

excluded

true

true

false

true

true

true

false

false

true

true

true

true

true

false

-

-

false

true

-

-


前三列與第一人的表格相同。後兩列的不同之處,只在於這個版本不指定第一個所指定的值。結果便得出程式碼撰寫方式的假設。第一個假設複雜的布林表示式:

if (appearsInFirst && appearsInSecond &&
    (inLowerLevel || !excluded)) {
  add to list
}

第二個假設巢狀布林表示式:

if (appearsInFirst && appearsInSecond) {
    // found match.
    if (inTopLevel && excluded) {
// filter it
    }
}     

這兩者不同之處,在於第一個的測試觀念偵測到兩個錯誤,但第二個的觀念則沒有,因為這些錯誤不適用。

  1. 在第一個實作中,括弧中會出錯。括住 || 的括弧正確或不正確? 由於第二個實作沒有括弧,也沒有 ||,因此,錯誤不可能存在。
  2. 第一項實作的測試需求檢查第二個表示式 && 是否應該是 ||。在第二項實作中,隱含的 && 取代了明確的 &&。本身並沒有 ||-for-&& 錯誤。(有可能是巢狀不正確,但這項技術不處理這個問題。)