一般來說,對于做B/S架構的朋友來說,更有機會遇到高并發的數據庫訪 問情況,因為現在WEB的普及速度就像火箭升空,同時就會因為高訪問量帶來一系列性能問題,而數據庫一直是用戶與商人之間交流的重要平臺。用戶是沒有耐心 忍受一個查詢需要用上10秒以上的,或者更少些,如果經常出現服務器死機或者是報查詢超時,我想那將是失敗的項目。做了幾年的WEB工作,不才,一直沒有 遇到過大訪問量或者是海量數據的情況。這里并不是說沒有海量數據的項目就不是好項目,要看項目的應用場合。
最近做項目時,偶然得到了這個機會,在我工作過程中,本人發現的單表最大記錄數高達9位數。像訂單表什么的也有8位數。在查詢訂單的時候往往不能通過單表查詢就能解決,還要和其它相關表進行關聯查詢。如此關聯的表數據不大還好,一旦發生大表關聯大表,在查詢時就有可能出現慢長的等待。
主旨: 如何避免這種情況的發生呢?既然有了這樣的數據,需求還是要實現,這里就我最近針對數據庫的優化過程,我分兩篇文章來說明下。
第一篇:如何盡量避免大表關聯。
第二篇:對大表進行分區。
背景:有兩張表:
1:訂單表:記錄用戶訂單的詳細信息。order,其中有一個會員卡號字段cardNo,訂單產生時間。
2:會員表:記錄會員相關信息。member,一個會員有一個代理號:proxyID,代理下面有許多的會員卡:cardNo,它們共用一個代理號。
兩表通過cardNo來相關聯。
需求:查詢一個用戶或者某些用戶某一時間段所有會員卡產生的訂單情況。
實現SQL:
select 字段 from order
inner join member on
order.cardNo=member.cardNo
and member.proxyID in('a-01',代理號二)
and 時間 between '20080101' and '20080131'
本人見解:我想一般的朋友看到這樣的需求大多會寫出這樣的查詢SQL,如果不喜歡用in或者認為in的性能不好的朋友可用union all 代替。SQL語句可以說簡單的不能再簡單了,本身并無問題,只是如果兩表的數據都在百萬以上,而且字段都特別多。此時如果只有索引的幫忙下并不一定能達到 預期的效果。
解決方案一:利用表變量來替換大表關聯,表變量的作用域為一個批處理,批處理完了,表變量也會隨之失效,比起臨時表有它獨特的優點:不用手動去刪除表變量以釋放內存。
可行性:因為需求中的輸出字段大多來自訂單表,member表只起到數據約束的作用,和查詢用戶會員卡號的作用,所有可以先把代理的會員卡號先取到表變量中,然后利用帶有卡號的表變量和訂單表相關聯查詢。
declare @t table (cardNo int) insert @t select cardNo from member where in('a-01',代理號二) select 字段 from order inner join @t on order.cardNo=@t.cardNoand 時間 between '20080101' and '20080131' |
這里我就不貼性能比較圖了,有興趣的朋友可以自己嘗試下。這種方法在查詢人員比較多的時候特別有幫助。它要開發員根據實際情況詳細比較,結果并不是統一的,不同的環境結果可能不一樣。希望大家理解。
解決方案二:利用索引視圖來提高大表關聯的性能。
可行性:一般在大表關聯時,我們的輸出列都遠小于兩表的字段合,像上面的member表只用到了其中的兩個字段(cardNo,proxyID)。設想一下,此時的member表如果只有這兩個字段情況會不會好些呢?答案不言而喻。
視圖這個名詞在我以前對它的印象中,從來沒有認為視圖能優化查詢,因為我認為視圖對于數據庫來說就是一個虛假表,在數據庫中并無實際物理位置來存儲數 據。對于用戶來說無非就是通過不同的視角來觀看結果。視圖數據的產生都是實時的,即當調用視圖時,自動擴展視圖,去運行里面相應的select語句。后來 才知道在2000后的版本中視圖分一般視圖和索引視圖,一般視圖就是沒有創建索引的我印象中的視圖。而創建了視圖后就稱為索引視圖。索引視圖是物理存在 的,可在視圖上首先創建一個唯一的聚集索引,其它字段上也可創建非聚集索引。在不改變基礎表的情況下,起到了優化的效果。
CREATE VIEW memberView
WITH SCHEMABINDING
AS
SELECT cardNo,proxyID from member
GO
--以會員卡號創建一個唯一聚集索引
CREATE UNIQUE CLUSTERED INDEX ix_member_cardNo ON member (cardNo); GO |
注意:創建索引視圖要點:
1: CREATE VIEW memberView后面要跟上WITH SCHEMABINDING
理由:
● 使用 schemaname。objectname 明確識別視圖所引用的所有對象,而不管是哪個用戶訪問該視圖。
● 不會以導致視圖定義非法或強制 SQL Server 在該視圖上重新創建索引的方式,更改視圖定義中所引用的對象。
2:視圖上的第一個索引必須為 CLUSTERED 和 UNIQUE。
理由:必須為 UNIQUE 以便在維護索引視圖期間,輕松地按鍵值查找視圖中的記錄,并阻止創建帶有重復項目的視圖(要求維護特殊的邏輯)。必須為 CLUSTERED,因為只有聚集索引才能在強制唯一性的同時存儲行。
3:以下情況可考慮創建索引視圖:
● 可預先計算聚合并將其保存在索引中,從而在查詢執行時,最小化高成本的計算。
● 可預先聯接各個表并保存最終獲得的數據集。
● 可保存聯接或聚合的組合。
4:基礎表的更新會引發索引視力的更新。
5:索引視圖的創建同時會帶來維護上的開銷。
理由:
1)因為索引視圖是物理存在的。
2)要額外的維護索引。
實現:SQL:select 字段 from order
inner join memberView on order.cardNo=member.cardNo and member.proxyID=in('a-01',代理號二) and 時間 between '20080101' and '20080131' |
總結:兩種解決方案來看,各有所長,一般可以優先考慮使用索引視圖來優化大表關聯。以上是本人對于如何盡量避免發生大表關聯所采取的措施,望大家指教。
根據約定,在使用
java編程的時候應盡可能的使用現有的類庫,當然你也可以自己編寫一個排序的方法,或者框架,但是有幾個人能寫得比JDK里的還要好呢?使用現有的類的另一個好處是代碼易于閱讀和維護,這篇
文章主要講的是如何使用現有的類庫對數組和各種Collection容器進行排序,(文章中的一 部分例子來自《Java Developers Almanac 1.4》)
首先要知道兩個類:java.util.Arrays和java.util.Collections(注意和Collection的區 別)Collection是集合框架的頂層接口,而Collections是包含了許多靜態方法。我們使用Arrays對數組進行排序,使用 Collections對結合框架容器進行排序,如ArraysList,LinkedList等。
例子中都要加上import java.util.*和其他外殼代碼,如類和靜態main方法,我會在第一個例子里寫出全部代碼,接下來會無一例外的省略。
對數組進行排序
比如有一個整型數組:
int[] intArray = new int[] {4, 1, 3, -23}; |
我們如何進行排序呢?你這個時候是否在想快速排序的算法?看看下面的實現方法:
- import java.util.*;
- public class Sort{
- public static void main(String[] args){
- int[] intArray = new int[] {4, 1, 3, -23};
- Arrays.sort(intArray);
- }
- }
|
這樣我們就用Arrays的靜態方法sort()對intArray進行了升序排序,現在數組已經變成了{-23,1,3,4}。
如果是字符數組:
String[] strArray = new String[] {"z", "a", "C"}; |
我們用:
進行排序后的結果是{C,a,z},sort()會根據元素的自然順序進行升序排序。如果希望對大小寫不敏感的話可以這樣寫:
Arrays.sort(strArray, String.CASE_INSENSITIVE_ORDER); |
當然我們也可以指定數組的某一段進行排序比如我們要對數組下表0-2的部分(假設數組長度大于3)進行排序,其他部分保持不變,我們可以使用:
Arrays.sort(strArray,0,2); |
這樣,我們只對前三個元素進行了排序,而不會影響到后面的部分。
當然有人會想,我怎樣進行降序排序?在眾多的sort方法中有一個
sort(T[] a, Comparator<? super T> c) |
我們使用Comparator獲取一個反序的比較器即可,Comparator會在稍后講解,以前面的intArray[]為例:
Arrays.sort(intArray,Comparator.reverseOrder()); |
這樣,我們得到的結果就是{4,3,1,-23}。如果不想修改原有代碼我們也可以使用:
Collections.reverse(Arrays.asList(intArray)); |
得到該數組的反序。結果同樣為4,3,1,-23}。
現在的情況變了,我們的數組里不再是基本數據類型(primtive type)或者String類型的數組,而是對象數組。這個數組的自然順序是未知的,因此我們需要為該類實現Comparable接口,比如我們有一個Name類:
- class Name implements Comparable<Name>{
- public String firstName,lastName;
- public Name(String firstName,String lastName){
- this.firstName=firstName;
- this.lastName=lastName;
- }
- public int compareTo(Name o) { //實現接口
- int lastCmp=lastName.compareTo(o.lastName);
- return (lastCmp!=0?lastCmp:firstName.compareTo(o.firstName));
- }
- public String toString(){ //便于輸出測試
- return firstName+" "+lastName;
- }
- }
這樣,當我們對這個對象數組進行排序時,就會先比較lastName,然后比較firstName 然后得出兩個對象的先后順序,就像compareTo(Name o)里實現的那樣。不妨用程序試一試:
- import java.util.*;
- public class NameSort {
- public static void main(String[] args) {
- Name nameArray[] = {
- new Name("John", "Lennon"),
- new Name("Karl", "Marx"),
- new Name("Groucho", "Marx"),
- new Name("Oscar", "Grouch")
- };[page]
- Arrays.sort(nameArray);
- for(int i=0;i<nameArray.length;i++){
- System.out.println(nameArray[i].toString());
- }
- }
- }
|
結果正如我們所愿:
- Oscar Grouch
- John Lennon
- Groucho Marx
- Karl Marx
|
對集合框架進行排序
如果已經理解了Arrays.sort()對數組進行排序的話,集合框架的使用也是大同小異。只是將Arrays替換成了Collections,注意Collections是一個類而Collection是一個接口,雖然只差一個"s"但是它們的含義卻完全不同。
假如有這樣一個鏈表:
- LinkedList list=new LinkedList();
- list.add(4);
- list.add(34);
- list.add(22);
- list.add(2);
|
我們只需要使用:
就可以將ll里的元素按從小到大的順序進行排序,結果就成了:
如果LinkedList里面的元素是String,同樣會想基本數據類型一樣從小到大排序。
如果要實現反序排序也就是從達到小排序:
Collections.sort(list,Collectons.reverseOrder()); |
如果LinkedList里面的元素是自定義的對象,可以像上面的Name對象一樣實現Comparable接口,就可以讓Collection.sort()為您排序了。
如果你想按照自己的想法對一個對象進行排序,你可以使用
sort(List<T> list, Comparator<? super T> c) |
這個方法進行排序,在給出例子之前,先要說明一下Comparator的使用,Comparable接口的格式:
- public interface Comparator<T> {
- int compare(T o1, T o2);
- }
|
其實Comparator里的int compare(T o1,T o2)的寫法和Comparable里的compareTo()方法的寫法差不多。在上面的Name類中我們的比較是從LastName開始的,這是西方 人的習慣,到了中國,我們想從fristName開始比較,又不想修改原來的代碼,這個時候,Comparator就可以派上用場了:
- final Comparator<Name> FIRST_NAME_ORDER=new Comparator<Name>() {
- public int compare(Name n1, Name n2) {
- int firstCmp=n1.firstName.compareTo(n2.firstName);
- return (firstCmp!=0?firstCmp:n1.lastName.compareTo
- (n2.firstName));
- }
- };
|
這樣一個我們自定義的Comparator FIRST_NAME_ORDER就寫好了。
將上個例子里那個名字數組轉化為List:
- List<Name> list=Arrays.asList(nameArray);
- Collections.sort(list,FIRST_NAME_ORDER);
|
這樣我們就成功的使用自己定義的比較器設定排序。
不同角色之間的劃分往往有助于在角色的沖突中將問題暴露,實現透明,最終改進和保證質量。任何的軟件開發團隊都離不開兩個基本角色:開發與測試。 你可以沒有項目經理,可以沒有架構師,也可以沒有設計師;但是不能沒有開發,否則沒有人可以幫你實現產品;也不能沒有測試,否則沒有人可以決定你的產品是 否能夠交付。這就好像你往杯子里面倒水必須要用眼睛看著,沒有眼睛反饋的信息,你永遠不知道何時該停下來,也不知道停在那里;我們不希望水太少,更不希望 水溢出來。眼睛與手的反饋循環就是我們實現倒水這一動作高質量的必要系統,而開發和測試的有效循環就是我們實現高質量軟件的必須環節。
但是開發和測試本身的角色的局限性造成了他們往往沒有辦法有效地形成循環,比如我們經常會聽到這樣的抱怨:
測試:這個軟件需要的環境太復雜,沒有辦法為每種情況都創建測試環境.
測試:我沒有辦法保證測試的一致性,因為環境在不停地變化,恢復到原來的狀態很麻煩.
開發:你是怎么測出這個Bug的,我怎么沒法重現?測試:我忘記步驟了.
其實這些問題都和測試人員本身的定位有關系,測試人員的首要目標是發現軟件中的問題,要做到這一點他們往往專注于軟件的反應而忽視了造成這種響應的原因,如:硬件軟件環境,系統配置情況,操作一致性等等;測試用例失敗有幾種原因:
功能缺陷BUG;
測試用例本身寫的有問題(ST或者ET腳本問題);
測試環境有問題;
而這些正是開發人員修復Bug最需要的內容。但是測試人員不關心,或者沒有更多的精力來關心這些內容,造成了非常多的“不可重現”的Bug的出現。
我們可以通過持續集成以及對代碼進行版本管理控制來定位變更和導致功能缺陷的原因,同樣的,我們也可以對測試環境的變更進行控制和版本管理。之前提到過持續集成要求對一切進行版本管理,其中也包括測試環境。
初看上去,測試環境的管理是一個非常復雜的問題,之前是否遇到過下面一類問題?
“要測一個什么東西,需要什么軟件,然后手動安裝一遍,結果發現另外一個機器上其實已經有這個軟件了。” ——測試環境的復用和共享問題。
“有一個測試用例失敗了,可是之前測試的時候一直通過的,開發人員在開發環境下測試也沒有問題,測試人員費了九牛二虎之力,借助開發人員的調試幫助,結 果發現是測試環境中的一個配置參數改變了。 此時,另外一個測試人員冒了一句,我之前測試另外一個問題的時候將這個參數改掉了。” ——測試人員花了大量時間確定環境變更,測試環境的變更控制問題。
“有一個機器,你也在里面裝個東西,我也在里面裝個東西,結果這個機 器的環境越來越亂,桌面上亂起八糟,最后誰也不記得機器里面的一些文件有什么用處了,當初是因為什么原因使用的,又不敢刪除,怕其他人有用,可是又不知道 會是誰。” 測試環境的管理和記錄問題,好一些的會漸漸使用一些文檔進行記錄并共享,但是還是經常出現問題,畢竟文檔也會過期。
“一個測試MM突然大喊, 誰把我的模板和數據刪除啦,給我出來?。。?四周鴉雀無聲。我小聲的問一句,你上傳到svn上了嗎,上次不是說過一切都要版本控制嗎?” 測試環境和數據的備份和刪除,廣義上說這個也屬于變更。
“我這里需要再安裝針對ubuntu和suse操作系統的測試,并且需要32位和64位都有,而且還要設置一大堆配置??墒乾F有的5臺機器都安裝滿了, 總不能重裝來重裝去的吧,每次重裝都要了我的老命了...” 測試硬件資源的利用,和環境管理的效率問題。自動配置技術和利用虛擬化技術解決,測試環境數據化,配置化,然后才能版本控制和管理。
開發人員:“我在自己機器上測試了沒問題啊”, 測試人員:“可我在測試環境下面就是有問題啊。” 統一的測試環境問題。
以上的這些問題,都指向了一個關鍵點,測試環境的管理,分而細之,又包括幾個重要的因素:變更、自動化、數據化、虛擬化、共享。
虛擬化技術
虛擬化技術可以幫助將測試環境數據化,自動化,并借此達到重復利用的目的。虛擬化技術有很多,比較優秀的有VMWare和Virtual Box。比如,很多測試環境是寄生在操作系統中的,我們可以將這些操作系統做成操作系統基線,平時不需要測試時可以不開著,要用的時候再開。這些操作系統基線可以進行版本控制,因為文件比較大,用svn之類的管理可能會遇到一些問題,可以針對性設計一些大文件版本控制軟件(比如:結合SVN和FTP的優點)。
配置管理自動化
先后研究了幾種配置管理的工具,Chef, CfEngine, puppet,最后用的比較多的是Chef。Chef比較好的一點是提供OpenSouce Chef Server,可以自己搭建服務器,也是這幾個里面最先搭成功的,算是比較容易上手吧。就像一個大廚(Chef)使用刀(Knife)實驗各種不同的菜單 (Recipes),制成各種食譜(CookBook)一樣,一個配置管理工程師就是用它來制作不同的測試環境。
當我們用iPhone玩一個很有名的游戲——堅守陣地(FieldRunners)時,防御的布局非常重要。如果布局不好,如圖2-1所示,就玩得很累,看著“生命”一個一個死去,即使采用了一些小的技巧,最后也過不了關。

圖2-1 不好的布局決定著失敗
而如果換一個思路,進行不同的布局,如圖2-2所示,不采用自然的豎直排序,而采用斜線排序,充分利用空間,而且進攻部隊前進的速度會大大降低,結果就很不一樣,游戲者便能輕松過關。

圖2-2 良好的布局是成功的一半
這里的布局,是指處理問題的全局規劃、整體設計。全局規劃如何、整體設計效果如何,自然關系到后面的整個過程,所以總會受到我們重視。
說到架構,我們會想到軟件架構,如C/S架構、SOA架構,甚至會想到以架構設計為中心的RUP。軟件架構的概念由來已久,軟件架構師的頭銜容易被大家認可,但“測試架構”的概念還不夠清晰,人家會有很多的問題要問:
什么是測試架構?
測試架構對軟件測試有什么幫助?
軟件公司需要設置“軟件測試架構師”職位嗎?
軟件測試架構師做哪些事情?
當人們知道微軟公司、阿里巴巴集團等設有“測試架構師(Test Architect)”職位時,可能會驚奇地問:什么? 測試團隊也設立“架構師”頭銜嗎?人們對開發團隊設立架構師已經比較習慣了,因為大家知道,在設計一個軟件系統時,需要考慮整個產品架構如何設計
、系統各個組件如何集成在一起、如何相互協調工作,而這些都需要“軟件架構師”來完成,但對測試團隊為何要設立“架構師”頭銜還是不夠清楚,主要是因為不了解測試架構從何而來。
在日常測試工作中,如何選擇測試工具和如何建立統一的自動化測試框架?這是經常困擾我們的問題。除此之外,我們還會碰到如下的一系列問題:
如何幫助開發人員提高產品設計和代碼的可測試性?
如何找到更有效的辦法來設計
測試用例?
如何通過一些技術手段來提高測試的覆蓋率?
如何完成復雜系統的非功能性(性能、安全性、兼容性、可靠性等)測試任務?
如何通過分析系統測試結果,找出系統存在的問題?
能否對測試技術的發展趨勢做出正確判斷,從而更有針對性地提高測試團隊的技術能力?
舉例、暗喻
測試架構從何而來?其實它就是為了解決上述問題而產生的。從基本的觀點看,測試架構是由軟件系統技術架構和軟件測試框架(特別是自動化測試框架)構建的需求而定。這些需求,決定了以下從不同方面所形成的測試架構。
大家都能理解,越早進行測試,就能越早地發現缺陷,對提高產品質量、降低企業成本就越有利,更重要的是越能預防系統 設計時出現嚴重的缺陷。如果所設計的系統架構存在嚴重的缺陷,直到系統集成測試時才發現,所造成的返工將是可怕的。這就需要測試人員對設計進行復審、評 審。測試人員應參與系統架構及其組件接口等設計的審查,包括是否全面考慮非功能特性、各個特性的可測試性評估、設計的合理性等。
從軟件系統來看,如何驗證系統的性能、安全性、可靠性和可伸縮性等,例如網站能否支持擴展到100M的點擊率,投票系統是否安全等都需要對系統架構進行分析,建立測試概念模型,從而科學、有效地完成認證。
現在的系統越來越復雜,其設計往往不是一蹴而就的,需要不斷地重構和優化,而這些工作是基于以前版本的測試結果(包 括發現的問題)來實施的。測試人員在完成系統測試后,可以通過對測試結果的分析發現問題,如系統性能瓶頸、安全漏洞等,進而可以對系統的性能、可靠性、安 全性等改善提出有價值的建設性意見。
在系統功能測試時,需要對功能進行合理的劃分、歸類,建立用例模型,設計合理的測試結構。
從測試工作自身來看,需要建立合適的測試管理系統,包括測試用例庫的設計、缺陷跟蹤機制等。
設計自動化測試框架,包括集成測試環境、測試腳本分層處理、執行結果自動生成報告等。
掌握測試技術發展趨勢,研發新的測試方法,并借助測試工具來實現,例如,在安全性測試上如何采用合適的模糊測試方法。
性能測試顧名思義,測試服務(
web服務,
數據庫服務,其他網絡應用服務,本地服務)的性能如何?如何衡量性能?最表面的無非就是看能支撐多少個用戶同時使用該服務。且關注用戶使用過程中的用戶體驗。
Transactions per Second(每秒通過事務數)
“每秒通過事務數/TPS”顯示在場景運行的每一秒鐘,每個事務通過、失敗以及停止的數量,使考查系統性能的一個重要參數。通過它可以確定系統在任何給定時刻的時間事務負載。分析TPS主要是看曲線的性能走向。
將它與平均事務響應時間進行對比,可以分析事務數目對執行時間的影響。
例:當壓力加大時,點擊率/TPS曲線如果變化緩慢或者有平坦的趨勢,很有可能是服務器開始出現瓶頸。
Average Transaciton Response Time(事務平均響應時間)
“事務平均響應時間”顯示的是測試場景運行期間的每一秒內事務執行所用的平均時間,通過它可以分析測試場景運行期間應用系統的性能走向。
例:隨著測試時間的變化,系統處理事務的速度開始逐漸變慢,這說明應用系統隨著投產時間的變化,整體性能將會有下降的趨勢。
通常web服務還需要關心如下點:
Hits per Second(每秒點擊次數)
“每秒點擊次數”,即使運行場景過程中虛擬用戶每秒向Web服務器提交的HTTP請求數。
通過它可以評估虛擬用戶產生的負載量,如將其和“平均事務響應時間”圖比較,可以查看點擊次數對事務性能產生的影響。通過對查看“每秒點擊次數”,可以判斷系統是否穩定。系統點擊率下降通常表明服務器的響應速度在變慢,需進一步分析,發現系統瓶頸所在。
性能測試工具一般都會根據實際測試的場景和結果,畫出tps,average response time,點擊率等曲線圖表。 同時還會算出其他一些非常參考意義的數值和圖表。
1、當壓力加大時,TPS曲線如果變化緩慢或者有平坦的趨勢,很有可能是服務器開始出現瓶頸。
解析:tps 曲線為什么會變平坦?因為系統處理事務的線程數往往是固定的一個數值。(一般是由程序設定或者服務器配置決定),假設響應時間是固定的一個值時,那么每秒 中系統能夠處理的事務數是固定的數值。不會因為壓力的增大,TPS也會一直增大。實際上,響應時間并不是一個固定的值,而是隨著壓力變大,響應時間往往會 增加。那么,實際上,系統最大的TPS值,往往會比根據基準值估算出來的TPS要小。
2、當壓力加大時,點擊率曲線變化緩慢或者平坦,很有可能是服務器開始出現瓶頸。
解析:在web服務測試當中,點擊率和模擬的用戶數是能夠反映出服務壓力的大小。當壓力變大時,事務的響應時間變長,則導致點擊率會受到響應時間的影響,不會因為用戶增多,而增加。點擊率在服務器出現瓶頸時,壓力的增加不會增加點擊率。
3、事務平均響應時間增長
解析:事務平均響應時間增加,必然是指服務器性能有所下降。服務器壓力的加大,是主要原因。
a)壓力增大到每秒鐘事務的請求數,超過了系統每秒處理事務占用的線程數。這時,一些事務開始排隊。排隊的事務請求的響應時間必然大于之前的平均響應時間。
能夠寫出可維護的面向對象JavaScript代碼不僅可以節約金錢,還能讓你很受歡迎。不信?有可能你自己或者其他什么人有一天會回來重用你 的代碼。如果能盡量讓這個經歷不那么痛苦,就可以節省不少時間。地球人都知道,時間就是金錢。同樣的,你也會因為幫某人省去了頭疼的過程而獲得他的偏愛。 但是,在開始探索如何編寫可維護的面向對象JavaScript代碼之前,我們先來快速看看什么是面向對象。如果已經了解面向對象的概念了,就可以直接跳 過下一節。
什么是面向對象?
面向對象編程主要通過代碼代表現實世界中的實質對 象。要創建對象,首先需要寫一個“類”來定義。 類幾乎可以代表所有的東西:賬戶,員工,導航菜單,汽車,植物,廣告,飲料,等等。而每次要創建對象的時候,就從類實例化一個對象。換句話說,就是創建類 的實例做為對象。事實上,通常處理一個以上的同類事物時就會使用到對象。另外,只需要簡單的函數式程序就可以做的很好。對象實質上是數據的容器。因此在一 個employee對象中,你可能要儲存員工號,姓名,入職日期,職稱,工資,資歷,等等。對象也包括處理數據的函數(也叫做“方法”)。方法被用作媒介 來確保數據的完整性,以及在儲存之前對數據進行轉換。例如,方法可以接收任意格式的日期然后在儲存之前將其轉化成標準化格式。最后,類還可以繼承其他的 類。繼承可以讓你在不同類中重復使用相同代碼。例如,銀行賬戶和音像店賬戶都可以繼承一個基本的賬戶類,里面包括個人信息,開戶日期,分部信息,等等。然 后每個都可以定義自己的交易或者借款處理等數據結構和方法。
警告:JavaScript面向對象是不一樣的
在上一節中,概述了經典的面向對象編程的基本知識。說經典是因為JavaScript并不遵循這些規則。相反地,JavaScript的類是寫成函數的樣子,而繼承則是通過原型實現的。原型繼承基本上意味著使用原型屬性來實現對象的繼承,而不是從類繼承類。
———————————————–
【2012-4-25 11:11:35 更新】:根據微博網友@高翌翔 的反饋,前文中有關“JS 面向對象”的內容不夠細?,F推薦《Javascript 面向對象編程》《再談javascript面向對象編程》兩篇文章。
———————————————–
對象的實例化
以下是JavaScript中對象實例化的例子:
// 定義Employee類 function Employee(num, fname, lname) {
this .getFullName = function () {
return fname + " " + lname;
}
};
// 實例化Employee對象
var john = new Employee( "4815162342" , "John" , "Doe" );
alert( "The employee's full name is " + john.getFullName());
|
在這里,有三個重點需要注意:
1、“class”函數名的第一個字母要大寫。這表明該函數的目的是被實例化而不是像一般函數一樣被調用。
2、在實例化的時候使用了new操作符。如果省略掉new而僅僅調用函數則會產生很多問題。
3、因為getFullName指定給this操作符了,所以是公共可用的,但是fname和lname則不是。由Employee函數產生的閉包給了getFullName到fname和lname的入口,但同時對于其他類仍然是私有的。
原型繼承
下面是JavaScript中原型繼承的例子:
// 定義Human類 function Human() {
this .setName = function (fname, lname) {
this .fname = fname;
this .lname = lname;
}
this .getFullName = function () {
return this .fname + " " + this .lname;
}
}
// 定義Employee類
function Employee(num) {
this .getNum = function () {
return num;
}
};
//讓Employee繼承Human類
Employee.prototype = new Human();
// 實例化Employee對象
var john = new Employee( "4815162342" );
john.setName( "John" , "Doe" );
alert(john.getFullName() + "'s employee number is " + john.getNum());
|
這一次,創建的Human類包含人類的一切共有屬性——我也將fname和lname放進去了,因為不僅僅是員工才有名字,所有人都有名字。然后將Human對象賦值給它的prototype屬性。
通過繼承實現代碼重用
在前面的例子中,原來的Employee類被分解成兩個部分。所有的人類通用屬性被移到了Human類中,然后讓Employee繼承 Human。這樣的話,Human里面的屬性就可以被其他的對象使用,例如Student(學生),Client(顧客),Citizen(公 民),Visitor(游客),等等?,F在你可能注意到了,這是分割和重用代碼很好的方式。處理Human對象時,只需要繼承Human來使用已存在的屬 性,而不需要對每種不同的對象都重新一一創建。除此以外,如果要添加一個“中間名字”的屬性,只需要加一次,那些繼承了 Human 類的就可以立馬使用了。反而言之,如果我們只是想要給一個對象加“中間名字”的屬性,我們就直接加在那個對象里面,而不需要在Human 類里面加。
1、Public(公有的)和Private(私有的)
接下來的主題,我想談談類中的公有和私有變量。根據對象中處理數據的方式不同,數據會被處理為私有的或者公有的。私有屬性并不一定意味著其他人無法訪問??赡苤皇悄硞€方法需要用到。
● 只讀
有時,你只是想要在創建對象的時候能有一個值。一旦創建,就不想要其他人再改變這個值。為了做到這點,可以創建一個私有變量,在實例化的時候給它賦值。
function Animal(type) { var data = []; data[ 'type' ] = type; this .getType = function () { return data[ 'type' ]; } } var fluffy = new Animal( 'dog' ); fluffy.getType(); // 返回 'dog'
|
在這個例子中,Animal類中創建了一個本地數組data。當 Animal對象被實例化時,傳遞了一個type的值并將該值放置在data數組中。因為它是私有的,所以該值無法被覆蓋(Animal函數定義了它的范 圍)。一旦對象被實例化了,讀取type值的唯一方式是調用getType方法。因為getType是在Animal中定義的,因此憑借Animal產生 的閉包,getType可以進到data中。這樣的話,雖可以讀到對象的類型卻無法改變。
有一點非常重要,就是當對象被繼承時,“只讀”技術就無法運用。在執行繼承后,每個實例化的對象都會共享那些只讀變量并覆蓋其值。最簡單的解決辦法是將類中的只讀變量轉換成公共變量。但是你必須保持它們是私有的,你可以使用Philippe在評論中提到的技術。
● Public(公有)
當然也有些時候你想要任意讀寫某個屬性的值。要實現這一點,需要使用this操作符。
function Animal() { this .mood = '' ; } var fluffy = new Animal(); fluffy.mood = 'happy' ; fluffy.mood; // 返回 'happy
|
這次Animal類公開了一個叫mood的屬性,可以被隨意讀寫。同樣地,你還可以將函數指定給公有的屬性,例如之前例子中的getType函數。只是要注意不要給getType賦值,不然的話你會毀了它的。
完全私有
最后,可能你發現你需要一個完全私有化的本地變量。這樣的話,你可以使用與第一個例子中一樣的模式而不需要創建公有方法。
function
Animal() {
var
secret =
"You'll never know!"
}
var
fluffy =
new
Animal();
2、寫靈活的API
既然我們已經談到類的創建,為了保持與產品需求變化同步,我們需要保持代碼不過時。如果你已經做過某些項目或者是長期維護過某個產品,那么你就 應該知道需求是變化的。這是一個不爭的事實。如果你不是這么想的話,那么你的代碼在還沒有寫之前就將注定荒廢??赡苣阃蝗痪托枰獙⑦x項卡中的內容弄成動畫 形式,或是需要通過Ajax調用來獲取數據。盡管準確預測未來是不大可能,但是卻完全可以將代碼寫靈活以備將來不時之需。
● Saner參數列表
在設計參數列表的時候可以讓代碼有前瞻性。參數列表是讓別人實現你代碼的主要接觸點,如果沒有設計好的話,是會很有問題的。你應該避免下面這樣的參數列表:
function Person(employeeId, fname, lname, tel, fax, email, email2, dob) { };
|
這個類十分脆弱。如果在你發布代碼后想要添加一個中間名參數,因為順序問題,你不得不在列表的最后往上加。這讓工作變得尷尬。如果你沒有為每個參數賦值的話,將會十分困難。例如:
var ara = new Person(1234, "Ara", "Pehlivanian", "514-555-1234", null, null, null, "1976-05-17"); |
操作參數列表更整潔也更靈活的方式是使用這個模式:
function Person(employeeId, data) { }; |
有第一個參數因為這是必需的。剩下的就混在對象的里面,這樣才可以靈活運用。
var ara = new Person(1234, { fname: "Ara" , lname: "Pehlivanian" , tel: "514-555-1234" , dob: "1976-05-17" });
| |
這個模式的漂亮之處在于它即方便閱讀又高度靈活。注意到fax, email和email2完全被忽略了。不僅如此,對象是沒有特定順序的,因此哪里方便就在哪里添加一個中間名參數是非常容易的:
var ara = new Person( 1234 , { fname: "Ara" , mname: "Chris" , lname: "Pehlivanian" , tel: "514-555-1234" , dob: "1976-05-17" });
|
類里面的代碼不重要,因為里面的值可以通過索引來訪問:
function Person(employeeId, data) { this.fname = data['fname']; }; |
如果data['fname'] 返回一個值,那么他就被設定好了。否則的話,沒被設定好,也沒有什么損失。
● 讓代碼可嵌入 隨著時間流逝,產品需求可能對你類的行為有更多的要求。而該行為卻與你類的核心功能沒有半毛錢關系。也有可能是類的唯一一種實現,好比在一個選 項卡的面板獲取另一個選項卡的外部數據時,將這個選項卡面板中的內容變灰。你可能想把這些功能放在類的里面,但是它們不屬于那里。選項卡條的責任在于管理 選項卡。動畫和獲取數據是完全不同的兩碼事,也必須與選項卡條的代碼分開。唯一一個讓你的選項卡條不過時而又將那些額外的功能排除在外的方法是,允許將行 為嵌入到代碼當中。換句話說,通過創建事件,讓它們在你的代碼中與關鍵時刻掛鉤,例如onTabChange, afterTabChange, onShowPanel, afterShowPanel等等。那樣的話,他們可以輕易地與你的onShowPanel事件掛鉤,寫一個將面板內容變灰的處理器,這樣就皆大歡喜了。 JavaScript庫讓你可以足夠容易地做到這一點,但是你自己寫也不那么難。下面是使用YUI 3的一個例子。
<script type= "text/javascript" src= "http://yui.yahooapis.com/combo?3.2.0/build/yui/yui-min.js" ></script> <script type= "text/javascript" > YUI(). use ( 'event' , function (Y) { function TabStrip() { this .showPanel = function () { this .fire( 'onShowPanel' ); // 展現面板的代碼 this .fire( 'afterShowPanel' ); }; }; // 讓TabStrip有能力激發常用事件 Y.augment(TabStrip, Y.EventTarget); var ts = new TabStrip(); // 給TabStrip的這個實例創建常用時間處理器 ts.on( 'onShowPanel' , function () { //在展示面板之前要做的事 }); ts.on( 'onShowPanel' , function () { //在展示面板之前要做的其他事 }); ts.on( 'afterShowPanel' , function () { //在展示面板之后要做的事 }); ts.showPanel(); });
|
這個例子有一個簡單的 TabStrip 類,其中有個showPanel方法。這個方法激發兩個事件,onShowPanel和afterShowPanel。這個能力是通過用 Y.EventTarget擴大類來實現的。一旦做成,我們就實例化了一個TabStrip對象,并將一堆處理器都分配給它。這是用來處理實例的唯一行為 而又能避免混亂當前類的常用代碼。
總結
如果你打算重用代碼,無論是在同一網頁,同一網站還是跨項目操作,考慮一下在類里面將其打包和組織起來。面向對象JavaScript很自然地 幫助實現更好的代碼組織以及代碼重用。除此以外,有點遠見的你可以確保代碼具有足夠的靈活性,可以在你寫完代碼后持續使用很長時間。編寫可重用的不過時 JavaScript代碼可以節省你,你的團隊還有你公司的時間和金錢。這絕對能讓你大受歡迎。
一旦UseStatic 類被裝載,所有的static語句被運行。首先,a被設置為3,接著static 塊執行(打印一條消息),最后,b被初始化為a*4 或12。然后調用main(),main() 調用meth() ,把值42傳遞給x。3個println () 語句引用兩個static變量a和b,以及局部變量x 。
注意:在一個static 方法中引用任何實例變量都是非法的。
下面是該程序的輸出:
Static block initialized. x = 42 a = 3 b = 12 |
在定義它們的類的外面,static 方法和變量能獨立于任何對象而被使用。這樣,你只要在類的名字后面加點號運算符即可。例如,如果你希望從類外面調用一個static方法,你可以使用下面通用的格式:
這里,classname 是類的名字,在該類中定義static方法。可以看到,這種格式與通過對象引用變量調用非static方法的格式類似。一個static變量可以以同樣的 格式來訪問——類名加點號運算符。這就是Java 如何實現全局功能和全局變量的一個控制版本。
下面是一個例子。在main() 中,static方法callme() 和static 變量b在它們的類之外被訪問。
- class StaticDemo {
- static int a = 42;
- static int b = 99;
- static void callme() {
-
- System.out.println("a = " + a);
- }
- }
-
- class StaticByName {
-
- public static void main(String args[]) {
- StaticDemo.callme();
- System.out.println("b = " + StaticDemo.b);
- }
- }
|
下面是該程序的輸出:
static成員是不能被其所在class創建的實例訪問的。
如果不加static修飾的成員是對象成員,也就是歸每個對象所有的。
加static修飾的成員是類成員,就是可以由一個類直接調用,為所有對象共有的。
運行結果:
利用靜態代碼塊可以對一些static變量進行賦值,最后再看一眼這些例子,都一個static的main方法,這樣JVM在運行main方法的時候可以直接調用而不用創建實例。
4、static和final一塊用表示什么
static final用來修飾成員變量和成員方法,可簡單理解為“全局常量”!
對于變量,表示一旦給值就不可修改,并且通過類名可以訪問。
對于方法,表示不可覆蓋,并且可以通過類名直接訪問。
有時你希望定義一個類成員,使它的使用完全獨立于該類的任何對象。通常情況下,類成員必須通過它的類的對象訪問,但是可以創建這樣一個成員,它 能夠被它自己使用,而不必引用特定的實例。在成員的聲明前面加上關鍵字static(靜態的)就能創建這樣的成員。如果一個成員被聲明為static,它 就能夠在它的類的任何對象創建之前被訪問,而不必引用任何對象。你可以將方法和變量都聲明為static。static 成員的最常見的例子是main() 。因為在程序開始執行時必須調用main() ,所以它被聲明為static。
聲明為static的變量實質上就是全局變量。當聲明一個對象時,并不產生static變量的拷貝,而是該類所有的實例變量共用同一個static變量。聲明為static的方法有以下幾條限制:
● 它們僅能調用其他的static 方法。
● 它們只能訪問static數據。
● 它們不能以任何方式引用this 或super(關鍵字super 與繼承有關,在下一章中描述)。
如果你需要通過計算來初始化你的static變量,你可以聲明一個static塊,Static 塊僅在該類被加載時執行一次。下面的例子顯示的類有一個static方法,一些static變量,以及一個static 初始化塊:
- // Demonstrate static variables,methods,and blocks.
-
- class UseStatic {
- static int a = 3;
- static int b;
-
- static void meth(int x) {
- System.out.println("x = " + x);
- System.out.println("a = " + a);
- System.out.println("b = " + b);
- }
-
- static {
- System.out.println("Static block initialized.");
- b = a * 4;
- }
-
- public static void main(String args[]) {
- meth(42);
- }
- }
static表示“全局”或者“靜態”的意思,用來修飾成員變量和成員方法,也可以形成靜態static代碼塊,但是Java語言中沒有全局變量的概念。
被static修飾的成員變量和成員方法獨立于該類的任何對象。也就是說,它不依賴類特定的實例,被類的所有實例共享。
只要這個類被加載,Java虛擬機就能根據類名在運行時數據區的方法區內定找到他們。因此,static對象可以在它的任何對象創建之前訪問,無需引用任何對象。
用public修飾的static成員變量和成員方法本質是全局變量和全局方法,當聲明它類的對象市,不生成static變量的副本,而是類的所有實例共享同一個static變量。
static變量前可以有private修飾,表示這個變量可以在類的靜態代碼塊中,或者類的其他靜態成員方法中使用(當然也可以在非靜態成員 方法中使用--廢話),但是不能在其他類中通過類名來直接引用,這一點很重要。實際上你需要搞明白,private是訪問權限限定,static表示不要 實例化就可以使用,這樣就容易理解多了。static前面加上其它訪問權限關鍵字的效果也以此類推。
static修飾的成員變量和成員方法習慣上稱為靜態變量和靜態方法,可以直接通過類名來訪問,訪問語法為:
類名.靜態方法名(參數列表...)
類名.靜態變量名
用static修飾的代碼塊表示靜態代碼塊,當Java虛擬機(JVM)加載類時,就會執行該代碼塊(用處非常大,呵呵)。
1、static變量
按照是否靜態的對類成員變量進行分類可分兩種:一種是被static修飾的變量,叫靜態變量或類變量;另一種是沒有被static修飾的變量,叫實例變量。
兩者的區別是:
對于靜態變量在內存中只有一個拷貝(節省內存),JVM只為靜態分配一次內存,在加載類的過程中完成靜態變量的內存分配,可用類名直接訪問(方便),當然也可以通過對象來訪問(但是這是不推薦的)。
對于實例變量,沒創建一個實例,就會為實例變量分配一次內存,實例變量可以在內存中有多個拷貝,互不影響(靈活)。
所以一般在需要實現以下兩個功能時使用靜態變量:
● 在對象之間共享值時
● 方便訪問變量時
2、靜態方法
靜態方法可以直接通過類名調用,任何的實例也都可以調用,
因此靜態方法中不能用this和super關鍵字,不能直接訪問所屬類的實例變量和實例方法(就是不帶static的成員變量和成員成員方法),只能訪問所屬類的靜態成員變量和成員方法。
因為實例成員與特定的對象關聯!這個需要去理解,想明白其中的道理,不是記憶?。?!
因為static方法獨立于任何實例,因此static方法必須被實現,而不能是抽象的abstract。
例如為了方便方法的調用,Java API中的Math類中所有的方法都是靜態的,而一般類內部的static方法也是方便其它類對該方法的調用。
靜態方法是類內部的一類特殊方法,只有在需要時才將對應的方法聲明成靜態的,一個類內部的方法一般都是非靜態的
3、static代碼塊
static代碼塊也叫靜態代碼塊,是在類中獨立于類成員的static語句塊,可以有多個,位置可以隨便放,它不在任何的方法體內,JVM加 載類時會執行這些靜態的代碼塊,如果static代碼塊有多個,JVM將按照它們在類中出現的先后順序依次執行它們,每個代碼塊只會被執行一次。例如:
- public class Test5 {
- private static int a;
- private int b;
-
- static{
- Test5.a=3;
- System.out.println(a);
- Test5 t=new Test5();
- t.f();
- t.b=1000;
- System.out.println(t.b);
- }
- static{
- Test5.a=4;
- System.out.println(a);
- }
- public static void main(String[] args) {
- // TODO 自動生成方法存根
- }
- static{
- Test5.a=5;
- System.out.println(a);
- }
- public void f(){
- System.out.println("hhahhahah");
- }
- }
CC(Attributes Components Compatibilities)是
Google測試團 隊使用的一種建模方法,用來快速地建立產品的模型,以指導下一步的測試計劃和設計。在Google內部,ACC得到較普遍的應用,一些工程師還開發了支持 ACC模型的Web應用,并將其開源。本文將介紹ACC的內容,所引用的Google+的例子摘錄自《How Google Tests Software》一書。此外,本文還將使用
啟發式測試策略模型(Heuristic
Test Strategy Model,簡稱HTSM)來分析ACC。
運用ACC建模的第一步是確定產品的Attributes(屬性)。按照谷歌的定義,Attributes是產品的形容詞(adjectives),是與競爭對手相區別的關鍵特征。按照敏捷開發的觀點,Attributes是產品所交付的核心價值(values)。從HTSM的角度,Attributes位于HTSM->Quality Criteria->Operation Criteria,隸屬于面向用戶的質量標準。
Google+的Attributes如下:
● Social(社交):鼓勵用戶去分享信息和他們的狀態
● Expressive(表現力):用戶可以運用各種功能去表達自我
● Easy(容易):讓用戶以直觀的方式做他們想做的事
● Relevant(相關):只顯示用戶感興趣的內容
● Extensible(可擴展):能夠與Google的已有功能、第三方網站和應用(Application)集成
● Private(隱私):用戶數據不會泄漏
ACC以Attribute開始,是產品競爭的自然選擇,也符合Google的開發實踐。在Google的項目中,開發人員和測試人員的比例通常是10:1或更高。開發人員會編寫大量的自動化測試用 例,對產品實施周密的測試,因此測試人員主要關注用戶價值和系統級測試。即便如此,測試人員也沒有足夠的資源測試所有用戶行為。所以,測試人員需要通過確 定Attributes來明確產品的核心價值,從而區分出測試對象的輕重緩急(priorities)。獲取Attributes的信息源可以是產品經 理、市場營銷人員、技術布道者、商業宣傳材料、產品廣告等。測試人員也可以使用“賣點漫游”(The Money Tour)來發掘和檢驗產品的賣點。
第二步是確定產品的Components(部件)。Components是產品的名詞(nouns),可以理解為產品的主要模塊、組件、子系統。從 HTSM的角度,Components位于HTSM->Product Elements->Structure和HTSM->Product Elements->Function,即同時具備代碼結構和產品功能的特征。
Google+的Components如下:
● Profile(個人資料):用戶的帳戶信息和興趣愛好
● People(人脈):用戶已經連接的好友
● Stream(信息流):由帖子、評論、通知、照片等組成的有序的信息流
● Circles(圈子):將好友分組,如把不同的好友歸于“朋友”、“同事”等小組
● Notifications(通知):當用戶被帖子提到時,向他顯示提示信息
● Hangouts(視頻群聊):視頻對話的小組
● Posts(帖子):用戶和好友所發表的信息
● Comments(評論):對帖子、照片、視頻等的評論
● Photos(照片):用戶和好友所上傳的照片
Components可以看作功能列表(Function List)的頂層元素,是產品核心功能的清單?!禜ow Google Tests Software》建議Components列表要盡可能簡單,10個Components很好,20個就太多了。其目的是重點考慮對產品、對用戶最重要 的功能與代碼,并避免漫長的Components列表所導致的分析癱瘓。
第三步是確定產品的Compatibilities(能力)。 Compatibilities是產品的動詞(verbs),描述了一個Component提供了何種能力來實現一個Attribute。在HTSM的角 度,Compatibilities位于HTSM->Product Elements->Function和HTSM->Quality Criteria->Operation Criteria->Compatibility,刻畫了產品實現其核心價值的手段。
Google+的Compatibilities矩陣如下:
| Social | Expressive | Easy | Relevant | Extensible | Private |
Profile | 在好友中分享個人資料和興趣愛好 | 用戶可以在網上表達自我 | 很容易創建、更新、傳播信息 |
| 向被批準的、擁有恰當訪問權限的應用提供數據 | -
用戶可以保密隱私信息 -
只向被批準、擁有恰當訪問權限的應用提供信息
|
People | 用戶能夠連接他的朋友 | 用戶可以定制個人資料,使自己與眾不同 | 提供工具讓管理好友變得輕松 | 用戶可以用相關性規則過濾好友 | 向應用提供好友數據 | 只向被批準、擁有恰當訪問權限的應用提供信息 |
Stream | 向用戶提示其好友的更新 |
|
| 用戶可以根據興趣過濾好友更新 | 向應用提供信息流 |
|
Circles | 將好友分組 | 根據用戶的語境創建新圈子 | 鼓勵創建和修改圈子 |
| 向應用提供圈子數據 |
|
Notifications |
|
| 簡明地展示通知 |
| 向應用提供通知數據 |
|
Hangouts | | 加入群聊前,用戶可以預覽自己的形象 | -
只要一次點擊就可以關閉視頻和音頻輸入 -
可將好友加入已有的群聊
|
| -
用戶可以在群聊中使用文字交流 -
YouTube視頻可以加入群聊 -
在“設置”中可以配置群聊的硬件 -
沒有攝像頭的用戶可以音頻交談
| -
只有被邀請的用戶才能加入群聊 -
只有被邀請的用戶才能收到群聊通知
|
Posts |
| 表達用戶的想法 |
|
| 向應用提供帖子數據 | 帖子只向被批準的用戶公布 |
Comments |
| 用評論表達用戶的想法 |
|
| 向應用提供評論數據 | 評論只向被批準的用戶公布 |
Photos | 用戶可以分享他的照片 |
| -
用戶能方便地上傳照片 -
用戶能方便的從其他來源導入照片
|
| 與其他照片服務集成 | 照片只向被批準的用戶公布 |
Compatibilities通常是面向用戶的(user-oriented),反映了用戶視角的產品行為。測試 人員也應該保持Compatibilities矩陣的簡潔,他們應該關注對用戶而言最有價值、最有吸引力的能力,并在合適的抽象層次(right level of abstraction)記錄Compatibilities。最重要的是,Compatibilities應該是可測的(testable),測試人員 能夠設計測試來檢查產品實現了預期的Compatibilities。
有了Compatibilities矩陣,測試團隊就完成初始的測試計劃。這就是前Google測試總監James Whittaker所說的10分鐘測試計劃(The Ten Minutes Test Plan)。其基本思路是專注于核心屬性、核心功能和核心能力,而省略一切不必要的細節。之后,測試團隊會利用矩陣去指導測試設計,通常矩陣中的一條 Compatibility就是一個測試對象、測試策略或測試情景,而復雜的Compatibility會演化出更多的測試設計。
Google所提供的開源Web應用可以分析項目信息,包括測試用例、代碼變更、產品缺陷等,以確定Compatibilities矩陣中的高 風險區域。下圖引用自James Whittaker在GTAC 2010的閉幕演講的幻燈片,是Chrome OS的Compatibilities矩陣的熱點圖(heap map)。圖中綠色表示低風險區域,紅色表示高風險區域,粉紅色和橙色則表示風險居于前兩者之間。測試人員可以根據熱點圖,更好地確定測試優先級,將有限 的資源運用在最需要的地方。

許多團隊的風險分析依賴于測試人員的經驗和猜測,Google的ACC工具則通過分析項目元素(測試用例、代碼變 更、產品缺陷等)來識別風險。這些被分析的元素位于HTSM->Project Environment,是項目環境的一部分。即便不使用Google的工具,測試人員也可以利用電子表格記錄Compatibilities矩陣,并自 行計算各個條目的風險(一些Google的測試人員也是這么做的)。在評估風險時,他可以考慮如下因素:
● 自動化測試用例:該區域有自動化測試用例嗎?測試在定期運行嗎?測試通過率是多少?測試用例覆蓋了哪些方面,沒有覆蓋哪些方面?
● 手動測試:有人手動測試該區域嗎?經過測試,他們對該區域有信心嗎?如果滿分是10分,他們會打幾分?
● 代碼變更:該區域近期存在代碼變更嗎?變更頻繁嗎?變更是新增功能、代碼重構、還是缺陷修復?
● 代碼復雜度:代碼的規模是多少?代碼是否復雜?如果復雜度的滿分是10分,該區域的代碼能得幾分?
● 產品缺陷:該區域的缺陷多嗎?有哪些典型缺陷?哪些缺陷已經被修復?哪些缺陷還沒有被修復?活躍的缺陷是在快速增加還是穩步下降?
在計算此類風險因素時,測試人員可以采用盡可能簡單的度量方法。一方面,簡單的方法更容易解釋度量值的含義,從而有 助于針對度量值采取相應的行動。另一方面,復雜的方法增大了分析的難度,卻往往不能提供更多的收益。通過測試去獲得直接的反饋,并定期重新度量風險因素, 是更注重實效的方法。這也符合ACC的風格:快速的前進,持續的迭代。在測試計劃時,測試人員只要快速地確定Compatibilities矩陣,而不必 擔心遺漏。隨著測試的進展,他會對矩陣做出必要的調整,以優化測試的價值。
版權聲明:本文出自 liangshi 的51Testing軟件測試博客:http://www.51testing.com/?298785
摘要: 【IT168 技術文檔】作為一名開發人員,大多情況下都會認真的做好功能測試,但是卻常常忽略了軟件開發之后的壓力測試,尤其是在面向大量用戶同時使用的Web應用系統的開發過程,壓力測試往往是不夠充分的。近期我在一個求職招聘型的網站項目中就對壓力測試的重要性體會頗深。 在項目中,我負責開發職位信息的搜索部分,但是由于缺乏壓力測試,倉促將搜素部分的功能提交到生產環境,結果當并發量稍稍到達一定程度時,數據...
閱讀全文