超簡單的變化測試

來源: MSDN  發布時間: 2011-09-07 13:22  閱讀: 1712 次  推薦: 0   原文鏈接   [收藏]  

  我認識的很多測試人員都聽說過變化測試,卻很少有人執行過該測試。 變化測試以難度大、需要昂貴的第三方軟件工具而聞名。 但是,在本月專欄中我將為您展示如何用 C# 和 Visual Studio 創建一個超簡單(不到 2 頁代碼,用不了 4 個小時)的變化測試系統。 簡單的變化測試系統能讓您用很少的時間和精力,就獲得成熟的變化測試系統所提供的大部分好處。

  變化測試是一種評估一組測試用例有效性的方法。 其原理很簡單。 假設您有 100 個測試用例,并且被測系統 (SUT) 通過了這 100 個測試。 如果您改變 SUT,例如把“>”更改為“<”或把“+”更改為“-”,則有可能將一個錯誤引入到了 SUT。 現在,如果您重新運行這 100 個測試用例,則可以預見至少有一個測試用例會失敗,表明其中一個測試用例捕獲了錯誤代碼。 但是,如果未看到測試失敗,則很可能是您的這組測試用例錯過了錯誤代碼,未能徹底測試 SUT。

  若要了解我將要介紹的內容,最好是看一下圖 1。

圖 1 變化測試運行演示

  該示例中的 SUT 是一個名為 MathLib.dll 的庫。 我在這里介紹的方法可用于測試大部分的 Microsoft .NET Framework 系統,包括 DLL、WinForms 應用程序、ASP.NET Web 應用程序等。 變化系統首先掃描 SUT 的原始源代碼,尋找可進行更改的候選代碼。 我的超簡單系統只搜索“<”和“>”運算符。 測試系統設置為創建并評估兩處變化。 在實際的工作中,您可能會創建上百甚至上千個變化。 第一處變化會隨機選擇并更改一個運算符,本示例中,是 SUT 源代碼中字符位置 189 上的“>”運算符,并將此符號更改為“<”。 接下來,構建變化 DLL 源代碼以創建變化的 MathLb.dll 庫。 然后,變化系統調用變化的 SUT 上的一組測試用例,并將結果記錄到文件中。 第二次迭代會以同樣的方法創建和測試第二處變化。 日志文件的結果如下所示:

=============
Number failures =0
Number test case failures =0 indicates possible weak test suite!
=============
Number failures =3
This is good.
=============

  第一個變化未生成任何測試用例失敗,這說明您應該檢查 189 位置上的源代碼并確定為何沒有測試用例檢查該代碼。

  SUT

  我的超簡單變化測試演示包含 3 個 Visual Studio 項目。 第一個項目包含 SUT,在本例中是一個名為 MathLib 的 C# 類庫。 第二個項目是一個可執行的測試工具,在本例中是一個名為 TestMutation 的 C# 控制臺應用程序。 第三個項目創建和構建變化,在本例中是一個名為 Mutation 的 C# 控制臺應用程序。 為方便起見,我將三個項目放置到一個名為 MutationTesting 的目錄中。 在變化測試中要跟蹤很多文件和文件夾,您不應忽視妥善組織它們所面臨的難度。 對于此演示,我使用了 Visual Studio 2008(可使用任何版本的 Visual Studio)來創建虛擬的 MathLib 類庫。 圖 2 顯示了該虛擬 SUT 的完整源代碼。

  圖 2 虛擬 SUT 的完整源代碼

using System;
namespace MathLib
{

publicclass Class1
{

publicstaticdouble TriMin(double x, double y, double z)
{

if (x < y)
return x;
elseif (z > y)
return y;
else
return z;
}
}
}

  請注意,我保留了默認類名稱 Class1。 該類包含一個靜態方法 TriMin,它返回三個 Double 參數中最小的一個。 還要注意,該 SUT 有意設計的不正確。 例如:如果 x = 2.0、y = 3.0 并且 z = 1.0,該 TriMin 方法會返回值 2.0 而不是正確值 1.0。 但是,請務必注意,變化測試并不 直接衡量 SUT 的正確性,而是衡量一組測試用例的有效性。 構建 SUT 后,下一步就是將源文件 Class1.cs 的一個基準副本保存到變化測試系統的根目錄中。 之所以這么做是因為,每次變化都會對 SUT 的原始源代碼進行一次修改,因此必須保留一份 SUT 原始源代碼。 在本示例中,我將原始源代碼保存在 C:\MutationTesting\Mutation 中,名為 Class1-Original.cs。

  測試工具

  在某些測試中,您可能會有一組現成的測試用例數據,在另一些測試中,您可能會有現成的測試工具。 對于這個超簡單變化測試系統,我創建了一個名為 TestMutation 的 C# 控制臺應用程序測試工具。 在 Visual Studio 中創建該項目后,我添加一個對 SUT 的引用。MathLib.dll 位于 C:\MutationTesting\MathLib\bin\Debug。 圖 3 顯示了測試工具項目的完整源代碼。

  圖 3 測試工具和測試數據

using System;
using System.IO;

namespace TestMutation
{

class Program
{

staticvoid Main(string[] args)
{

string[] testCaseData =newstring[]
{
"1.0, 2.0, 3.0, 1.0",
"4.0, 5.0, 6.0, 4.0",
"7.0, 8.0, 9.0, 7.0"};

int numFail =0;

for (int i =0; i < testCaseData.Length; ++i) {
string[] tokens = testCaseData[i].Split(',');
double x =double.Parse(tokens[0]);
double y =double.Parse(tokens[1]);
double z =double.Parse(tokens[2]);
double expected =double.Parse(tokens[3]);

double actual = MathLib.Class1.TriMin(x, y, z);
if (actual != expected) ++numFail;
}

FileStream ofs
=new FileStream("..
\\..
\\logFile.txt
",
FileMode.Append);
StreamWriter sw
=new StreamWriter(ofs);
sw.WriteLine(
"=============");
sw.WriteLine(
"Number failures = "+ numFail);
if (numFail ==0)
sw.WriteLine(

"Number test case failures = "+
"0 indicates possible weak test suite!");
elseif (numFail >0)
sw.WriteLine(
"This is good.");
sw.Close(); ofs.Close();
}
}
}

  您將看到,測試工具有三個硬編碼的測試用例。 在實際工作中,您可能會有數百個測試用例存儲在文本文件中,并將文件名作為 args[0] 傳遞到 Main 中。 在第一個測試用例中,“1.0, 2.0, 3.0, 1.0,”代表 x、y 和 z 參數(1.0、2.0 和 3.0),后面是 SUT 的 TriMin 方法的預期結果 (1.0)。 很明顯,測試集是不充分的:這三個測試用例中的每一個基本上都是等效的,并且將最小值作為 x 參數。 但是,如果您研究原始的 SUT,您會發現事實上三個測試用例都會通過。 我們的變化測試系統能夠檢測出這個測試集的缺陷嗎?

  測試工具迭代每個測試用例,分析輸入參數和預期返回值,使用輸入參數調用 SUT,提取實際返回值,將實際返回值和預期返回值進行比較,從而確定測試用例結果為通過還是失敗,然后累計測試用例失敗的總數量。 請注意,在變化測試中,我們主要關注是否至少有一個新的測試用例失敗,而不是有多少測試用例通過。 測試工具將日志文件寫入到調用程序的根文件夾中。

  變化測試系統

  在這部分中,我將逐行向您介紹變化測試程序,但是省略了圖 1 中所示的用于生產輸出的 WriteLine 語句的大部分內容。 我在 MutationTesting 根目錄中創建了一個名為 Mutation 的 C# 控制臺應用程序。 該程序的開頭如下所示:

using System;
using System.Collections.Generic;
using System.IO;
using System.Diagnostics;
using System.Threading;

namespace Mutation
{

class Program
{

static Random ran =new Random(2);
staticvoid Main(string[] args)
{

try
{
Console.WriteLine(
"\nBegin super-simple mutation testing demo\n");
...

  Random 對象的目的是生成一個隨機的變化位置。 我使用了一個種子值 2,但其實任意值都可以。 接下來,設置文件位置:

string originalSourceFile ="..
\\..
\\Class1-Original.cs
";
string mutatedSourceFile ="..
\\..
\\..
\\MathLib\\Class1.cs
";
string mutantProject ="..
\\..
\\..
\\MathLib\\MathLib.csproj
";
string testProject ="..
\\..
\\..
\\TestMutation\\TestMutation.csproj
";
string testExecutable =
"..
\\..
\\..
\\TestMutation\\bin\\Debug\\TestMutation.exe
";
string devenv ="C:\\Program Files (x86)\\Microsoft Visual Studio 9.0\\Common7\\IDE\\devenv.exe";
...

  稍后,您將看到如何使用每個文件。 請注意,我所指向的 devenv.exe 程序與 Visual Studio 2008 相關聯。 我沒有對該位置進行硬編碼,而是生成一個 devenv.exe 的副本,并放置到變化系統的根文件夾中。

  程序繼續:

List<int> positions = GetMutationPositions(originalSourceFile);
int numberMutants =2;
...

  我調用幫助程序 GetMutationPositions 方法來掃描原始源代碼文件并將所有的“<”和“>”字符的字符位置存儲到 List 中,然后將要創建和測試的變化數量設置為 2。

  主處理循環為:

for (int i =0; i < numberMutants; ++i) {
Console.WriteLine(
"Mutant # "+ i);
int randomPosition = positions[ran.Next(0, positions.Count)];
CreateMutantSource(originalSourceFile, randomPosition, mutatedSourceFile);


try {
BuildMutant(mutantProject, devenv);
BuildTestProject(testProject, devenv);
TestMutant(testExecutable);
}

catch {
Console.WriteLine(
"Invalid mutant.
Aborting.");
continue;
}
}
...

  在循環中,程序會提取字符的隨機位置以便從可能位置列表進行變化,然后調用幫助程序方法來生成變化的 Class1.cs 源代碼,構建相應的變化 MathLib.dll,重新構建測試工具以便使用新的變化,然后測試變化的 DLL,并希望會生成錯誤。 因為變化的源代碼很可能是無效的,所以我將構建和測試嘗試封裝在 try-catch 語句中,以便可以中止對不可構建的代碼的測試。

  Main 方法封裝為:

...
Console.WriteLine(
"\nMutation test run complete");
}

catch (Exception ex) {
Console.WriteLine(
"Fatal: "+ ex.Message);
}
}
// Main()

  創建變化源代碼

  獲取可能的變化位置列表的幫助程序方法是:

static List<int> GetMutationPositions(string originalSourceFile)
{
StreamReader sr
= File.OpenText(originalSourceFile);
int ch =0; int pos =0;
List
<int> list =new List<int>();
while ((ch = sr.Read()) !=-1) {
if ((char)ch =='>'|| (char)ch =='<')
list.Add(pos);

++pos;
}
sr.Close();

return list;
}

  該方法逐個字符匹配源代碼,尋找大于和小于運算符,并將字符位置添加到 List 集合中。 請注意,這里介紹的超簡單變化測試系統存在一項限制,即只能變化單字符的符號,如“>”或“+”,而不能變化多字符的符號,如“>=”。 實際用于變化 SUT 源代碼的幫助程序方法如圖 4 所示。

  圖 4 CreateMutantSource 方法

staticvoid CreateMutantSource(string originalSourceFile,
int mutatePosition, string mutatedSourceFile)
{
FileStream ifs
=new FileStream(originalSourceFile, FileMode.Open);
StreamReader sr
=new StreamReader(ifs);
FileStream ofs
=new FileStream(mutatedSourceFile, FileMode.Create);
StreamWriter sw
=new StreamWriter(ofs);
int currPos =0;
int currChar;

while ((currChar = sr.Read()) !=-1)
{

if (currPos == mutatePosition)
{

if ((char)currChar =='<') {
sw.Write(
'>');
}

elseif ((char)currChar =='>') {
sw.Write(
'<');
}

else sw.Write((char)currChar);
}

else
sw.Write((char)currChar);

++currPos;
}

sw.Close(); ofs.Close();
sr.Close(); ifs.Close();
}

  CreateMutantSource 方法接受原始的源代碼文件(已提前保存),還接受要變化的字符位置和生成的變化文件的名稱及保存位置。 此處,我僅檢查“<”和“>”字符,但您可能希望看一下其他變化。 通常,您希望變化會產生有效的源,因此您不能將“>”改成“=”。 此外,在多個位置上變化也是不可取的,因為這多個變化中可能只有一個變化會生成新的測試用例失敗,從而表明測試集有效,但其實并非如此。 一些變化沒有實用性(例如變化注釋里的字符),另一些變化會產生無效代碼(例如將移位運算符“>>””更改為“><“)。

  構建和測試變化

  BuildMutant 幫助程序方法是:

staticvoid BuildMutant(string mutantSolution, string devenv)
{
ProcessStartInfo psi
=
new ProcessStartInfo(devenv, mutantSolution +" /rebuild");
Process p
=new Process();

p.StartInfo
= psi; p.Start();
while (p.HasExited ==false) {
System.Threading.Thread.Sleep(
400);
Console.WriteLine(
"Waiting for mutant build to complete .
. "
);
}
p.Close();
}

  我使用 Process 對象來調用 devenv.exe 程序以重新構建 Visual Studio 解決方案,該方案中包含已變化的 Class1.cs 源代碼并產生 MathLib.dll 變化。 無需參數,devenv.exe 便可啟動 Visual Studio IDE,但是當參數傳遞后,devenv 即可用于重新構建項目或解決方案。 請注意,我使用了延時循環,每 400 毫秒暫停一次以便讓 devenv.exe 有時間完成構建變化 DLL;否則,變化系統會在變化 SUT 創建之前即嘗試測試它。

  用于重新構建測試工具的幫助程序方法是:

staticvoid BuildTestProject(string testProject, string devenv)
{
ProcessStartInfo psi
=
new ProcessStartInfo(devenv, testProject +" /rebuild");
Process p
=new Process();

p.StartInfo
= psi; p.Start();
while (p.HasExited ==false) {
System.Threading.Thread.Sleep(
500);
Console.WriteLine(
"Waiting for test project build to complete .
. "
);
}
p.Close();
}

  這里的主要思路是,通過重新構建測試項目,測試工具在執行時會使用新的變化 SUT 而不使用以前使用過的變化 SUT。 如果您的變化源代碼無效,則 BuildTestProject 將拋出異常。

  超簡單變化測試系統的最后一個部分是用于調用測試工具的幫助程序方法:

...
staticvoid TestMutant(string testExecutable)
{
ProcessStartInfo psi
=new ProcessStartInfo(testExecutable);
Process p
=new Process(); p.StartInfo = psi;
p.Start();

while (p.HasExited ==false)
System.Threading.Thread.Sleep(
200);

p.Close();
}

}
// class Program
} // ns Mutation  

  正如我前面談到的,測試工具使用硬編碼的日志文件名稱和位置,但您可以將其參數化,方法是將信息作為參數傳遞給 TestMutant,并放在 Process 的 StartInfo 中(在此,它可被 TestMutation.exe 測試工具接受)。

  實際應用的變化測試系統

  變化測試從原理上講并不復雜,但是創建一個成熟的變化測試系統需要注意的細節卻很有挑戰性。 然而,通過盡可能簡化變化測試系統,并利用 Visual Studio 和 devenv.exe,您可針對 .NET SUT 創建非常有效的變化測試系統。 使用我在此處介紹的示例,您應該可以創建自己的 SUT 變化測試系統。 示例變化測試系統的主要限制在于,由于該系統是基于單字符更改,因此,您不能輕松執行多字符運算符的變化,例如將“>=”更改為其求補運算符“<”。 另一個限制是,該系統僅為您提供變化的字符位置,不能讓您輕松診斷變化。 盡管存在這些限制,但該示例系統已成功運用到許多中型軟件系統中,用來衡量測試套件的有效性。

0
0
 
標簽:VS2010 測試
 
 

文章列表

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()