<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    qileilove

    blog已經轉移至github,大家請訪問 http://qaseven.github.io/

    TDD中的單元測試寫多少才夠?

     測試驅動開發(TDD)已經是耳熟能詳的名詞,既然是測試驅動,那么測試用例代碼就要寫在開發代碼的前面。但是如何寫測試用例?寫多少測試用例才夠?我想大家在實際的操作過程都會產生這樣的疑問。
      3月15日,我參加了thoughtworks組織的“結對編程和TDD Openworkshop”活動,聆聽了tw的資深咨詢專家仝(tong2)鍵的精彩講解,并在講師的帶領下實際參與了一次TDD和結對編程的過程。活動中,仝鍵老師對到底寫多少測試用例才夠的問題,給出了下面一個解釋:
      我們寫單元測試,有一個重要的原因是用來防止自己犯低級錯誤的。我們不能把寫實現代碼的人當作我們的敵人,一定要把全部情況都測到,以防止他們在里面故意留下各種隱蔽的陷阱。測試寫的再多可能也沒有辦法覆蓋全部情況,所以只要能讓自己感到安全即可。怎樣才能讓自己感到安全呢?這是沒有標準答案的,只能是寫多了測試以后慢慢體會。
      另外,寫測試也要花時間的,比如compare這個方法的實現部分,我們只花了一兩分鐘就寫完了,而這些測試代碼,我們花了足足半個多小時,這樣做值得嗎?對于簡單的業務邏輯來說,當然是不值得的,畢竟我們還很多工作等著做,老板花錢是為了我們的產品代碼,而不是測試代碼。
      再考慮一種情況,我要創業,想了一個點子,做了一個網站,我當然是想以最快的速度把它做成型讓別人用。如果我在完全不知道人們會不會喜歡的時候,先花大量時間寫測試,最后發現沒人用只能丟掉,這些測試豈不是白寫了。
      所以還是上面那句話:單元測試是讓你提升自己對代碼的信心的,只要你感覺安全可以繼續開發時就夠了,不是越多越好。
      我相信上面一段解釋對于本文中提出的問題大家都沒有什么異議。但是這里我們不考慮特殊情況,在實際操作中,是否有辦法對單元測試這一工作進行衡量?來判斷是否足夠?
      使用代碼覆蓋率來衡量單元測試是否足夠
      常見的代碼覆蓋率有下面幾種:
      語句覆蓋(Statement Coverage):這是最常用也是最常見的一種覆蓋方式,就是度量被測代碼中每個可執行語句是否被執行到了。
      判定覆蓋(Desicion Coverage):它度量程序中每一個判定的分支是否都被測試到了。
      條件覆蓋(Condition Coverage):它度量判定中的每個子表達式結果true和false是否被測試到了。
      路徑覆蓋(Path Coverage):它度量了是否函數的每一個分支都被執行了。
      前三種覆蓋率大家可以查看下面的引用的第3篇文章,這里就不再多說。我們通過一個例子,來看看路徑覆蓋。比如下面的測試代碼中有兩個判定分支
    int foo(int a, int b)
    {
    int nReturn = 0;
    if (a < 10)
    {// 分支一
    nReturn+= 1;
    }
    if (b < 10)
    {// 分支二
    nReturn+= 10;
    }
    return nReturn;
    }
      我們仔細看看邏輯,nReturn的結果一共有4種可能,我們通過路徑覆蓋的方法設計出來的測試用例:
      Perfect。但是實際中的代碼往往比上面的例子復雜,如果代碼中有5個if-else,那么按照路徑覆蓋的方法,至少需要25=32個測試用例。這樣簡直要瘋掉了。
      沒必要追求代碼覆蓋率,真正要覆蓋的是邏輯
      簡單追求代碼結構上的覆蓋率,容易導致產生大量無意義的測試用例或者無法覆蓋關鍵業務邏輯。我們再看看上面解釋的第一段話。
      我們寫單元測試,有一個重要的原因是用來防止自己犯低級錯誤的。我們不能把寫實現代碼的人當作我們的敵人,一定要把全部情況都測到,以防止他們在里面故意留下各種隱蔽的陷阱。測試寫的再多可能也沒有辦法覆蓋全部情況,所以只要能讓自己感到安全即可。怎樣才能讓自己感到安全呢?這是沒有標準答案的,只能是寫多了測試以后慢慢體會。
     怎么才算讓自己感到安全?覆蓋邏輯,而不是代碼。站在使用者的角度考慮,需要關心的是軟件實現邏輯,而不是覆蓋率。如下面的例子:
    public class UserBusiness
    {
    public string CreateUser(User user)
    {
    string result = "success";
    if (string.IsNullOrEmpty(user.Username))
    {
    result = "usename is null or empty";
    }
    else if (string.IsNullOrEmpty(user.Password))
    {
    result = "password is null or empty";
    }
    else if (user.Password != user.ConfirmPassword)
    {
    result = "password is not equal to confirmPassword";
    }
    else if (string.IsNullOrEmpty(user.Creator))
    {
    result = "creator is null or empty";
    }
    else if (user.CreateDate == new DateTime())
    {
    result = "createdate must be assigned value";
    }
    else if (string.IsNullOrEmpty(user.CreatorIP))
    {
    result = "creatorIP is null or empty";
    }
    if (result != "success")
    {
    return result;
    }
    user.Username = user.Username.Trim();
    user.Password = BitConverter.ToString(MD5.Create().ComputeHash(Encoding.UTF8.GetBytes(user.Password)));
    UserDataAccess dataAccess = new UserDataAccess();
    dataAccess.CreateUser(user);
    return result;
    }
    }
    在寫UserBusiness.CreateUser的測試用例的時候,我們定義了下面幾個單元測試用例:
    [TestClass()]
    public class UserBusinessTest
    {
    private TestContext testContextInstance;
    /// <summary>
    ///Gets or sets the test context which provides
    ///information about and functionality for the current test run.
    ///</summary>
    public TestContext TestContext
    {
    get
    {
    return testContextInstance;
    }
    set
    {
    testContextInstance = value;
    }
    }
    [TestMethod()]
    public void Should_Username_Not_Null_Or_Empty()
    {
    UserBusiness target = new UserBusiness();
    User user = new User();
    string expected = "usename is null or empty";
    string actual = target.CreateUser(user);
    Assert.AreEqual(expected, actual);
    }
    [TestMethod()]
    public void Should_Password_Not_Null_Or_Empty()
    {
    UserBusiness target = new UserBusiness();
    User user = new User()
    {
    Username = "ethan.cai"
    };
    string expected = "password is null or empty";
    string actual = target.CreateUser(user);
    Assert.AreEqual(expected, actual);
    }

    [TestMethod()]
    public void Should_Password_Equal_To_ConfirmPassword()
    {
    UserBusiness target = new UserBusiness();
    User user = new User()
    {
    Username = "ethan.cai",
    Password = "a121ww123",
    ConfirmPassword = "a121ww1231"
    };
    string expected = "password is not equal to confirmPassword";
    string actual = target.CreateUser(user);
    Assert.AreEqual(expected, actual);
    }
    [TestMethod()]
    public void Should_Creator_Not_Null_Or_Empty()
    {
    UserBusiness target = new UserBusiness();
    User user = new User()
    {
    Username = "ethan.cai",
    Password = "a121ww123",
    ConfirmPassword = "a121ww1231"
    };
    string expected = "password is not equal to confirmPassword";
    string actual = target.CreateUser(user);
    Assert.AreEqual(expected, actual);
    }
    [TestMethod()]
    public void Should_CreateDate_Assigned_Value()
    {
    UserBusiness target = new UserBusiness();
    User user = new User()
    {
    Username = "ethan.cai",
    Password = "a121ww123",
    ConfirmPassword = "a121ww123",
    Creator = "ethan.cai"
    };
    string expected = "createdate must be assigned value";
    string actual = target.CreateUser(user);
    Assert.AreEqual(expected, actual);
    }
    [TestMethod()]
    public void Should_CreatorIP_Not_Null_Or_Empty()
    {
    UserBusiness target = new UserBusiness();
    User user = new User()
    {
    Username = "ethan.cai",
    Password = "a121ww123",
    ConfirmPassword = "a121ww123",
    Creator = "ethan.cai",
    CreateDate = DateTime.Now
    };
    string expected = "creatorIP is null or empty";
    string actual = target.CreateUser(user);
    Assert.AreEqual(expected, actual);
    }
    [TestMethod()]
    public void Should_Trim_Username()
    {
    UserBusiness target = new UserBusiness();
    User user = new User()
    {
    Username = "ethan.cai  ",
    Password = "a121ww123",
    ConfirmPassword = "a121ww123",
    Creator = "ethan.cai",
    CreateDate = DateTime.Now,
    CreatorIP = "127.0.0.1"
    };
    string expected = "ethan.cai";
    target.CreateUser(user);
    Assert.AreEqual(expected, user.Username);
    }
    [TestMethod()]
    public void Should_Save_MD5_Hash_Password()
    {
    UserBusiness target = new UserBusiness();
    User user = new User()
    {
    Username = "ethan.cai  ",
    Password = "a121ww123",
    ConfirmPassword = "a121ww123",
    Creator = "ethan.cai",
    CreateDate = DateTime.Now,
    CreatorIP = "127.0.0.1"
    };
    string actual = target.CreateUser(user);
    Assert.IsTrue("success" == actual
    && user.Password == BitConverter.ToString(MD5.Create().ComputeHash(Encoding.UTF8.GetBytes("a121ww123"))));
    }
    [TestMethod()]
    public void Should_Create_User_Successfully_When_User_Is_OK()
    {
    UserBusiness target = new UserBusiness();
    User user = new User()
    {
    Username = "ethan.cai  ",
    Password = "a121ww123",
    ConfirmPassword = "a121ww123",
    Creator = "ethan.cai",
    CreateDate = DateTime.Now,
    CreatorIP = "127.0.0.1"
    };
    string expected = "success";
    string actual = target.CreateUser(user);
    Assert.IsTrue(expected == actual);
    }
    }
      
      如果僅從代碼覆蓋率的角度來看,單元測試Should_Trim_Username、Should_Save_MD5_Hash_Password不會增加覆蓋率,似乎沒有必要,但是從邏輯上看,創建的賬戶的Username頭尾不能包含空白字符,密碼也不能明文存儲,顯然這兩個用例是非常有必要的。
      單元測試寫多少才夠?這個問題沒有確定的答案,但原則是讓你自己覺得安全。代碼覆蓋率高不能保證安全,真正的安全需要用測試用例覆蓋邏輯。

    posted @ 2014-04-18 13:25 順其自然EVO 閱讀(186) | 評論 (0)編輯 收藏

    性能測試關聯學習

    今天學習性能測試的關聯。以前都是從ppt上簡單知道關聯操作,不懂得那些操作需要關聯,關聯意義,為什么需要關聯,那些字段需要關聯。今天在kelly幫助講解下,了解了關聯的意義。
      舉例從創建預收貨訂單保存訂單-》保存商品明細-》審核-》確認收貨  最終狀態是完全收貨。
      其中涉及需要關聯有商品明細的保存、審核、收貨確認 均需要關聯
      操作關聯步驟:
      1.錄制腳本,進行回放發現,回放一次只會在在原來的訂單下新增一條商品明細,不會每次回放均新增一條記錄,同時狀態均是新增,故存在問題。
      2.尋找問題出在商品保存的時候,一直獲取是原因的訂單的ID和OrderNO  ,所以每次保存即在原來的訂單下進行保存。
      3.把腳本切換到TREE目錄下,查找商品明細保存的這段的request的內容,在<Operation>的節點中,看到代碼寫的方法,可以看到變量,分析哪些變量是不變,哪些變量是更新。從中得知ID和OrderNO,是一直保存于訂單頭部的一致。
      4.ID和OrderNO是可以從訂單頭部的保存自動生成,即在TREE的Response的XML下可以找到服務器反饋回來的值。
      5.此時需要在訂單頭部查找到Response函數,然后點擊函數右鍵,保存在一個參數里。即Response全部信息均保存在參數里。
      6.由于商品保存只需要獲得ID和OrderNO,所以需要把這兩個另存為其他參數,在商品保存可以直接調用。
      7.在商品保存查看Request和tree模式下,找到ID和OrderNO,點擊save value in parameter 。即name可以是默認,XML Source是來源頭部保存的參數。
      8.此時在Script腳本下,在商品保存中,去把ID和OrderNO參數化,調用在request中保存的參數即可。
      9.審核和收貨確認也一樣進行分析。
      10.從新跑腳本即可以新增一條訂單,同時狀態是完全收貨。

    posted @ 2014-04-18 13:23 順其自然EVO 閱讀(276) | 評論 (0)編輯 收藏

    sqlite數據庫 boolean類型的小小測試

     根據官方文檔的介紹:
      SQLite does not have a separate Boolean storage class. Instead, Boolean values are stored as integers 0 (false) and 1 (true).
      sqlite數據庫中沒有單獨的Boolean存儲類,Booean值以0(false)和1(true)來存儲.
      經我短時間測試的實踐, 顯示boolean 有三種狀態, 0(false)  1(true)  和 null,如下圖所示,
      經過下列插入語句,測試,均可插入成功.而且, 可以通過
      select * from stu where flag  ="數據庫"
      查詢到name 為a9 的行.
    insert into stu (name,flag) values ('a1','true'); -- 0
    insert into stu (name,flag) values ('a2','ture'); -- 0
    insert into stu (name,flag) values ('a3',1);  -- 1
    insert into stu (name,flag) values ('a4','null'); --0
    insert into stu (name,flag) values ('a5','1');   --1
    insert into stu (name,flag) values ('a6',null); -- null
    insert into stu (name,flag) values ('a7','2'); --1
    insert into stu (name,flag) values ('a8',15); --1
    insert into stu (name,flag) values ('a9',"數據庫"); --0
      導出數據庫,可以發現,執行的sql語句是這樣的,
    insert into [stu] values('a1', 0);
    insert into [stu] values('a2', 0);
    insert into [stu] values('a3', 1);
    insert into [stu] values('a4', 0);
    insert into [stu] values('a5', 1);
    insert into [stu] values('a6', null);
    insert into [stu] values('a7', 1);
    insert into [stu] values('a8', 1);
    insert into [stu] values('string', 0);
    insert into [stu] values('string2', 0);
    insert into stu (name,flag) values ('a9',0); --0
      如此, 猜想, sqlite 是采用了 字符型存儲插入的boolean類型數據, 但是,取出的時候, 會將插入的字符型數據轉換成int類型來使用.
      因此,可以得到下面的結論:
      -- 字符可轉換為int類型的為且不為0的為 true, 轉換失敗或轉換后為0的為 false(0)
      -- int ,long double 等數字,0為false, 其他>=1的為 true(1)
      -- 布爾類型報錯, null為 null,默認值
      ps:尚未對其進行深入了解,目前是實踐測試的結論,純屬猜測,如果有知情者,可告知.

    posted @ 2014-04-17 10:49 順其自然EVO 閱讀(264) | 評論 (0)編輯 收藏

    SQL盲注攻擊的簡單介紹

     1 簡介
      1.1 普通SQL注入技術概述
      目前沒有對SQL注入技術的標準定義,微軟中國技術中心從2個方面進行了描述[1]:
      (1) 腳本注入式的攻擊
      (2) 惡意用戶輸入用來影響被執行的SQL腳本
      根據Chris Anley的定義[2], 當一個攻擊者通過在查詢語句中插入一系列的SQL語句來將數據寫入到應用程序中,這種方法就可以定義成SQL注入。Stephen Kost[3]給出了這種攻擊形式的另一個特征,“從一個數據庫獲得未經授權的訪問和直接檢索”,SQL注入攻擊就其本質而言,它利用的工具是SQL的語法,針對的是應用程序開發者編程過程中的漏洞,“當攻擊者能夠操作數據,往應用程序中插入一些SQL語句時,SQL注入攻擊就發生了”。實際上,SQL注入是存在于常見的多連接的應用程序中一種漏洞,攻擊者通過在應用程序中預先定義好的查詢語句結尾加上額外的SQL語句元素,欺騙數據庫服務器執行非授權的任意查詢。這類應用程序一般是網絡應用程序(Web Application),它允許用戶輸入查詢條件,并將查詢條件嵌入SQL請求語句中,發送到與該應用程序相關聯的數據庫服務器中去執行。通過構造一些畸形的輸入,攻擊者能夠操作這種請求語句去獲取預先未知的結果。
      在風險方面,SQL注入攻擊是位居前列的,與緩沖區溢出等漏洞基本相當。而且如果要實施緩沖區溢出攻擊,攻擊者必須首先能繞過站點的防火墻;而對于SQL注入攻擊,由于防火墻為了使用戶能訪問網絡應用程序,必須允許從Internet到Web服務器的正向連接,因此一旦網絡應用程序有注入漏洞,攻擊者就可以直接訪問數據庫進而甚至能夠獲得數據庫所在的服務器的訪問權,因此在某些情況下,SQL注入攻擊的風險要高于所有其他漏洞。
      SQL注入攻擊利用的是SQL語法,這使得這種攻擊具有廣泛性。理論上說,對于所有基于SQL語言標準的數據庫軟件包括SQL Server,OracleMySQL, DB2,Informix等以及與之連接的網絡應用程序包括Active/Java Server Pages, Cold Fusion Management, PHP或Perl等都是有效的。當然各種軟件有自身的特點,實際的攻擊代碼可能不盡相同。SQL注入攻擊的原理相對簡單,且各類基于數據庫系統的應用程序被廣泛使用,介紹注入漏洞和利用方法的公開出版物也大量問世,造成近年SQL注入攻擊的數量一直增長,注入攻擊的形式也有被濫用的趨勢。
      關于針對MS SQL Server的普通SQL注入技術的詳細介紹,可以參考Chris Anley所撰的“SQL Server應用程序中的高級SQL注入”[2]一文和其后續“更多的高級SQL注入”[4],Cesar Cerrundo所撰的“利用SQL注入操縱Microsoft SQL Server” [5]一文,以及SPI實驗室的Kevin Spett撰寫的白皮書“SQL注入 你的網絡應用程序是否會受攻擊?” [6];而針對Oracle的普通SQL注入技術介紹,可以參考Stephen Kost的“針對Oracle開發人員的SQL注入攻擊簡介”[3]一文。
      1.2 SQL注入攻擊的防御手段
      由于越來越多的攻擊利用了SQL注入技術,也隨之產生了很多試圖解決注入漏洞的方案。目前被提出的方案有:
      (1) 在服務端正式處理之前對提交數據的合法性進行檢查;
      (2) 封裝客戶端提交信息;
      (3) 替換或刪除敏感字符/字符串;
      (4) 屏蔽出錯信息。
      方案(1)被公認是最根本的解決方案,在確認客戶端的輸入合法之前,服務端拒絕進行關鍵性的處理操作,不過這需要開發者能夠以一種安全的方式來構建網絡應用程序,雖然已有大量針對在網絡應用程序開發中如何安全地訪問數據庫的文檔出版,但仍然有很多開發者缺乏足夠的安全意識,造成開發出的產品中依舊存在注入漏洞;方案(2)的做法需要RDBMS的支持,目前只有Oracle采用該技術;方案(3)則是一種不完全的解決措施,例如,當客戶端的輸入為“…ccmdmcmdd…”時,在對敏感字符串“cmd”替換刪除以后,剩下的字符正好是“…cmd…”;方案(4)是目前最常被采用的方法,很多安全文檔都認為SQL注入攻擊需要通過錯誤信息收集信息,有些甚至聲稱某些特殊的任務若缺乏詳細的錯誤信息則不能完成,這使很多安全專家形成一種觀念,即注入攻擊在缺乏詳細錯誤的情況下不能實施。
      而實際上,屏蔽錯誤信息是在服務端處理完畢之后進行補救,攻擊其實已經發生,只是企圖阻止攻擊者知道攻擊的結果而已。本文所介紹SQL盲注技術就是一些攻擊者使用的新技術,其在錯誤信息被屏蔽的情況下使攻擊者仍能獲得所需的信息,并繼續實施注入攻擊。
      1.3 本文的結構組織
      為了理解盲注攻擊,我們首先將介紹確定SQL注入漏洞所需的服務器的最小響應;其次,我們將構造一個合乎語法的SQL請求,并可以將之替換成任何有效的SQL請求;最后,我們將討論在沒有詳細錯誤信息的情況下如何利用UNION SELECT語句。本文所討論的盲注攻擊的條件是我們在攻擊前對網絡應用程序、數據庫類型、表結構等等信息都一無所知,這些信息都需要在注入的過程中通過探測獲得。


     2 確定注入漏洞
      要進行SQL注入攻擊,首先當然是確認要攻擊的網絡應用程序存在注入漏洞,因此攻擊者首先必須能確立一些與服務器產生的錯誤相關的提示類型。盡管錯誤信息本身已被屏蔽,網絡應用程序仍然具有能區分正確請求和錯誤請求的能力,攻擊者只需要學習去識別這些提示,尋找相關錯誤,并確認其是否和SQL相關。
      2.1 識別錯誤
      一個網絡應用程序主要會產生兩種類型的錯誤,第一種是由Web服務器產生的代碼異常(exception),類似于“500:Internal Server Error”,通常如果SQL注入語句出現語法錯誤,比如出現未閉合的引號,就會使服務器拋出這類異常。如果要屏蔽該類錯誤,一般會采用將默認的錯誤信息替換成一個事先定制的HTML頁面,但只要觀察到有這種響應出現,就可以確認其實是發生了服務器錯誤。在其他情況下,為了進一步屏蔽該類錯誤,有些服務器一出現異常,會簡單地跳轉到主頁面或前一個訪問過的頁面, 或者顯示一條簡單的錯誤消息但不提供任何細節。
      第二類錯誤是由應用程序代碼產生的,這代表其開發者有較好的編程習慣。這類應用程序考慮到可能會出現一些無效的情況,并分別為之產生了一個特定的錯誤信息。盡管出現這類錯誤一般會返回一個請求有效的響應(200 OK),但頁面仍然會跳轉到主頁面,或者采用某種隱藏信息的辦法,類似于“Internal Server Error”。
      為了區分這兩種錯誤,我們看一個例子:有兩個電子商務的應用程序,A和B,兩個應用程序都使用同一個叫proddetails.asp的頁面,該頁面期待獲得一個參數,叫ProdID。它獲取該參數后,從數據庫中提取相應的產品詳細信息數據,然后對返回的結果進行一些處理。兩個應用程序都是通過一個產品列表頁面上的鏈接調用proddetails.asp,因此能保證ProdID一直都是存在且有效的。應用程序A認為這樣就不會出現問題,因此對參數不做額外的檢查,而如果攻擊者篡改了ProdID,插入了一個在數據表中不存在的id,數據庫就會返回一個空記錄。由于應用程序A沒有料到可能會出現空記錄,當它試圖去處理該記錄中的數據時,就可能會出現異常,產生一個“500:Internal Server Error”。而應用程序B,會在對記錄進行處理前確認記錄的大小超過0,如果是空記錄,則會出現一個錯誤提示“該產品不存在”,或者開發者為了隱藏該錯誤,會將頁面重新定位到產品的列表頁面。
      因此攻擊者為了進行SQL盲注,會首先嘗試提交一些無效的請求,并觀察應用程序如何處理這些錯誤,以及如果出現SQL錯誤會發生什么情況。
      2.2 定位錯誤
      對要攻擊的應用程序有了初步的認識后,攻擊者會試圖定位由人為構造的輸入而產生的錯誤信息。這時,攻擊者就會使用標準的SQL注入測試技術,比如添加一些SQL關鍵字(如OR,AND等)和一些META字符(如;或’等)。每一個參數都被獨立地進行測試,而獲得的響應將被檢驗用來判斷是否產生了錯誤。通過一個攔截代理服務器(intercepting proxy)或者類似的工具可以方便地識別頁面跳轉和其他一些可預測的隱藏錯誤,而任何一個返回錯誤的參數都有可能存在SQL注入漏洞。而在單獨測試每個參數過程中,必須保證其他參數都是有效的,因為需要避免除注入以外任何其他可能的原因所導致的錯誤影響了判斷結果。測試的結果一般是一個可疑參數的列表,列表中的一些參數可能的確可以進行注入利用,另外一些參數則可能是由一些SQL無關的錯誤所造成,因此需要被剔除。攻擊者接下來就需要從這些參數中挑選真正存在注入漏洞的參數,我們稱之為確定注入點。
      2.3 確定注入點
      SQL字段可以被劃分為三個主要類型:數字、字符串和日期。雖然每個類型都有其特點,但卻與注入的過程無關。每一個從網絡應用程序提交給SQL查詢的參數都屬于以上三個類型中的一類,其中數字參數被直接提交給服務器,而字符串和日期則需要加上引號才被提交,例如:
      SELECT * FROM Products WHERE ProdID = 4
      與
      SELECT * FROM Products WHERE ProdName = 'Book'
      而SQL服務器,并不關心它接受到的是什么類型的參數表達式,只要該表達式是相關的類型即可。而這個特點則使攻擊者能夠很容易地確認一個錯誤是否和SQL相關。如果是數字類型,最簡單的處理辦法是使用基本的算術操作,例如以下請求:
      /mysite/proddetails.asp?ProdID=4
      測試該參數的一種辦法是插入4’作為參數,另一種是使用3+1作為參數,假設這兩個參數已直接被提交給SQL請求語句,則將形成以下兩個SQL請求語句:
      (1) SELECT * FROM Products WHERE ProdID = 4'
      (2) SELECT * FROM Products WHERE ProdID = 3 + 1
      第一個SQL語法有問題,將一定會產生一個錯誤,而第二個如果被順利地執行,返回和最初的請求(即ProdID等于4)一樣的產品信息,這就提示該參數是存在注入漏洞的。
      類似的技術可以被應用于用一個符合SQL語法的字符串表達式替換該參數,這里有兩個區別:第一,字符串表示式是放在引號中的,因此需要阻斷引號;第二,不同的SQL服務器連結字符串的語法不同,比如MS SQL Server使用符號+來連結字符串,而Oracle使用符號||來連結。例如以下請求:
      /mysite/proddetails.asp?ProdName=Book
      要測試該ProdName參數是否有注入漏洞,可以先其替換成一個無效的字符串比如Book’,然后再替換成一個可能生成正確字符串的表達式,比如B’+’ook(對于Oracle,是B’||’ook)。這就會形成以下兩個SQL請求語句:
      (1) SELECT * FROM Products WHERE ProdName = 'Book''
      (2) SELECT * FROM Products WHERE ProdID = 'B' + 'ook'
      則第一個仍然可能產生一個SQL錯誤,而第二個則可能返回和最初的請求一樣的值為Book的產品。
      我們注意到,即使應用程序已經過濾了’和+等META字符,我們仍然可以在輸入時過把字符轉換成URL編碼(即字符ASCII碼的16進制)來繞過檢查,例如:
      /mysite/proddetails.asp?ProdID=3+1就等于/mysite/proddetails.asp?ProdID=3%2B1
      /mysite/proddetails.asp?ProdID=B’+’ook就等于/mysite/proddetails.asp?ProdID=B%27%2B%27ook
      類似的,任何表達式都可以用來替換最初的參數。而特殊的系統函數也可以被用來提交以返回一個數字,一個字符串或一個日期,比如Oracle中sysdate返回一個日期表達式,而在SQL Server中,getdate()會返回日期表達式。其他的技術同樣可以被用來判斷是否存在SQL注入漏洞。
      通過以上介紹可以發現,即使沒有詳細的錯誤信息,對于攻擊者來說,判斷是否存在SQL注入漏洞仍然是一個非常簡單的任務。
      3 實施注入攻擊
      攻擊者在確定注入點后,就要嘗試進行注入利用,這需要其能確定符合SQL語法的注入請求表達式,判斷出后臺數據庫的類型,然后構造出所需的利用代碼。
      3.1 確定正確的注入句法
      這是SQL盲注攻擊中最難也最有技巧的步驟,如果最初的SQL請求語句很簡單,那么確定正確的注入語法也相對容易,而如果最初的SQL請求語句較復雜,那么要想突破其限制就需要多次的嘗試,但進行這些嘗試所需要的基本技術卻是非常簡單。
      確定基本的句法的過程即通過標準的SELECT … WHERE語句,被注入的參數(即注入點)就是WHERE語句的一部分。為了確定正確的注入句法,攻擊者必須能夠在最初的WHERE語句后添加其他數據,使其能返回非預期的結果。對一些簡單的應用程序,僅僅加上OR 1=1就可以完成,但在大多數情況下如果想構造出成功的利用代碼,這樣做當然是不夠的。經常需要解決的問題是如何配對插入語符號(parenthesis,比如成對的括號),使之能與前面的已使用的符號,比如左括號匹配。另外常見的問題是一個被篡改的請求語句可能會導致應用程序產生其他錯誤,這個錯誤往往難于和一個SQL錯誤相區分,比如應用程序一次如果只能處理一個記錄,在請求語句后添加OR 1=1可能使數據庫返回1000條記錄,這時就會產生錯誤。由于WHERE語句本質上是一串通過OR、AND或插入語符號連接起來的值為TRUE或FALSE的表達式,因此要想確定正確的注入句法,關鍵就在于能否成功地突破插入語符號限制并能順利地結束請求語句,這就需要進行多次組合測試。例如,添加AND 1=2能將整個表達式的值變為FALSE,而添加OR 1=2則不會對整個表達式的值產生影響(除非操作符有優先級)。
      對于一些注入利用,僅僅改變WHERE語句就足夠了,但對于其他情況,比如UNION SELECT注入或存儲過程(stored procedures)注入,還需要能先順利地結束整個SQL請求語句,然后才能添加其他攻擊者所需要的SQL語句。在這種情況下,攻擊者可以選擇使用SQL注釋符號來結束語句,該符號是兩個連續的破折號(--),它要求SQL Server忽略其后同一行的所有輸入。例如,一個登錄頁面需要訪問者輸入用戶名和密碼,并將其提交給SQL請求語句:
      SELECT Username, UserID, Password FROM Users WHERE Username = ‘user’ AND Password = ‘pass’
      通過輸入john’--作為用戶名,將會構造出以下WHERE語句:
      WHERE Username = ‘john’ --'AND Password = ‘pass’
      這時,該語句不但符合SQL語法,而且還使用戶跳過了密碼認證。但是如果是另外一種WHERE語句:
      WHERE (Username = ‘user’ AND Password = ‘pass’)
      注意到這里出現了插入語符號,這時再使用john’--作為用戶名,請求語句就會錯誤:
      WHERE (Username = ‘john' --' AND Password = ‘pass’)
      這是因為有未配對的插入語符號,請求語句就不會被執行。
      這個例子顯示出使用注釋符號能夠用來判斷請求語句是否被順利地結束了,如果添加了注釋符號且沒有產生錯誤,這就意味著注釋符號前的語句已經順利地被結束。如果出現了錯誤,這就需要攻擊者進行更多的請求嘗試。
      3.2 判斷數據庫類型
      攻擊者一旦確定了正確的注入句法后,就會開始利用注入去判斷后臺數據庫的類型,這個步驟比確定注入句法要簡單得多。攻擊者一般會使用以下幾種技巧,這些技巧是基于不同類型數據庫引擎在具體實現上的差異。下面只介紹如何區分Oracle和MS SQL Server:
      最簡單的辦法,就是前面提到的利用字符串的連結符號,在注入句法已經確定的情況下,攻擊者可以對WHERE語句自由地添加額外的表達式,那么就可以利用字符串的比較來區分數據庫,例如:
      AND 'xxx' = 'x' + 'xx' (或者 AND %27xxx%27+%3D+%27x%27+%2B+%27xx%27)
      通過將+替換成||,就可以判斷出是數據庫是Oracle還是MS SQL Server,或者是其他類型。
      其他的辦法是利用分號字符(即;),在SQL中,分號是用來將幾個SQL語句連接在同一行中。在注入時,也可以在注入代碼中使用分號,但Oracle驅動程序卻不允許這樣使用分號。假設在前面使用注釋符號時沒有出現錯誤,那么在注釋符號前加上分號對MS SQL Server是沒有影響的,但如果是Oracle就會產生錯誤。另外,還可以使用COMMIT語句來確認是否允許在分號后再執行其他語句(例如,注入語句xxx' ; COMMIT --),如果沒有出現錯誤就可以認為允許多句執行。
      最后,表達式還可以被替換成能返回正確值的系統函數,由于不同類型的數據庫使用的系統函數也是不同的,因此也可以通過使用系統函數來確定數據庫類型,比如2.3節提到的MS SQL Server的日期函數getdate()與Oracle的sysdate.
      3.3 構造注入利用代碼
      當所有相關的信息都已獲得后,攻擊者就可以開始進行注入利用,而且在構造注入利用代碼過程中也不再需要詳細的錯誤信息,構造利用代碼本身可以參考其他描述標準SQL注入攻擊的文檔。
      由于對于普通的SQL注入利用,已經有很多其他論文進行了詳細的討論,故本文只會在下一節介紹一種UNION SELECT注入。
      4 UNION SELECT注入
      盡管通過篡改SELECT…WHERE語句來注入對于很多應用程序非常有效,但在盲注情況下,攻擊者仍然愿意使用UNION SELECT語句,這是因為與WHERE語句所進行的操作不同,使用UNION SELECT可以讓攻擊者在沒有錯誤信息的情況下依然能訪問數據庫中所有表。
      進行UNION SELECT注入需要預先獲知數據庫的表中的字段個數和類型,而這些信息一般被認為在沒有詳細錯誤信息的提示下是不可能獲得的,但本文下面就將給出解決該問題的方法。
      另外需要注意的是,進行UNION SELECT的前提是攻擊者已經確定了正確的注入句法,本文的前面一節已經闡明了這在盲注條件下是可以實現的,而且在使用UNION SELECT語句之前,SQL語句中所有的插入語符號都應該已經完成配對,從而可以自由地使用UNION或者其它指令進行注入。UNION SELECT還要求當前語句和最初的語句查詢的信息必須具有相同的數和相同的數據類型,不然就會出錯。
      4.1 統計列數
      當錯誤信息沒有被屏蔽時,要獲取列數只需要在進行UNION SELECT注入時每次嘗試使用不同的字段數即可,當錯誤信息由“列數不匹配”變成“列的類型不匹配”時,當前嘗試的列數就是正確的。但在盲注條件下,由于我們對無法獲悉錯誤信息究竟是哪個,所以該方法也就失去了作用。
      新的辦法是利用ORDER BY語句,在SELECT語句最后加上ORDER BY能夠改變返回的記錄集的次序,一般是按一個指定的列名的值進行排序。例如,當通過產品號查詢產品時,一個有效的注入語句如下:
      SELECT ProdNum FROM Products WHERE (ProdID=1234) ORDER BY ProdNum --
      AND ProdName=’Computer’) AND UserName=’john’
      人們往往會忽略的是ORDER BY語句后還可以使用數字指代列名,在上例中如果ProdNum是查詢請求返回的記錄中的第一列,則注入1234) ORDER BY 1--返回的結果是一樣的。由于上例查詢請求只返回一個字段,注入1234) ORDER BY 2 --就會出錯,即返回的記錄無法按指定的第二個字段排序。這樣,ORDER BY就可以被利用來對列數進行統計了。由于每個SELECT語句都至少返回一個字段,故攻擊者可以先在注入句法中添加ORDER BY 1來確定語句是否能被正確執行,有時對字段的排序也可能會產生錯誤,這時添加關鍵字ASC或DESC可以解決該問題。一旦確定ORDER BY句法是有效的,攻擊者就會對排序列號從列1到列100進行遍歷(或者到列1000,直到列號被確定為無效),理論上當出現第一個錯誤時,前一個列號就是要統計的列數,但在實際情況中,有些字段可能不允許排序,那么在出現第一次錯誤時可以再多嘗試一到兩個數字,以確認列號已遍歷完。
      4.2 判斷列的數據類型
      在統計完列數后,攻擊者需要再判斷列的數據類型,在盲注情況下判斷類型也是有技巧的,由于UNION SELECT要求前后查詢語句查詢的字段類型相同,故如果字段數有限,可以簡單地利用UNION SELECT語句對字段類型進行暴力窮舉(brute force),但如果字段數較多,判斷就會出現問題。根據前文,字段的類型只有數字、字符串和日期三種可能的類型,一旦字段數有10個,那么就意味著有310(約60,000)種可能的組合,假設每一秒可以自動進行20次嘗試,窮舉一遍也需要近一個小時,如果字段數更多,那么測試所需時間就會令人難以忍受。
      一種簡單的辦法是利用SQL的關鍵字NULL,與靜態字段的注入需要區分是數字類型還是字符類型不同,NULL可以匹配任何一種數據類型。因此可以注入一個所有查詢字段都為NULL的UNION SELECT語句,那么就不會出現任何類型不匹配的錯誤。讓我們再舉一個與前面類似的例子:
      SELECT ProdNum,ProdType,ProdPrice,ProdProvider FROM Products
      WHERE (ProdID=1234 AND ProdName=’ Computer’) AND UserName=’john’
      假設攻擊者已經獲得了列數(在該例中為4),那么就可以很簡單地構造一個UNION SELECT語句,其中所有查詢字段都為NULL,還需要構造一個不會產生權限問題的FROM語句。對于MS SQL Server,即使忽略FROM語句也不會出錯,但對于Oracle,則可以使用一個名叫dual的表。最后,還需要一個值一定為FALSE的WHERE語句(比如WHERE 1=2),這是為了確保查詢不會返回只包含null值的記錄集,以杜絕產生其他可能的錯誤。那么針對MS SQL Server的注入語句如下:
      SELECT ProdNum,ProdType,ProdPrice,ProdProvider FROM Products
      WHERE (ProdID=1234) UNION SELECT NULL,NULL,NULL,NULL
      WHERE 1=2 -- AND ProdName=’ Computer’) AND UserName=’john’
      這個NULL注入語句有兩個目的,主要目的是構造一個不會產生任何錯誤的UNION SELECT語句以測試UNION語句是否可以被執行,另一個目的是為了對數據庫類型的判斷進行100%確認(可以通過在FROM語句里添加一個數據庫開發商預置的表名進行測試)。
      如果NULL注入語句被順利執行,那么就可以快速地對每個列的類型進行判斷。在每一輪嘗試中,只對一個字段類型進行測試,由于類型只有三類,所以每個字段最多被測試三次就會有結果,這樣嘗試的次數最多是列數的三倍,而不是以3為底數以列數為指數的次數。假設ProdNum屬于數字類型,其它三個字段都屬于字符串類型,那么以下順序的注入語句就可以判斷出正確的類型:
       1234) UNION SELECT NULL,NULL,NULL,NULL WHERE 1=2 --
      無錯 句法正確,使用的是MS SQL Server數據庫
       1234) UNION SELECT 1,NULL,NULL,NULL WHERE 1=2 --
      無錯 第一個字段是數字類型
       1234) UNION SELECT 1,2,NULL,NULL WHERE 1=2 --
      出錯 第二個字段不是數字類型
       1234) UNION SELECT 1,’2’,NULL,NULL WHERE 1=2 --
      無錯 第二個字段是字符串類型
       1234) UNION SELECT 1,’2’,3,NULL WHERE 1=2 --
      出錯 第三個字段不是數字類型
       1234) UNION SELECT 1,’2’,’3’,NULL WHERE 1=2 --
      無錯 第三個字段是字符串類型
       1234) UNION SELECT 1,’2’,’3’,4 WHERE 1=2 --
      出錯 第四個字段不是數字類型
       1234) UNION SELECT 1,’2’,’3’,’4’ WHERE 1=2 --
      無錯 第四個字段是字符串類型
      攻擊者現在就已經獲得了每一列的數據類型,盲注還可以被應用于從數據庫的表中獲取數據,比如獲得數據表的列表以及它們各自的列名,還可以從應用程序中獲得數據,而這些技術在其他一些關于SQL注入的論文中已經有討論,故本文不再繼續介紹。


    posted @ 2014-04-17 10:40 順其自然EVO 閱讀(2498) | 評論 (0)編輯 收藏

    LoadRunner下載文件腳本

     在看普澤關于pezybase的測試報告的時候,發現里面有用到jmeter(http協議)并發測試下載文件,考慮到后面可能需要在公司pezybase的并發下載,把之前使用過的loadrunner下載文件腳本重新運行和整理一下。
      一、http協議
      loadrunner使用http協議是無法錄制到下載過程的,只會往服務器發送一個下載請求,其實服務器已經把數據返回給客戶端了,但是loadrunner是錄制不到保存文件到本地這個過程,所以就是需要我們手動把收到的內容保存打本地即可。(http協議上傳文件的完整過程是可以直接錄制的,在UC中已經使用過)。
      注意:下面腳本中用到的LR函數如下,還使用了一些C語言基本的文件操作方法。在腳本中還可以加入一些判斷來進行事務是否成功以及文件大小是否正確等的判斷;
      web_reg_save_param:關聯函數,放在http請求前面,保存請求返回的內容;
      web_url:http請求函數,向指定的url發送請求,下載文件也就是直接往下載鏈接發送請求;
      web_get_int_property:獲取下載請求返回的文件長度;
      web_set_max_html_param_len:設置web_set_max_html_param_len方法中參數的最大長度,要求大于需要下載文件的大小;
    Action() {
    int flen;//定義一個整型變量保存獲得文件的大小
    long fileContent;//保存文件句柄,也就是文件的內容
    char fileName[]="";//保存文件路徑及文件名
    char * strNumber;
    strNumber=lr_eval_string("test{NewParam}");//獲取一個隨機數并轉化成字符串 ,NewParam設置參數為隨機類型,這里根據自己需要進行參數化設置
    strcat(fileName,"C:/test/");//將路徑保存到file變量中
    strcat(fileName,strNumber); //拼接文件名
    strcat(fileName,".pdf");//拼接后綴名,根據需要設置,最后就完成了完整的路徑和文件名
    web_set_max_html_param_len("20000");//設置參數的最大長度,注意該值必須大于文件的大小
    //使用關聯函數獲取下載文件的內容,在這里不定義左右邊界,獲得服務器響應的所有內容
    web_reg_save_param("fcontent", //返回的內容全部存儲在fcontent這個參數中
    "LB=",
    "RB=",
    "SEARCH=BODY",
    LAST);
    lr_start_transaction("下載文件");   #設置的事務開始點
    #web_url方法你可以直接編寫,也可以在啟動錄制的時候,輸入下載鏈接URL進行簡單錄制,主要注意URL和Resource這兩個參數即可;
    web_url("file.php",      #函數名,沒有實際作用
    "URL=http://forum.ubuntu.org.cn/download/file.php?id=129973&sid=78fc8d76767ef49b606595824ceb963d",    #下載鏈接,也就是該鏈接輸入到瀏覽器會提示下載,獲取方法很多;
    "Resource=1",    #1表示是下載資源,0表示是頁面資源
    "RecContentType=application/octetstream",
    "Referer=",
    "Snapshot=t1.inf",
    LAST);
    flen = web_get_int_property(HTTP_INFO_DOWNLOAD_SIZE);//獲取響應中的文件長度
    if(flen > 0)
    {               //以寫方式打開文件
    fileContent = fopen(fileName, "wb");
    if(fileContent == NULL)  #看是否正確打開了需要保存下載內容的文件,fileName是上面準備組織的文件路徑;
    {
    lr_output_message("打開文件失敗!");
    return -1;
    }
    fwrite(lr_eval_string("{fcontent}"), flen, 1, fileContent);//寫入文件內容
    fclose(fileContent);//關閉文件
    }
    lr_end_transaction("下載文件",LR_AUTO);#事務結束點
    return 0;
    }
     二、java user協議
      使用java user協議更簡單,就是直接使用java編寫一段從指定鏈接下載文件的腳本即可;
    import java.io.BufferedInputStream;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.net.MalformedURLException;
    import java.net.URL;
    import java.net.URLConnection;
    import lrapi.lr;
    public class Actions {
    public int init() throws Throwable
    {
    return 0;
    }//end of init
    public int action() throws Throwable
    {
    int DownLoadSize = 0;
    String path = "c:\\temp\\";  //設置下載文件保存路徑
    String vuid = String.valueOf(lr.get_vuser_id()); //獲取當前虛擬用戶ID并轉換成字符串
    DownLoadSize = UrlTools.getHttpFileByUrl("http://forum.ubuntu.org.cn/download/file.php?id=129973&sid=78fc8d76767ef49b606595824ceb963d",vuid,path); //調用UrlTools.getHttpFileByUrl()
    return 0;
    }//end of action
    public int end() throws Throwable
    {
    return 0;
    }//end of end }
    class UrlTools
    {
    public static int getHttpFileByUrl(String address,String userid,String path)
    {
    //定義下面需要用到的變量
    URL url;
    URLConnection conn = null;
    int BUFF_SIZE = 1024;
    byte[] buf = new byte[BUFF_SIZE];
    int DownLoadSize = 0;
    BufferedInputStream bis;
    FileOutputStream fos = null;
    int size = 0;
    try
    {
    url = new URL(address); //address為傳遞進來需要下載的鏈接
    conn =  url.openConnection();//使用url實例化需要下載的鏈接
    bis = new BufferedInputStream(conn.getInputStream()); //把需要下載的文件內容保存在bis這個輸入流中
    fos = new FileOutputStream(path+"\"+userid+"test000001"+“.pdf”));//組成完整路徑,并實例化到輸出流,這里可以進行參數化,如參數化文件名, // 路徑需要事先手動創建好,當然你也可以在腳本中創建實現不同的路徑
    System.out.println("需要下載的文件大小為:" + conn.getContentLength()/1024 + "k");
    while((size = bis.read(buf)) != -1)   #按照設置的buf大小寫文件并記錄下載的大小
    {
    fos.write(buf,0,size);
    DownLoadSize = DownLoadSize+size;
    }
    bis.close();
    fos.close();
    System.out.println("用戶" + userid + "下載" + url +"完成!");
    }catch(MalformedURLException e)
    {
    System.out.println("下載發生異常:");
    e.printStackTrace();
    }catch(IOException e)
    {
    System.out.println("下載發生異常:");
    e.printStackTrace();         }
    return DownLoadSize/1024;
    }
    }

    posted @ 2014-04-17 10:38 順其自然EVO 閱讀(1841) | 評論 (0)編輯 收藏

    Selenium WebDriver如何查找WebElement

    這里從我們team的代碼中來總結下常見的幾種找頁面元素的方法:
      (1)通過WebElement的ID
      如果某個WebElement提供了ID,
      <input type="text" name="passwd"id="passwd-id" />
      (2)通過WebElement的name查找:
      WebElement element = driver.findElement(By.name("passwd"));
      (3)通過WebElement的xpath查找:
      WebElement element =driver.findElement(By.xpath("http://input[@id='passwd-id']"));
      (4)通過WebElement的樣式查找:
      <div class="cheese"><span>Cheddar</span></div><divclass="cheese"><span>Gouda</span></div>
      可以通過這樣查找頁面元素:
      List<WebElement>cheeses = driver.findElements(By.className("cheese"));
      (5)通過超鏈接文本查找:
      <a>cheese</a>>
      那么可以通過這樣查找:
      WebElement cheese =driver.findElement(By.linkText("cheese"));

    posted @ 2014-04-17 10:34 順其自然EVO 閱讀(1135) | 評論 (0)編輯 收藏

    自動化測試和測試自動化的區別

    這是兩個很繞口的詞。而且乍一看起來好像就是同一份工作。今兒聊聊我個人對于這兩者的認識。
      舉例:
      有一天,一家手機公司要做一個UI自動化測試,于是他們聘請了一名工程師。
      這個工程師需要做的事情,首先就是setup一個自動化測試環境。單單從這方面來說,測試工程師和自動化工程師需要做的是完全一樣的。比如搭建起來一套完整的UiAutomator環境。
      之后就會有區別了。當環境搭建好以后,測試工程師的主要精力就會鋪到編寫腳本,執行測試上。而自動化工程師則會把精力放在如何優化UiAutomator環境上
      比如,大家都知道UiAutomator的case編寫完成后,首先需要通過ant編譯,然后再通過adb命令進行push,最后才能執行。這一點上,一般來說測試工程師就不會做什么改變了,但是自動化工程師一定會做一個程序或者批處理或者其他的什么,讓這幾個步驟變成點一下就全干完的事情。
      什么是測試自動化:
      這是一種讓測試過程脫離人工的一次變革。對于控制成本,控制質量,回溯質量和減少測試周期都有積極影響的一種研發過程。
      什么是自動化測試:
      通過將測試執行部分部分或者全部交由機器執行的一種測試,叫做自動化測試。這種測試不需要人的實時參與。同時這種測試在小規模應用時會比手動測試昂貴許多。
      自動化測試可以看作測試自動化的一部分。
      不同的工程師,工作不同:
      一個自動化工程師,會比較專注于測試工具的研發。最主要的是這個工程師會從成本的角度去考慮問題。這一點比較像PM。他所做的一切是為了減少自己或者團隊的工作量,盡可能的將重復的,有規律可循的工作代碼化,自動化。
      一個自動化測試工程師,會比較專注于測試代碼的開發,以及測試結果的分析。對于被測設備本身非常感興趣。他們比較傾向于一種完美主義者,追求的是高質量而經常忽略成本。這一點更像開發人員。
      現在絕大多數公司都會執著于自動化測試,而忽略測試自動化。這一點會讓整個AT(automation test,下同)成本變得非常高。
      我曾經面試過一家公司的AT工程師,對方對于AT的做法就是每天都在release新的測試代碼,每天都在run不同的測試。每天都在修改之前的case。我說你這個并不是自動化測試,而是一種用代碼測試產品的手動測試。這樣的測試,經常被冠以自動化測試之名混水摸魚。
      這家公司很明顯的只是將代碼單元測試貼上了自動化的幌子。
    自動化測試的幾個準則:
      并不是將測試用例代碼化了,就可以稱之為自動化測試了。這是現在很多公司宣稱自己做AT的一個噱頭。
      AT的代碼有很多的要求。
      首先就是你的覆蓋面要夠廣。個位數case的自動化完全沒有意義。
      第二就是你的case必須要能夠復用:軟件每天都在變,如果你的case要天天跟著軟件變,那你的case是完全不合格的。
      第三就是測試的規模要夠大:要么時間長(case多或者是壓力測試),要么測試產品多。這樣才能體現出來自動化測試的優勢。、
      測試自動化的幾個準則:
      第一個就是要減少除工具研發部門外,其他所有測試部門的人力成本。這個是測試自動化追求的終極目標之一。、
      第二個就是提高測試質量,不僅僅包括測試執行的質量,還包括測試的統計質量,數據回溯質量,等等等等。這些質量的提高可以幫助測試團隊修正他們的測試方法,而不是每天將精力鋪在無止境的數據收集和分析中。
      第三個就是要搶出時間。某一項工作自動化后的時間,要么比人手做時間短,要么可以在非工作的16個小時中進行。通過讓電腦OT的方法來解放工程師或者項目經理。
      自動化的三大入手點:
      自動化的三大入手點其實和三大準則是一樣的。看哪個需求更加迫切:
      1. 成本:自動化并不一定圍繞測試執行,還可以包括測試的準備,log的提取,數據分析等等。將所有的與測試有關的工作逐一列出,然后找到重復的,可以被代碼化的部分,評估現有工作成本和自動化成本,尋找到收益最大的工作塊并順序將之代碼化。
      2. 質量:和成本差不多,只是在評估的時候需要評估的是該工作塊現有的質量狀況和需求質量間的差異,尋找到差異最多的那個模塊,并將所有質量差的模塊逐一進行自動化。
      3. 時間:和以上兩點一樣,都需要尋找到與測試有關的所有步驟和工作塊,將其中關鍵路徑上,動作最慢,耗時最大的部分進行自動化。
    版權聲明:本文出自 zeustest 的51Testing軟件測試博客:http://www.51testing.com/?15030005
    原創作品,轉載時請務必以超鏈接形式標明本文原始出處、作者信息和本聲明,否則將追究法律責任。

    posted @ 2014-04-17 10:33 順其自然EVO 閱讀(2317) | 評論 (0)編輯 收藏

    Web測試中,各類web控件測試點總結

      一 、界面檢查
      進入一個頁面測試,首先是檢查title,頁面排版,字段等,而不是馬上進入文本框校驗
      1、頁面名稱title是否正確
      2、當前位置是否可見  您的位置:xxx>xxxx
      3、文字格式統一性
      4、排版是否整齊
      5、列表項顯示字段是否齊全,列表項字段名稱是否跟表單統一
      6、同一頁面,是否出現 字段名稱相同、值取不同的問題。
      7、數據加載情況:除了文本框的值,還要注意:
      復選框,是否保存打√,或者保存不打√
      下拉框,是否保存選擇的值
      多文本框,值是否都被保存,空格,換行是否保存
      二、單文本框(type=text)
      邊界:字段長度
      判空:是否可以為空
      唯一性:是否唯一        (小歸結:邊界、判空、唯一性、特殊字符、正確性)
      考慮語言,操作環境
      特殊符號測試輸入:
      ' or 1<>'1   ' or '1'='1  ' or '1'<>'2  "|?><
      where a='xxx'   下劃線是否允許  輸入全部空格  輸入 單引號
      ><script>alert(“123”);</script>>
      特殊字段輸入限定:
      框內容是否合法(tel,ip,url,email)序號等,直接限制輸入數字,其他過濾掉
      輸入金額文本框,整數首位為0,過濾掉,小數點后面,一般保留兩個有效數字。
      正確性測試:(必不可少的步驟)
      1)、(字段長度輸入最大允許長度時)數據允許長度的測試:
      a、頁面是否被擠出的測試(都輸入長英文字符串,是否斷行);
      b、數據庫是否允許最大字符(都輸入漢字、都輸入英文、混合……);
      c、最短長度的正確流程,最大長度的正確流程覆蓋。
      2)、對于允許為空的字段,不填入,再次數據傳遞后,看是否報500錯誤。
      3)、未規定字段長度(或者數值大小),不按死板輸入,輸入非常多字符(或者非常大的數值)時,做允許動作的正確性校驗,看是否報錯。(要達到的結果:不管有沒有長度限制(沒有給最長、最大限制讓你去測?),最終頁面不能拋數據庫異常。)monkey test
      說明:通過不斷輸入長字符串,看是否有長度校驗;
      最終都會出現以下兩種情況的一種:
      A、頁面(前臺)有校驗長度、大小;   或者
      B、無校驗,數據庫報錯。
      所以: 所有字段都要做長度、大小限制(不管需求有沒有給出明確要求,不管測試顆粒度,都要限制長度,不允許報數據庫錯誤,都要測!!!)。最大長度限制可限定方法:1、不允許再輸入;2、自動截斷處理,并且給用戶提示
     關于長度概念:
      1、 數據庫規定的字節長度A
      2、 頁面上可以輸入的字符數B
      控制方法:
      1)、頁面上,不管輸入什么字符(全角如漢字、半角如字母),統一規定不能超過B個字符,此種限制,
      測試點:全部輸入全角B個,測試(B*3字節)會不會超過數據庫字節長度
      全部輸入半角B個,測試(B*1字節)會不會超過數據庫字節長度
      混合輸入全角X半角Y,測試(X*3+Y字節)會不會超過數據庫長度
      2)、頁面上,不以字符統計,以總的輸入字節數統計,比如,全部輸入全角字符,允許可以輸入A/3個字符,全部輸入半角字符,允許輸入A個字符( 民生網的設計)
      測試點:全部輸入全角,看是否允許輸入A/3個字符
      全部輸入半角,看是否允許輸入A個字符
      混合輸入全角X,半角Y,看是否允許X*3+Y=A
      (5個:判空、唯一、邊界值、特殊字符、正確流程(多種數據、多種分支))
      +測試校驗位置:ajax鼠標事件校驗、前臺提交按鈕js校驗,服務器拿到數據后再次驗證
      三、多文本框(type=textarea)
      1)、空格和換行的問題,看需求,是否需要做支持HTML Encoding
      輸入全部空格時,是否判空處理?””空格,   。
      輸入折行,是否也顯示折行?
      比如:列點說明原因,就需要支持。
      2)、字母截斷的問題
      對于一串字母,開發人員往往會忘掉做截斷,這樣如果展示在我們的平臺上的話,這一串字母就會把我們的UI撐開
      3)、長度控制格式, 您還可以輸入***個字符
      四、添加按鈕
      添加動作檢查范圍:
      失敗:是否提示
      提示內容是否正確
      失敗時:保存用戶已輸入的內容,避免重新再輸入
      成功:對話框消失
      記錄是否可直接查看(還需要刷新?)
      列表記錄順序
      重復提交情況,點擊一次后,是否變成disable
      上傳附件的添加:
      A. 文件名稱:文件名稱很長;文件名稱字符多樣化(漢字,英文,符號);文件名稱重復。
      B. 判空?
      C. 附件格式類型支持?
      D. 附件個數?
      E. 附件空間大小。
      五、移除按鈕
      1.一般都要在前臺先給出一個提示操作“確定移除該……”
      2.相關聯的東西,是否需要限制移除“該類型下存在應用,無法移除”有到后臺比較
      3.確定后,真正執行移除操作。
      結果:
      移除后,列表數據是否立即消失。
      必須有確認刪除的提示信息
      六、列表
      1)、列表記錄順序
      2)、是否需要翻頁、有沒有翻頁功能
      3)、字段名稱是否與表單一致
      七、搜索-文本框
      1、功能點、需求點考慮:
      是否提供模糊查詢、輸入數值有種類有限定時,是否考慮換成下拉框搜索;
      2、檢查點:
      文本框值是否消失(是否回填條件值),再次點擊“查詢”可查看所有記錄;
      考慮搜索結果:是否存在分頁,分頁是否正常;是否有序;
      注意:分頁是否仍保存查詢條件,檢查后面的記錄是否符合條件
      3、查詢數據多樣性:
      輸入不存在的字段值測試、包括特殊字符查詢測試例如:' or '1'='1;
      輸入類似程序語句的條件時是否執行查詢,如:XXXX”、XXX and ;
      4、操作類型:
      1) 不輸入的查詢
      2) 輸入全部空格的查詢
      3) 模糊查詢(輸入部分字段,或者說,輸入英文字母,查詢到相關中文數據)
      4) 輸入不存在的查詢
      5) 輸入存在的查詢
      6) 單個查詢和多個條件復合查詢。
      八、搜索-下拉框
      檢查點:
      a) 搜索結果是否有序;
      b) 下拉框值是否齊全;(下拉框值本身也是一個動態查詢的結果)
      c) 下拉框值是否自動消失,再次點擊“查詢”可查看所有記錄(是否要回填條件值);
      d) 分頁時,是否保存搜索條件。
      (從UI、開發、業務邏輯、用戶使用等角度測試)
      PS:
      以上總結的, 是比較純粹的從頁面控件角度測試點出發, 對于完整測試一個整體頁面,需要各類測試有機結合起來:
      1)UI測試:
      頁面布局; 頁面樣式檢查;控件長度是否夠長;顯示時,是否會被截斷;支持的快捷鍵,Tab鍵切換焦點順序正確性等。
      2)功能測試:頁面上各類控件的測試范圍,測試點,可參考上方
      結合控件的實際作用來補充檢查點: 比如, 密碼框是否*顯示, 輸入是否做trim處理等
      3)安全測試:輸入特殊字符,sql注入,腳本注入測試
      后臺驗證測試,對于較重要的表單 ,繞過js檢驗后臺是否驗證
      數據傳輸是否加密處理,比如, 直接請求轉發,地址欄直接顯示發送字符串?
      數據庫存儲,特別密碼等,是否加密形式存儲
      4)兼容性測試
      5)性能測試

    posted @ 2014-04-17 10:19 順其自然EVO 閱讀(914) | 評論 (0)編輯 收藏

    Oracle數據庫的邏輯結構

     oracle的體系邏輯結構
      oracle 數據庫的邏輯結構是:
      (1)表空間(TABLESPACE) ,包括:系統表空間、回滾段表空間、臨時表空間、用戶表空間(除用戶表空間外其他三張表空間有各自特定的用途,不可隨意更改和破壞)
      在建表時,可以指定表空間,例如: create table t(id int)  tablespace  tbs_test;
      建各類表空間的方法:
      ① 普通數據表空間
    create tablespace TBS_LJB(表空間名)
    datafile  'E:\ORADATA\ORA11\DATAFILE\TBS_LJB_01.DBF'(數據文件全路徑)      size 100M(大小)
    extent management local        --(這兩句,在oracle 10g及以上版本,可以取消)
    segment space management auto;
      ②臨時表空間
      CREATE TEMPORARY TABLESPACE  temp_ljb
      TEMPFILE  'E:\ORADATA\ORA11\DATAFILE\TMP_LJB.DBF'   SIZE 100M;
      ③回滾段表空間
      create undo tablespace undotbs2
      datafile  'E:\ORADATA\ORA11\DATAFILE\UNDOTBS2.DBF'  size  100M;
      補充:
      1、建用戶,并將先前建的表空間 tbs_ljb 和臨時表空間 temp_ljb 作為 ljb 用戶的默認使用空間。
    create user ljb
    identified by ljb
    default tablespace tbs_ljb
    temporary tablespace temp_ljb;
    -- 賦給ljb dba權限
    grant dba to ljb;
      2、如果表空間不足,可以有兩種方法:
      第一種,增加數據文件:
      ALTER  TABLESPACE TBS_LJB(表空間名)  ADD DATAFILE  '......'(數據文件全路徑名)  SIZE 100M;
      第二種,把表空間設置為自動擴展:
      ALTER DATABASE DATAFILE   '.......'(數據文件全路徑名)   autoextend on;  (在創建表空間時,就可以加上這個關鍵字,表示該表空間自動擴展)
      3、刪除表空間
      drop  tablespace  TBS_LJB
      including contents and datafile;
      如果表空間有數據,不增加 including contents 將無法刪除成功,增加 and datafiles 關鍵字在linux 及 unix 下可自動刪除數據文件,而在windows 環境下需要手動刪除
      4、UNDO 表空間和 TEMP 表空間在數據庫建好是必然已經創建好了,不過,它們都可以新建,并且用戶都可以指定新建的空間。
      5、oracle 可以為不同的用戶指定不同的臨時表空間,而且可以為同一用戶的不同session 設置不同的臨時表空間(臨時表空間組),從而減緩IO 競爭。
      (2)段(SEGMENT)
      每建立一張表,往往對應一個段,如果是分區表,那么各個分區又獨立成段。在表上建一個索引,則又會有一個對應的索引段。
      (3)區(EXTENT)
      oracle 分配空間的最小單位
      (4)塊(BLOCK)
      oracle 的最小邏輯數據單位

    11
    11

    posted @ 2014-04-16 11:23 順其自然EVO 閱讀(160) | 評論 (0)編輯 收藏

    Java使用字符流拷貝文件出現亂碼

     Java代碼
    //      BufferedReader in = null;
    //      BufferedWriter out = null;
    Reader in = null;
    Writer out = null;
    try {
    //          in = new BufferedReader(new FileReader(src));
    //          in = new BufferedReader(new InputStreamReader(new FileInputStream(src)));
    //          out = new BufferedWriter(new FileWriter(new File(dir, src.getName())));
    //          out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(new File(dir, src.getName()))));
    in = new FileReader(src);
    out = new FileWriter(new File(dir, src.getName()));
    System.out.println("正在拷貝文件(" + src + ")到目錄("+dir+")下");
    char[] buffer = new char[1024];
    int len = 0;
    while((len = in.read(buffer)) != -1) {
    out.write(buffer, 0, len);
    out.flush();
    }
    return true;
    } catch (Exception e) {
    return false;
    } finally {
    closeIO(in, out);
    }
      亂碼情況
    <img src="http://img.blog.csdn.net/20140414090344562?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaWNlcl93ZWk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="">
      怎樣解決?
      我想知道這種情況是怎么造成的?又該怎樣解決?

    posted @ 2014-04-16 11:22 順其自然EVO 閱讀(666) | 評論 (0)編輯 收藏

    僅列出標題
    共394頁: First 上一頁 124 125 126 127 128 129 130 131 132 下一頁 Last 
    <2025年5月>
    27282930123
    45678910
    11121314151617
    18192021222324
    25262728293031
    1234567

    導航

    統計

    常用鏈接

    留言簿(55)

    隨筆分類

    隨筆檔案

    文章分類

    文章檔案

    搜索

    最新評論

    閱讀排行榜

    評論排行榜

    主站蜘蛛池模板: 亚洲欧洲自拍拍偷综合| 国产一区二区免费视频| 亚洲欧洲日产国产综合网| 免费看国产一级片| 无码国产精品一区二区免费式直播 | 国产精品四虎在线观看免费| 最近免费中文字幕大全免费版视频 | 国产电影午夜成年免费视频| 国产午夜无码片免费| 欧洲精品码一区二区三区免费看| 亚洲av一本岛在线播放| 久久久久亚洲精品天堂| 亚洲国产成人一区二区三区| 亚洲精品一级无码鲁丝片| 国产jizzjizz视频免费看| 四虎成人免费大片在线| 免费av欧美国产在钱| 97热久久免费频精品99 | 亚洲人成网站日本片| 亚洲∧v久久久无码精品| 久久久久久久尹人综合网亚洲| 亚洲阿v天堂在线2017免费| 国产成人啪精品视频免费网| 成年18网站免费视频网站| 免费国产成人高清在线观看网站| 四虎1515hh永久久免费| 99在线在线视频免费视频观看| 日韩精品免费在线视频| 国产猛男猛女超爽免费视频| 波多野结衣免费一区视频| 东方aⅴ免费观看久久av | 久久亚洲AV午夜福利精品一区| 亚洲va无码专区国产乱码| 亚洲av无码一区二区三区不卡| 亚洲第一极品精品无码久久| 亚洲成AV人片在| 亚洲人成电影在在线观看网色| 久久亚洲精品无码| 亚洲精品国产成人中文| 亚洲冬月枫中文字幕在线看| 国产成人亚洲精品|