從前面三張圖可以看出,隨著CL值的減小與頻率的提升,性能是都有提升的。DDR2的時序活動空間不大,如果要穩定運行于CL3時序,頂級內存也只能降頻到800以下,常用的時序一般為4-4-4-12和5-5-5-15,而且從測試數據來看,DDR2內存,提升頻率帶來的性能提升比較明顯,同時,低時序帶來的提升也不可忽視。
MEM 333 CL5-5-5-15 4M PI截圖:
EVEREST內存測試截圖:
PCMARK05截圖:
3DMARK05截圖:
MEM 500 CL4-4-4-12 4M PI截圖:
EVEREST截圖:
PCMARK05截圖:
3DMARK05截圖:
應用軟件測試
應用軟件測試,用winrar來測試內存性能對實際日常使用帶來的影響,壓縮對象為XPSP2光碟里面的I386目錄,已復制到硬盤,體積為542M,采用標準壓縮,每測試一次后刪除壓縮目錄重啟一次。只采用了667CL5和1110CL5兩種此次測試極端性能模式進行對比。
測試成績為:當內存運行667CL5時,壓縮I386目錄用時3分30秒,而當運行于1110CL5時,只用了3分鐘,整整快了30秒,相當于節約了1/7的時間,再一次說明了選擇高性能內存的必要性。
專業軟件測試
專業軟件測試:采用的是有代表性的SPECViewperf 10,SPECviewperf 10是圖形工作站專業顯卡OpenGL測試程序,可以測試顯卡在多個CAD/DCC應用程序中的OpenGL性能,包括3ds max、CATIA、EnSight、Maya、Pro/ENGINEER、SolidWorks等,它的成績可以比較真實的反映顯卡在這些軟件中的運行速度。為了盡量減低CPU的影響,此次測試CPU更換為默認3.16G的E8500,顯卡為9600GT。
SPECViewperf 10測試項目中,內存從333CL5提升到555CL5,各項測試均有不同程度的提升,以3DSMAX-04提升比例最大,所有的測試項目都是比較具有代表性的,因此,在專業軟件應用上,提升內存性能對提高工作效率,縮短工作時間均是有不小幫助的。
柱狀對比圖:
內存333/CL5時的表現:
內存500/CL4的表現:
總結
無論從測試軟件、普通應用軟件還是從專業軟件的測試情況來看,選擇高品質內存,提升內存性能對于整機性能的提升具有極大的意義,象國際知名的G.SKILL、OCZ之類的頂級內存品牌,價格也不是貴得離譜,非常值得選購。
1 研究背景
隨著敏捷開發的流行,傳統的軟件測試也在發生著翻天覆地的變化。傳統的軟件測試已不能適應當前的開發方式,急需新的理論和方法論來尋求改變,并以此來推進軟件工程的進步。本文將關注與敏捷測試相關理論與技術。
1.1 敏捷技術方法與分析
我們現在面對著飛速變化的業務和技術環境。在這樣一個環境中,傳統的軟件開發方法所認為需求需要在項目初期分析清楚并且保持穩定的想法是行不通的。不能快速持續的將需求變化融合到軟件中就意味著對業務環境反映遲鈍,最終導致業務上的失敗。同樣,新技術不斷地涌現,也要求軟件產品的代碼時刻處于一種良好的狀態,能夠適應各種調整。于是,敏捷開發過程應運而生。
2001年以Kent Beck,Martin Fowler,Robert C.Martin及Ward Cunningham等為首的一些軟件工程的專家成立了“敏捷聯盟”(Agile Alliance),并提出了著名的敏捷宣言,即敏捷過程的價值觀:
? 人和交互重于過程和工具。
? 可以工作的軟件重于求全責備的文檔。
? 客戶合作重于合同談判。
? 隨時應對變化重于循規蹈矩。
這些價值觀是專家們在求同存異的基礎上對敏捷技術的最基本的總結,也是他們在敏捷技術方面達成的最大共識,其反映的是兩個更深層的特點:
1) 敏捷型方法是“適應性”而非“預見性”
工程方法試圖對一個軟件開發項目在很長的時間跨度內做出詳細的計劃, 然后依計劃進行開發。這類方法在一般情況下工作良好,但(需求、環境等) 有變化時就不太靈了。因此它們本質上是拒絕變化的。而敏捷型方法則歡迎變化。其實,它們的目的就是成為適應變化的過程,甚至能允許改變自身來適應變化。
2) 敏捷型方法是“面向人”的,而非“面向過程”的
工程型方法的目標是定義一個過程,不管是誰用都工作。而敏捷型方法 則認為沒有任何過程能代替開發組的技能,過程起的作用是對開發組的 工作提供支持。
敏捷聯盟還以這4個價值觀為原則,提出了敏捷過程的12條指導原則,以期能更好的指導人們了解敏捷過程。
敏捷開發過程,指的就是一種與傳統的瀑布模型開發和CMM(Capability Maturity Model,軟件開發的能力成熟度模型)所追求的嚴謹的文檔制度截然相反的開發過程。這一開發過程注重開發團隊和成員之間的關系而不是以開發的進程和使用的工具為重點,注重所開發的軟件產品而不是追求廣泛的文檔編制,注重開發過程中與客戶的協同工作而不是以簽訂合同的談判為工作的核心,注重在開發過程中隨時調整計劃而不是同意完全遵循某一開發計劃,以實現所謂開發過程的“敏捷”。
1.2 敏捷測試及其研究現狀
敏捷方法的發展,打破了傳統的瀑布開發模型,改變了整個軟件開發過程中的角色和定位。由于在敏捷開發運動的初期,主要依靠開發人員來進行推動。很多測試人員不了解敏捷方法,仍然習慣了按照傳統的瀑布模式進行軟件測試,即按照V模型所指導的步驟進行測試,保證軟件與需求、設計的相符合,但這樣很容易形成了一種測試思維的定勢。當“用戶需求不明確”、“需求變化較快”時,沿用傳統測試方法的測試人員將變的無所適從。
目前比較流行的敏捷測試方法有測試驅動開發和相關環境驅動測試等。還有很多國外知名專家按照“敏捷”的原理為軟件測試開發了相應的測試框架,其中最著名的就是Kent Beck等提出的xUnit系列單元測試框架和Ward Cunningham等提出的Framework for Integrated Test(FIT)集成測試框架。xUnit系列提出的比較早,目前已有一套完善的測試工具和方法論來支持了,適用于各種語言的單元測試。FIT框架是當前國內外的研究重點,很多知名的測試專家如Lisa Crispin等都在如何使用FIT進行有效的軟件測試方面得出了很多的研究成果。
1.3 基于接口參數的測試用例自動生成算法
在軟件測試工作中,由于輸入、輸出空間,特別是輸入空間的無限性,使得無法對軟件進行全面的測試。因此,如何從大量的輸入數據中挑選適量的具有代表性、典型性的數據,特別是怎樣用較少的測試用例對軟件進行較全面的測試是測試人員面臨的一大難題。
測試用例的選擇無論是對黑箱測試還是對白箱測試都起著關鍵的作用,決定著軟件測試的質量和效果。所謂測試用例選擇就是指從所有的可用測試用例中選出少量典型的測試用例,以達到對測試域的最大限度覆蓋。多年來,許多研究者對之進行了廣泛而深入的研究,并取得了許多研究成果。常用的基于接接口參數的黑箱測試用例選擇方法是對系統每個接口參數采用邊際值分析法和等價類劃分法等選取一組典型的值,然后在這些取值組合中隨機選取一組測試用例,或者使用一些啟發式方法從中進行篩選。但這些方法的缺點是帶有主觀傾向性,不具有普遍性。
2 基于敏捷測試的相關技術討論
2.1 FIT框架及應用
在敏捷開發過程中,軟件測試是至關重要的,尤其是在最為流行的敏捷開發過程:極限編程(XP)中顯的更為突出。誠然,所有的過程都提到測試,但一般都不怎么強調。可是XP將測試作為開發的基礎,要求每個程序員寫一段源碼時都得寫相應的測試碼。這些測試片段不斷地積累并被整合到系統中。這樣的過程會產生一個高度可靠的建造平臺,為進一步開發提供了良好的基礎。
但是,即使是單元測試工具JUnit也存在一些缺點:比如JUnit里要進行數據填充,但是數據經常改變,使維護工作變成了可怕的噩夢,測試不同的組合,需要不同的數據,這也許會使測試工作變得日益復雜。而目前的集成測試又缺乏有效的方法論,不能自動化,測試的質量比較依賴測試人員的水平。
Framework for Integrated Test(簡稱FIT)就是一個用于增強交流和協作的工具。FIT創建了一個在客戶和程序員之間的反饋循環。FIT讓客戶和測試人員可以使用諸如Microsoft Office之類的工具來給出程序應當如何表現的例子——而無需成為直接編碼的程序員。FIT自動針對實際的程序檢測那些例子,這樣就在業務世界和軟件工程世界之間建立了一個簡單而且有效的橋梁。
FIT給予了客戶和程序員一個關于軟件的精確交流的方法。客戶所給的具體的例子讓程序員能深刻理解將要構建的產品。程序員的對于裝置的工作和軟件可以讓客戶給出不同的例子進行試驗來獲取對于軟件如何真正工作更深入的了解。這樣通過一起工作,整個團隊可以學會更多關于產品的內容并產生更好的結果。
2.2 測試用例自動生成技術
正交試驗設計起源于科學試驗,它由田口玄一博士在1949年創立,并于60年代初從日本傳人中國。它應用依據Galois理論導出的正交表,從大量試驗條件中挑選出適量的、有代表性的條件來合理地安排試驗。運用這種方法安排的試驗具有“均勻分散、整齊可比”的特點。“均勻分散”性使試驗點均衡地分布在試驗范圍內,讓每個試驗點有充分的代表性;“整齊可比”性使試驗結果的分析十分方便,可以估計各因素對指標的影響,找出影響事物變化的主要因素。
但正交試驗設計仍然存在著一些有待解決的弊端:比如正交表難以構造,因素、水平過多時測試用例數目還是過多等。所以一些專家又提出一種基于對接口參數進行組合覆蓋的黑箱測試用例自動生成算法模型,據此來得到一個對所有接口參數進行兩兩組合覆蓋的測試用例表。這種方法有著類似正交試驗設計的特點,實際上,在特定情況下,這種算法模型得出的測試用例表就是正交表。
3 技術實現的考慮
3.1 基于FIT框架對軟件進行集成測試
使用基于FIT框架的開源FIT工具來實現真正的測試先行開發過程,并讓客戶、需求提報工程師、開發人員、以及測試人員進行協同工作,達到需求更精準、減少需求更改、測試數據與JUnit單元測試代碼分離的目的,讓這一切更簡潔、更易于維護。
將根據以下步驟進行研究:
1) 使用FIT框架進行實際項目測試的實踐,從中提煉出一套使用FIT框架進行集成測試的通用方法。
2) 通過實踐,對FIT框架進行合理的改進和拓展,結合JUnit單元測試,現實單元測試和集成測試的無縫連接,達到提高軟件質量的效果。
3) 在理論研究和實踐的基礎上,規約出適用于單元測試和集成測試的通用方法。
3.2 整合測試用例的自動生成技術至FIT
按照敏捷過程中“簡單”原則,本課題將編寫一個輔助接口測試的工具,用來自動產生少而有效的測試用例,以達到對測試域的最大限度覆蓋。通過該工具產生的測試用例表,能符合FIT框架的要求,并可被FIT所執行而得到HTML形式的可視化的測試結果。通過這種方式,大大增加了測試的自動化。
為了實現該目標,將按照以下步驟進行研究:
1) 查看“正交試驗設計方法”的原理及其資料,了解測試用例生成的規則。
2) 查閱兩兩覆蓋測試用例生成的相關算法,并根據算法用程序實現,進行實踐研究。
3) 根據實踐研究,對兩兩覆蓋測試用例進行改進,以期能更高效的實現測試用例的生成。
4) 修改依據改進后的算法實現的測試工具,使其輸入輸出符合FIT框架的要求。在此基礎上,把此工具集成到FIT框架中。
4 小結
本文討論了當前軟件測試中的兩大重要研究領域:敏捷測試方法和測試用例的選擇與生成技術。進一步的工作是,根據“敏捷”的集成測試框架FIT需要人工構造表格形式的數據作為輸入的前提,深入研究如何自動生成FIT需要的表格數據?再對FIT進行擴展,為FIT嵌入測試用例表格自動生成功能。其中測試用例集的生成將依據各參數兩兩覆蓋的原則,以求達到對測試域的最大限度覆蓋
軟件開發和使用的歷史已經留給了我們很多由于軟件缺陷而導致的巨大財力、物力損失的經驗教訓。這些經驗教訓迫使我們這些測試工程師們必須采取強有力的檢測措施來檢測未發現的隱藏的軟件缺陷。 生產軟件的最終目的是為了滿足客戶需求,我們以客戶需求作為評判軟件質量的標準,認為軟件缺陷( Software Bug )的具體含義包括下面幾個因素: ? 軟件未達到客戶需求的功能和性能; ? 軟件超出客戶需求的范圍; ? 軟件出現客戶需求不能容忍的錯誤; ? 軟件的使用未能符合客戶的習慣和工作環境。 考慮到設計等方面的因素,我們還可以認為軟件缺陷還可以包括軟件設計不符合規范,未能在特定的條件(資金、范圍等)達到最佳等。可惜的是,我們中的很多人更傾向于把軟件缺陷看成運行時出現問題上來,認為軟件測試僅限于程序提交之后。 在目前的國內環境下,我們幾乎看不到完整準確的客戶需求說明書,加以客戶的需求時時在變,追求完美的測試變得不太可能。因此作為一個優異的測試人員,追求軟件質量的完美固然是我們的宗旨,但是明確軟件測試現實與理想的差距,在軟件測試中學會取舍和讓步,對軟件測試是有百益而無一弊的。 下面是一些軟件測試的常識,對這些常識的理解和運用將有助于我們在進行軟件測試時能夠更好的把握軟件測試的尺度。 ? 測試是不完全的(測試不完全) 很顯然,由于軟件需求的不完整性、軟件邏輯路徑的組合性、輸入數據的大量性及結果多樣性等因素,哪怕是一個極其簡單的程序,要想窮盡所有邏輯路徑,所有輸入數據和驗證所有結果是非常困難的一件事情。我們舉一個簡單的例子,比如說求兩個整數的最大公約數。其輸入信息為兩個正整數。但是如果我們將整個正整數域的數字進行一番測試的話,從其數目的無限性我們便可證明是這樣的測試在實際生活中是行不通的,即便某一天我們能夠窮盡該程序,只怕我們乃至我們的子孫都早已作古了。為此作為軟件測試,我們一般采用等價類和邊界值分析等措施來進行實際的軟件測試,尋找最小用例集合成為我們精簡測試復雜性的一條必經之道。 ? 測試具有免疫性(軟件缺陷免疫性) 軟件缺陷與病毒一樣具有可怕的 “ 免疫性 ” ,測試人員對其采用的測試越多,其免疫能力就越強,尋找更多軟件缺陷就更加困難。由數學上的概率論我們可以推出這一結論。假設一個 50000 行的程序中有 500 個軟件缺陷并且這些軟件錯誤分布時均勻的,則每 100 行可以找到一個軟件缺陷。我們假設測試人員用某種方法花在查找軟件缺陷的精力為 X 小時 /100 行。照此推算,軟件存在 500 個缺陷時,我們查找一個軟件缺陷需要 X 小時,當軟件只存在 5 個錯誤時,我們每查找一個軟件缺陷需要 100X 小時。實踐證明,實際的測試過程比上面的假設更為苛刻,為此我們必須更換不同的測試方式和測試數據。該例子還說明了在軟件測試中采用單一的方法不能高效和完全的針對所有軟件缺陷,因此軟件測試應該盡可能的多采用多種途徑進行測試。 ? 測試是 “ 泛型概念 ” (全程測試) 我一直反對軟件測試僅存在于程序完成之后。如果單純的只將程序設計階段后的階段稱之為軟件測試的話,需求階段和設計階段的缺陷產生的放大效應會加大。這非常不利于保證軟件質量。需求缺陷、設計缺陷也是軟件缺陷,記住 “ 軟件缺陷具有生育能力 ” 。軟件測試應該跨越整個軟件開發流程。需求驗證(自檢)和設計驗證(自檢)也可以算作軟件測試(建議稱為:需求測試和設計測試)的一種。軟件測試應該是一個泛型概念,涵蓋整個軟件生命周期,這樣才能確保周期的每個階段禁得起考驗。同時測試本身也需要有第三者進行評估(信息系統審計和軟件工程監理),即測試本身也應當被測試,從而確保測試自身的可靠性和高效性。否則自身不正,難以服人。 另外還需指出的是軟件測試是提高軟件產品質量的必要條件而非充分條件,軟件測試是提高產品質量最直接、最快捷的手段,但決不是一個根本手段。 ? 80-20 原則 80% 的軟件缺陷常常生存在軟件 20% 的空間里。這個原則告訴我們,如果你想使軟件測試有效地話,記住常常光臨其高危多發 “ 地段 ” 。在那里發現軟件缺陷的可能性會大的多。這一原則對于軟件測試人員提高測試效率及缺陷發現率有著重大的意義。聰明的測試人員會根據這個原則很快找出較多的缺陷而愚蠢的測試人員卻仍在漫無目的地到處搜尋。 80-20 原則的另外一種情況是,我們在系統分析、系統設計、系統實現階段的復審,測試工作中能夠發現和避免 80% 的軟件缺陷,此后的系統測試能夠幫助我們找出剩余缺陷中的 80% ,最后的 5% 的軟件缺陷可能只有在系統交付使用后用戶經過大范圍、長時間使用后才會曝露出來。因為軟件測試只能夠保證盡可能多地發現軟件缺陷,卻無法保證能夠發現所有的軟件缺陷。 80-20 原則還能反映到軟件測試的自動化方面上來,實踐證明 80% 的軟件缺陷可以借助人工測試而發現, 20% 的軟件缺陷可以借助自動化測試能夠得以發現。由于這二者間具有交叉的部分,因此尚有 5% 左右的軟件缺陷需要通過其他方式進行發現和修正。 ? 為效益而測試 為什么我們要實施軟件測試,是為了提高項目的質量效益最終以提高項目的總體效益。為此我們不難得出我們在實施軟件測試應該掌握的度。軟件測試應該在軟件測試成本和軟件質量效益兩者間找到一個平衡點。這個平衡點就是我們在實施軟件測試時應該遵守的度。單方面的追求都必然損害軟件測試存在的價值和意義。一般說來,在軟件測試中我們應該盡量地保持軟件測試簡單性,切勿將軟件測試過度復雜化,拿物理學家愛因斯坦的話說就是: Keep it simple but not too simple 。 ? 缺陷的必然性 軟件測試中,由于錯誤的關聯性,并不是所有的軟件缺陷都能夠得以修復。某些軟件缺陷雖然能夠得以修復但在修復的過程中我們會難免引入新的軟件缺陷。很多軟件缺陷之間是相互矛盾的,一個矛盾的消失必然會引發另外一個矛盾的產生。比如我們在解決通用性的缺陷后往往會帶來執行效率上的缺陷。更何況在缺陷的修復過程中,我們常常還會受時間、成本等方面的限制因此無法有效、完整地修復所有的軟件缺陷。因此評估軟件缺陷的重要度、影響范圍,選擇一個折中的方案或是從非軟件的因素(比如提升硬件性能)考慮軟件缺陷成為我們在面對軟件缺陷時一個必須直面的事實。 ? 軟件測試必須有預期結果 沒有預期結果的測試是不可理喻的。軟件缺陷是經過對比而得出來的。這正如沒有標準無法進行度量一樣。如果我們事先不知道或是無法肯定預期的結果,我們必然無法了解測試正確性。這很容易然人感覺如盲人摸象一般,不少測試人員常常憑借自身的感覺去評判軟件缺陷的發生,其結果往往是把似是而非的東西作為正確的結果來判斷,因此常常出現誤測的現象。 ? 軟件測試的意義 - 事后分析 軟件測試的目的單單是發現缺陷這么簡單嗎?如果是 “ 是 ” 的話,我敢保證,類似的軟件缺陷在下一次新項目的軟件測試中還會發生。古語說得好, “ 不知道歷史的人必然會重蹈覆轍 ” 。沒有對軟件測試結果進行認真的分析,我們就無法了解缺陷發生的原因和應對措施,結果是我們不得不耗費的大量的人力和物力來再次查找軟件缺陷。很可惜,目前大多測試團隊都沒有意識到這一點,測試報告中缺乏測試結果分析這一環節。 結論: 軟件測試是一個需要 “ 自覺 ” 的過程,作為一個測試人員,遇事沉著,把持尺度,從根本上應對軟件測試有著正確的認識,希望本文對讀者對軟件測試的認識有所幫助 |
以下內容含腳本,或可能導致頁面不正常的代碼 |
---|
說明:上面顯示的是代碼內容。您可以先檢查過代碼沒問題,或修改之后再運行. |
1.jpg
2.jpg
3.jpg
4.jpg
5.jpg
6.jpg
7.jpg
8.jpg
9.jpg
10.jpg
基于模式的靜態代碼分析、運行時內存監測、單元測試以及數據流分析等軟件驗證技術是查找嵌入式C語言程序/軟件缺陷行之有效的方法。上述技術中的每一種都能查找出某一類特定的錯誤。即便如此,如果用戶僅采用上述技術中的一種或者幾種來進行驗證,這樣的驗證方法很有可能會漏過對程序中的一些缺陷的檢查。解決此類問題的一種安全和有效的策略就是同時使用上述軟件驗證中的所有互補技術。這樣就能建立起一個牢固的框架來幫助用戶檢查出可能會避開某種特定技術的缺陷。與此同時,用戶也自然地建立起一個能檢測出關鍵并且難以查找的功能性錯誤的環境。
本文將詳盡闡述基于模式的靜態代碼分析、運行時內存錯誤檢測、單元測試以及數據流分析等自動化技術共同使用時是如何查找出嵌入式C語言程序/軟件中的缺陷的。本文中將以Parasoft C++test為例來演示上述各項技術。C++teST是一個經廣泛的最佳實踐證明能提升軟件開發團隊開發效率以及軟件質量的自動化集成解決方案。
當讀者在閱讀本文以及任何時候思考查找到的缺陷時,關注文中的截圖是很重要的。自動化檢測例如內存崩潰和死鎖的缺陷,毫無疑問對任何開發團隊都是一項必不可少的任務。盡管如此,最致命的缺陷卻是功能性錯誤,這往往是難以自動發現的。在本文的結論部分我們將簡要地討論一下查找這些缺陷的技術。
情景簡介
為了給出一個具體的示例,我們將就一個我們最近遇到的案例來介紹以及演示我們所推薦的缺陷查找策略:一個運行在ARM 板上的簡單傳感器應用程序。
假設我們已經創建了該應用系統,但是當我們將程序上載到系統目標板上并試圖運行該程序時,我們沒有在LCD屏上看到所預期的輸出。
我們尚不明確系統不能正常工作的原因,因此我們設法對系統進行調試,但是在目標板上進行調試是一件耗時而且煩人的事。因為我們不得不手動分析調試器的結果并試圖人工判斷出問題的真正原因。或者我們使用一些被證實能自動定位出錯誤的工具或技術來幫助我們減輕負擔。
從這一點而言,我們要么期待使用調試器來調試程序能夠帶來好運,要么我們嘗試使用一種自動化的測試策略來查找代碼中所存在的錯誤。如果自動化技術仍然沒有幫助我們查找到錯誤,那么我們不得不回到使用調試器作為最后的辦法。
基于模式的靜態代碼分析
這里,我們假設僅在絕對必要的情況下才使用調試器進行調試,因此我們從運行基于模式的靜態代碼分析開始。它將查找到如下圖所示的問題:
這是違反了 MISRA 的一個規則,此違規說明該處的賦值運算符存在一些可疑情況。的確,編程者此處的本意是使用比較運算符而不是賦值運算符。因此我們將此處檢測到的沖突修改掉,并重新運行程序。
我們發現有了一些改善:一些輸出被顯示在了LCD屏上了。但是,由于一次訪問違規,程序崩潰掉了。因此我們需要再次地做出選擇。我們是應該使用調試器還是繼續使用自動化的錯誤檢測技術。由于經驗告訴我們自動化錯誤檢測技術能非常高效地檢查出我們當前程序所遇到的內存崩潰這類問題,因此我們決定使用運行時內存監測來查找問題。
整個程序的運行時內存監測
為了進行運行時內存監測,我們使用 C++test 來插裝應用程序。這樣的插裝是輕量級的,所以經過插裝后的程序適合在目標板上運行。當我們把程序上載到目標板上并運行經過插裝的程序后,我們將結果下載到PC上,如下的錯誤將被報告出來:
該結果指出在第48行代碼處產生了一次讀取數組越界的錯誤。顯然,msgIndex變量的值肯定超過了數組的范圍。如果我們隨著堆棧追蹤上一級的原因,我們將發現此處的打印信息所指示的值的確超出了數組的范圍(因為在調用printMessage()函數前我們給出了一個錯誤的條件)。我們可以刪除掉這個不必要的條件(value <= 20)以修改這個錯誤。
void handleSensorValue(int value)
{
initialize();
int index = -1;
if (value >= 0 && value <= 10) {
index = VALUE_LOW;
} else if ((value > 10) && (value <= 20)) {
index = VALUE_HIGH;
}
printMessage(index, value);
}
然后我們重新運行程序,將不會再報告任何內存錯誤。當我們把程序上載到目標板上時,它似乎如我們預期那么在工作了。盡管如此,我們仍然有一些擔心。
我們僅查找到我們所執行的代碼路徑中的一個內存寫溢出實例,我們憑什么能夠斷定我們尚未執行到的代碼就不會有內存寫溢出錯誤了呢?如果我們檢查覆蓋率分析,我們就會發現reportSensorFailure()這個函數從未被執行到。我們有必要對這個函數進行測試,但是具體如何進行呢?建立一個調用該函數的單元測試用例就是一個不錯的辦法。
在單元測試中使用運行時內存監測:我們使用C++test的測試用例向導來創建一個測試用例的框架,并向其中添加一些測試代碼。然后運行該測試用例——以檢查上面提到的未經測試的函數,同時打開運行時內存監測功能。使用C++teST,全過程大約只需要數秒鐘。結果標明該函數已經被覆蓋到了,但同時也查找到了新的錯誤:
我們的測試用例查找到了更多的內存相關錯誤。很顯然,當失敗處理函數被調用時,我們的內存初始化存在問題(空指針)。通過更進一步的分析,我們發現在reportSensorValue()函數中存在函數調用順序錯誤。finalize()函數先于printMessage()函數被調用,但是finalize()函數中釋放了printMessage()函數需要使用的內存。
void finalize()
{
if (messages) {
free(messages[0]);
free(messages[1]);
free(messages[2]);
}
free(messages);
}
將函數調用順序進行修改后,我們重新運行程序。
這樣我們就解決了上面報告中的第一個錯誤。現在我們再來分析報告中的第二個錯誤:即打印信息中的AccessViolatiONException。產生這個錯誤的原因是相應的消息列表未經初始化。為了解決該問題,我們在打印該信息前調用一次initialize()函數來對其進行初始化。經修改后的函數如下所示:
void reportSensorFailure()
{
initialize();
printMessage(ERROR, 0);
finalize();
}
當我們再次運行該測試用例時,僅有一個任務被報告出來:未經驗證的單元測試用例(an unvalidated unit test case),這其實并不算一條錯誤。我們只需對輸出進行一下驗證,以將該測試用例轉換為回歸測試。通過創建合適的斷言,C++test會自動為我們完成這些步驟。
接下來我們再次運行整個程序。覆蓋率分析告訴我們幾乎整個程序都已經被覆蓋到了,并且沒有發現任何內存錯誤。
這樣就結束了嗎?其實不然。雖然我們運行了整個程序并為未覆蓋到的函數創建了單元測試用例,但還是有一些路徑是沒有被覆蓋到的。我們仍然可以繼續創建單元測試用例,但是若指望通過這樣的方法來覆蓋程序中的所有路徑將耗費相當長的時間。或者我們使用另外的方法,使用數據流分析來對這些路徑進行模擬。
數據流分析
我們使用C++test的BugDetective來進行數據流分析,BugDetective能模擬系統中的不同路徑并檢查這些路徑中是否存在潛在的問題。進行數據流分析后,我們得到如下結果:
仔細分析報告的結果,我們發現程序中存在一條未被覆蓋到的潛在路徑可能會造成在finalize()函數中出現兩次free的操作。在程序中,reportSensorValue()函數調用了finalize()函數,然后finalize()函數調用了free()。同時,finalize()函數還會被mainLoop()函數調用。我們可以修改finalize()函數以使其更加智能化,從而修復這個問題,修改后的代碼如下:
void finalize()
{
if (messages) {
free(messages[0]);
free(messages[1]);
free(messages[2]);
free(messages);
messages = 0;
}
}
現在我們再次運行數據流分析,得到的結果將只有兩個問題:
這里我們可能使用了-1作為索引來訪問了數組。這是由于整型變量index被設置的初始值為-1,并且存在一條可能通過if語句的路徑在未將該整型變量正確的進行初始化之前便調用了printMessage()函數。運行時分析未檢查到這樣的一條路徑,并且該路徑很有可能在真實世界中永遠不可能被執行到。這就是靜態數據流分析相對于運真實運行時內存監測最主要的不足:數據流分析能檢查出潛在的路徑,這些路徑可能包含在程序實際執行過程中不會執行到或不存在的路徑。盡管如此,為了做到有備無患,我們刪除了上述的不必要的條件(value>=0)以修改這個潛在的錯誤。
void handleSensorValue(int value)
{
initialize();
int index = -1;
if (value <= 10) {
index = VALUE_LOW;
} else {
index = VALUE_HIGH;
}
printMessage(index, value);
}
相同地,我們也對最后一個報告的錯誤進行相應的處理。現在我們再次運行數據流分析,將不會再有錯誤被報告出來。
為了確保程序運行一切正常,我們重新運行整個分析過程。首先,我們開啟運行時內存監測并運行應用程序,一切表現正常。然后我們開啟內存監測并運行單元測試,一個任務被報告出來:
我們的單元測試檢測到reportSensorFailure()函數的行為已經發生了改變。這是由于我們已經對finalize()函數進行了修改——為了糾正之前報告的一個問題所做的修改。此處報告的任務是為了讓我們注意此修改,并提示我們應該對測試用例進行相應的審查,并且確定是否應該對代碼或者測試用例進行相應的修改,以表示這種新的行為實際上是我們所預期的行為。在檢查完代碼之后,我們發現后者(修改)是正確的并且應該更新斷言的正確條件。
/* CPPtest_TEST_CASE_BEGIN test_reportSensorFailure */
/* CPPTEST_TEST_CASE_CONTEXT void reportSensorFailure(void) */
void sensor_tests_test_reportSensorFailure()
{
/* Pre-condition initialization */
/* Initializing global variable messages */
{
messages = 0 ;
}
{
/* Tested function call */
reportSensorFailure();
/* Post-condition check */
CPPTEST_ASSERT(0 == ( messages ));
}
}
/* CPPTEST_TEST_CASE_END test_reportSensorFailure */
作為最終的確認,我們需要獨立地運行整個程序——在IDE中關閉掉運行時內存監測來對程序進行構建。結果顯示一切如我們所預期一樣運行。
總結
作為全文的結尾,讓我們一起對上述各個步驟進行一個鳥瞰式的總結。
首先,我們開發的程序并未如我么所預期那樣運行,我們不得不在兩種解決方法中選擇一種來查找程序中的錯誤:通過運行調試器或者使用自動錯誤檢測技術。
如果我們使用調試器運行代碼來查找錯誤,我們將會看到一些很奇怪的現象:程序中的一些變量總是被賦予了相同的值。基于這種現象我們不得不通過排除法來查找問題的原因——即在應該使用比較運算符的地方我們錯誤地使用了賦值運算符。而靜態代碼分析則能為我們自動地檢查出該邏輯錯誤。運行時內存分析是不可能檢查出這種錯誤的,因為這種錯誤與內存無關。數據流分析也很有可能找不到這類錯誤因為數據流分析僅僅是通過這些路徑而不會驗證這些條件的正確性。
當我們解決了這個問題后,程序可以運行了,但是仍然還有內存相關的問題。內存相關的問題是很難被調試器發現的;當用戶使用調試器調試程序時,用戶并不知道內存的實際大小。但是自動錯誤檢查工具能夠做到這點。因此,為了查找這些內存問題,我們將整個程序進行插裝,并使用運行時內存分析工具來運行程序。這樣我們就能知道到底是那一片內存發生了寫溢出錯誤。
盡管如此,在審查覆蓋率分析結果的時候,我們注意到在目標板上測試的時候,并不是全部代碼都被覆蓋到了。通過自動化的工具得到這樣的覆蓋率信息是簡單的,因為工具會自動地
跟蹤覆蓋率,但是,如果我們是通過調試器,就不得不判斷哪一部分程序經過了驗證。而這通常只能依靠我們人工記錄的方式來實現。
當工具提醒我們一些代碼未被覆蓋到時,我們決定改變單元測試來額外地增加我們測試執行的覆蓋率。這就揭示了程序中另外一些問題。在目標系統的正常測試中,覆蓋所有函數也許是不可能完成的任務,因為其中一些函數可能是硬件的失敗處理函數或僅在某些小概率的特定情況下才會被調用的函數。而對這些函數的測試對于一些注重安全性的程序而言又是至關重要的。試想在飛機上用來處理速度傳感器問題的程序中存在著代碼錯誤:我們會有系統崩潰的危險,而不是導致某個設備為非工作狀態。因此,通過創建單元測試用例來覆蓋這類型的執行路徑往往是對其進行有效測試的唯一方法。
接下來,我們修復了工具檢查到的所有問題,同時通過驗證相應的結果創建了一個回歸測試用例(作為報告的任務之一引導我們完成)。然后我們運行數據流分析來覆蓋在目標系統上即便使用單元測試也未執行到的路徑。在此之前,我們幾乎已經達到了100%的代碼行覆蓋率,但是我們的路徑覆蓋率卻未達到這個水平。BugDetective幫我們發現了這些方面的一些潛在問題。這些問題可能并沒有實際發生或者有可能永遠不會發生。也許在實際運行時,這些問題僅僅會在當其條件滿足的情況下才會出現,并且在現實生活中,這些條件可能永遠不可能滿足。盡管如此,我們不能保證隨著代碼的升級,應用程序不會執行到這些路徑。
安全起見,我們仍然修改了所報告的問題以排除任何可能影響它的實際應用執行的風險。在修改代碼的同時,我們同時也引入了回歸測試,當我們再次運行單元測試時立即被檢測到。在所有的自動化錯誤檢測方法中,回歸測試是唯一能夠幫助我們檢查到代碼是否發生了功能性的改變的方法,并且能驗證出對代碼進行的修改是否引入了功能性的錯誤以及不可預知的副作用。最后,我們修改了回歸測試套件,并重新測試代碼,發現一切運行正常。
正如讀者所見,我們使用的一切測試方法——基于模式的靜態代碼分析、內存分析、單元測試、數據流分析以及回歸測試——并不是相互競爭的關系,恰好相反,它們是一種互補的關系。將上述工具結合使用,它們就是一套具有強大作用的工具集,并為嵌入式C語言程序/軟件提供一個無可比擬的自動化錯誤檢測解決方案。
總而言之,通過自動地查找很多關于內存和其它編碼的缺陷,我們成功地讓程序運行起來了。盡管如此,值得注意的是,最危險的缺陷卻是實際的功能性錯誤:例如程序并未如所指定的要求運行。而不幸的是,這些錯誤往往是非常難以被發現的。
查找這類缺陷的最好的一個方式就是通過同行代碼審查來實現。即另指派至少一人來檢查代碼并且審查代碼與需求內容的一致性,這樣用戶就能對實際程序是否會如預期那樣運行有一個很好的*估。
另外一個十分有用的策略是圍繞代碼創建一個回歸測試套件,這能幫助用戶快捷地驗證代碼與規范的一致性。在本文所描述的示例情景中,單元測試被用來強制執行應用程序級的運行時內存監測所未覆蓋到的代碼:它能覆蓋到當前程序的功能性,在此之后,我們對代碼做了一些修改,它能提醒我們代碼出現的相應的功能性問題。事實上,這種單元測試用例應該被更早地創建起來:理想情況下,當用戶在實現程序的功能時就應該被創建起來。這樣,用戶就能得到更高的覆蓋率并同時構建起一個更強壯的“安全網”來捕捉關鍵的功能性改變。
Parasoft的C++test能幫助用戶完成這兩個任務:從自動化到管理同行代碼審查流程,以及幫助團隊創建,持續地運行并維護一個高效的回歸測試套件。
關于Parasoft C++test
Parasoft C++test是一個經廣泛的最佳實踐證明能提升軟件開發團隊開發效率以及軟件質量的自動化集成解決方案。C++test能進行諸如編碼策略增強、靜態代碼分析、運行時內存監測、自動同行代碼審查以及單元和組件測試,從而為軟件開發團隊提供一種更加實用的方法來確保其C以及C++程序能如所預期那樣工作。C++test可以用于在通用開發IDE下的桌面平臺中,以及在回歸測試時通過命令行以批處理模式的方式運行。同時,C++test還集成了Parasoft的報告系統,該系統能提供具有細分能力的基于Web 的儀表板,這使得開發團隊根據C++test的測試結果和其他的一些關鍵進程指標來更加方便地跟蹤項目的狀態和趨勢。
通過在宿主機上進行大量的測試以及在目標系統中進行的平滑的驗證,C++test能夠幫助軟件開發團隊減少花在嵌入式系統開發中的時間、精力以及成本。隨著代碼在宿主機上的構建,C++test的自動化框架使得開發者能在目標硬件系統尚未準備好的情況下就開始測試以提升代碼質量。這大大地縮短了花在目標系統上測試的時間。早期在宿主機上構建的測試套件可以被重用來在仿真器或真實的目標板上驗證程序的功能性。
]]>