基于xUnit的單元測試管理(Java語言)

讀書筆記,更新到學完為止

什么是xUnit

  • xUnit是各種代碼驅動測試框架的統(tǒng)稱,這些框架可以測試軟件的不同內(nèi)容
  • 主要優(yōu)點:提供了一個自動化測試的解決方案,不需要多次編寫重復的測試代碼,也不必記錄測試的結果。
  • Java語言中典型的xUnit是JUnit和TestNG蝌麸,Python語言中是UnitTest、PyTest艾疟。

xUnit的組成

  • 底層是xUnit的framwork来吩,xUnit的類庫敢辩,提供了對外的功能方法、工具類弟疆、api等
  • TestCase(具體的測試用例)去使用framwork
  • TestCase執(zhí)行后會有TestResult
  • 使用TestSuite控制TestCase的組合
  • TestRunner執(zhí)行器戚长,負責執(zhí)行case
  • TestListener過程監(jiān)聽,監(jiān)聽case成功失敗以及數(shù)據(jù)結果怠苔,輸出到結果報告中

xUnit用于測試的四要素

  • 測試目標(對象)
  • 測試集
  • 測試執(zhí)行(過程)
  • 斷言

JUnit和TestNG

  • 注解 suite>test>group>class>method


  • TestSuite
  • TestCase
  • TestRunner
  • TestResult
  • TestListener

常用斷言

  • assertTrue 驗證是或否
  • assertSame 驗證是否匹配同廉、相似、模糊匹配
  • assertEquals 驗證是否相等

運行策略配置

  • JUnit

    • 運行器 Runwith(*Runner.class)
    • 套件 Suite.SuiteClass({*TestClass.class})
  • TestNG

    • 統(tǒng)一配置文件 Testng.xml
    • 套件 <suite><test></test></suite>

參數(shù)化和數(shù)據(jù)驅動

  • JUnit

    • @Parameterized.Parameters()
    • JUnit參數(shù)化是類級別的柑司,即每循環(huán)一次都將執(zhí)行這個類迫肖,包括@Before和@After
  • TestNG

    • <Parameter name="" value="">
    • @DataProvider(name="param")
    • @Test(dataprovider="param")
    • TestNG參數(shù)化是在測試級別的
  • 數(shù)據(jù)驅動能力

    • 第一級能力參數(shù)化
    • 第二級能力數(shù)據(jù)化
    • 第三級能力業(yè)務邏輯數(shù)據(jù)化
    • 第四級能力測試框架數(shù)據(jù)化

監(jiān)聽

  • ITestNGListener
  • IReporter

拓展性

  • JUnit
    • @Runwith(SpringRunner.class)
    • @SpringBootTest

與Maven結合的項目實踐

  • 開源項目準備
  • 應用實戰(zhàn)

    • 開發(fā)寫了一個用戶登陸的方法隅津,也就是我們的測試對象
    //XunitDemo/src/main/java/DemoXunit/Login.java
    public class Login {
      public static boolean isLogin = false;
      public String userLogin(String name,String pwd){
          if(name == null || name.equals("") || pwd ==null || pwd.equals("")){
              System.out.println("用戶名或密碼為空");
              isLogin = false;
              return "用戶名或密碼不能為空";
    
          }else if (name == "admin" || name.equals("admin")){
              System.out.println("管理員");
              isLogin = true;
              return "歡迎管理員";
    
          }else{
              System.out.println("正常用戶");
              isLogin = true;
              return "歡迎"+name;
          }
      }
    //開發(fā)自測
       public static void main(String[] args){
            Login login = new Login();
            login.userLogin("","");
      }
    }
    
    • 第一個測試用例
    //src/main/test/LoginTest.java
    public class LoginTest {
      @Test
      public void testLogin(){
          Login login = new Login();
          //執(zhí)行用例
          String actual = login.userLogin("zhangsan","123456");
          //斷言
          Assert.assertEquals(actual, "歡迎zhangsan");
        }
      }
    
    • 覆蓋代碼每個分支
    //src/main/test/TestXunit/LoginTest.java
    public class LoginTest {
     @Test
     public void testUserLogin1(){
         Login login = new Login();
         //執(zhí)行用例
         String actual = login.userLogin("zhangsan","123456");
         //斷言
         Assert.assertEquals(actual, "歡迎zhangsan");
       }
     @Test
     public void testUserLogin2(){
         Login login = new Login();
         String actual = login.userLogin("","");
         Assert.assertEquals(actual, "用戶名或密碼不能為空");
       }
     @Test
     public void testUserLogin3(){
         Login login = new Login();
         String actual = login.userLogin("admin","");
         Assert.assertEquals(actual, "用戶名或密碼不能為空");
       }
     @Test
     public void testUserLogin4(){
         Login login = new Login();
         String actual = login.userLogin("admin","12345");
         Assert.assertEquals(actual, "歡迎管理員");
       }
      }
    
    • 運行配置
      一個testng.xml只能配置一個<suite>,一個<suite>可以有多個<test>,一個<test>有多個<group>,一個<group>下有多個<class>,一個<class>下有多個<methods>劲室,一個<methods>下有多個<include>或<exclude>

      • 如何生成testng.xml文件
        方法一:plugins ---> 搜索插件creat testng xml ---> 安裝 ---> 項目文件右鍵 ---> creat testng xml
        方法二:新建testng.xml文件饥瓷,復制粘貼
        <?xml version="1.0" encoding="UTF-8"?>
        <!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
        <suite name="All Test Suite">
            <test verbose="2" preserve-order="true" name="/Users/leitianxiao/Documents/xuint">
            </test>
        </suite>
        
      • testng.xml
      <?xml version="1.0" encoding="UTF-8"?>
      <!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
      <suite name="All Test Suite">
          <test verbose="1" preserve-order="true" name="/Users/leitianxiao/Documents/XunitDemo">
             <groups>
                 <dependencies>
                     <group name="group1" depends-on="">
                         <classes>
                             <class name="LoginTest">
                                 <methods>
                                     <include name="testUserLogin1"></include>
                                     <include name="testUserLogin2"></include>
                                     <include name="testUserLogin3"></include>
                                     <include name="testUserLogin4"></include>
                                 </methods>
                             </class>
                         </classes>
                     </group>
                 </dependencies>
             </groups>
          </test>
      </suite>
      
    • 監(jiān)聽

      • 配置test-output
        Run --->Edit Configuarations --->Listeners ---> 勾選use default report

      • 查看report
        Run過testng.xml文件后,會在項目文件夾下生成test-output文件夾
        打開test-output/index.html痹籍,即TestNG reports
        test-output/old/index.html是舊版report

    • 參數(shù)化

      • 測試用例代碼抽象化
      //src/main/test/TestXunit/LoginTest.java
      public class LoginTest {
      @Test
      public void testUserLogin(String name,String pwd,String expect){
          Login login = new Login();
          String actual = login.userLogin(name,pwd);
          Assert.assertEquals(actual, expect);
        }
      }
      
      • 參數(shù)化
        借助 @Parameters讀取testng.xml中參數(shù)

        //src/main/test/TestXunit/LoginTest.java
        public class LoginTest {
            @Parameters({"name","pwd","expect"})
            @Test
            public void testUserLogin(String name,String pwd,String expect){
                Login login = new Login();
                String actual = login.userLogin(name,pwd);
                Assert.assertEquals(actual, expect);
            }
        }
        
        <!--testng.xml-->
        <?xml version="1.0" encoding="UTF-8"?>
        <!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
        <suite name="All Test Suite">
            <test verbose="2" preserve-order="true" name="/Users/leitianxiao/Documents/XunitDemo">
                <groups>
                    <dependencies>
                        <group name="group1" depends-on="">
                            <classes>
                                <class name="TestXunit.LoginTest">
                                    <methods>
                                        <parameter name="name" value="zhangsan"></parameter>
                                        <parameter name="pwd" value="123456"></parameter>
                                        <parameter name="expect" value="歡迎zhangsan"></parameter>
                                    </methods>
                                </class>
                            </classes>
                        </group>
                    </dependencies>
                </groups>
            </test>
        </suite>    
        
    • 復雜測試數(shù)據(jù)使用@DataProvider注解傳參

      • 創(chuàng)建一個專門存放測試數(shù)據(jù)的package:XunitDemo/src/test/java/LoginData

      • 在DataParams中新建一個存放測試數(shù)據(jù)的class:LoginParams.java,固定格式晦鞋,每個花括號對應一條測試用例數(shù)據(jù)的name蹲缠、pwd、expect

        //test/java/DataParams/LoginParams.java
        public class LoginParams {
           /**
           * 小技巧:打/** 然后command+回車悠垛,寫注釋线定,方便自己和別人查看代碼
           *
           * 提供用戶登陸測試數(shù)據(jù)
           * @return
           */
          @DataProvider
          public Object[][] getUsers(){
              return new Object[][]{
                    {"zhangsan","123456","歡迎zhangsan"},
                    {"","","用戶名或密碼不能為空"},
                    {"admin","","用戶名或密碼不能為空"},
                    {"admin","12345","歡迎管理員"}
                };
            }
        }
        
      • 測試數(shù)據(jù)關聯(lián)到測試用例

        //src/main/test/TestXunit/LoginTest.java
        public class LoginTest {
          //dataProvider指定方法,dataProviderClass指定該方法所在的類
          @Test(dataProvider = "getUsers",dataProviderClass = LoginParams.class)
          public void testUserLogin(String name,String pwd,String expect){
              Login login = new Login();
              String actual = login.userLogin(name,pwd);
              Assert.assertEquals(actual, expect);
            }
        }
        
      • testng.xml運行配置

        <?xml version="1.0" encoding="UTF-8"?>
        <!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
        <suite name="All Test Suite">
          <test verbose="2" preserve-order="true" name="/Users/leitianxiao/Documents/XunitDemo">
              <groups>
                  <dependencies>
                      <group name="group1" depends-on="">
                          <classes>
                              <class name="LoginTest">
                                  <methods>
                                      <include name="testUserLogin"></include>
                                  </methods>
                              </class>
                          </classes>
                      </group>
                  </dependencies>
              </groups>
            </test>
        </suite>
        

實戰(zhàn)進階(關聯(lián)性測試)

  • 開發(fā)寫的一個新方法
//src/main/java/DemoXunit/Shopping.java
public class Shopping {
    Login login = new Login();
    Products pro;

    /**
     * 通過ID查找商品價格
     *
     * @param proId 商品ID
     * @return 商品價格 确买; -1 沒有此商品斤讥;-2 未登錄
     */
    public int getPrice(int proId) {
        if (login.isLogin == true) {
            if (proId <= 0) { //1
                return -1;
            } else {
                Products p = Products.getPro(proId);

                return p.getPrice();
            }
        } else {

            return -2;
        }
    }
      //自測
    public static void main(String[] args) {
        Shopping shopping = new Shopping();
        Login login = new Login();
        login.userLogin("", "123456");
        shopping.getPrice(1);
    }
}
  • 寫了一個枚舉類,假裝是數(shù)據(jù)庫
//src/main/java/DemoXunit/Products.java
//模擬數(shù)據(jù)庫
public enum Products {
    //圍巾的商品ID是1湾趾,商品名稱是“圍巾”芭商,價格是200,庫存是0
    WEIJIN(1,"圍巾",200,0),
   //帽子的商品ID是2搀缠,商品名稱是“帽子”铛楣,價格是200,庫存是10
    MAOZI(2,"帽子",120,10),
    //手套的商品ID是3艺普,商品名稱是“手套”簸州,價格是80鉴竭,庫存是1
    SHOUTAO(3,"手套",80,1);

    private int proId;  //商品ID
    private String proName;  //商品名稱
    private int price;  //價格
    private int count;  //庫存

    private Products(int proId, String proName, int price, int count){
        this.proId = proId;
        this.proName = proName;
        this.price = price;
        this.count = count;
    }

    /**
     * 通過商品ID 獲取商品信息
     * @param proId
     * @return
     */
    public static Products getPro(int proId){

        for(Products product : Products.values()){
            if(product.getProId() == proId){
                return product;
            }
        }
        return null;
    }

  • 如何將登錄關聯(lián)到getPrice方法的測試

    • 方式一:通過配置執(zhí)行順序
      @BeforeClass,@AfterClass,@BeforeMethod,@AfterMethod...配合testng.xml

    • 方式二:寫一個新的測試方法,包括了登錄

    //src/test/java/TestXunit/ShoppingTest.java
    public class ShoppingTest {
        @Test(dataProvider ="getProPrice",dataProviderClass = ShoppingParams.class)
        public void testGetPrice(String name,String pwd,int proId,int expect){
            //登錄
            Login login=new Login();
            login.userLogin(name,pwd);
            //查詢商品價格
            Shopping shopping=new Shopping();
            int price=shopping.getPrice(proId);
            Assert.assertEquals(price,expect);
        }
    }
    

    參數(shù)化

    public class ShoppingParams {
      @DataProvider
      public Object[][] getProPrice(){
          return new Object[][]{
                  {"","",1,-2},
                  {"admin","",2,-2},
                  {"","",0,-2},
                  {"zhangsan","12345",1,200},
                  {"admin","12345",2,120},
                  {"lisi","123",3,80},
                  {"wang","123",0,-1}
    
          };
      }
    

    運行配置

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
    <suite name="All Test Suite">
        <test verbose="2" preserve-order="true" name="/Users/leitianxiao/Documents/XunitDemo">
            <groups>
                <dependencies>
                    <group name="group1" depends-on="">
                        <classes>
                            <class name="TestXunit.LoginTest">
                                <methods>
                                    <include name="testUserLogin"></include>
                                </methods>
                            </class>
                            <class name="TestXunit.ShoppingTest">
                                <methods>
                                    <include name="testGetPrice"></include>
                                </methods>
                            </class>
                        </classes>
                    </group>
                </dependencies>
            </groups>
        </test>
    </suite>
    
  • TestNG與Surefire插件引入
    • 什么是Surefire插件

      • 它是一個用于mvn 生命周期的測試階段的插件,可以通過一些參數(shù)設置方便的在testNG或junit下對測試階段進行自定義岸浑。
      • testng是對測試用例管理搏存,Surefire是對testng進行管理、對測試用例集(多個suite)的管理矢洲。
      • 在jenkin上做持續(xù)集成璧眠,命令mvn surefire:test,執(zhí)行surefire中配置的testng.xml中的用例
    • pom文件添加依賴

       <build>
          <plugins>
              <plugin>
                  <groupId>org.apache.maven.plugins</groupId>
                  <artifactId>maven-surefire-plugin</artifactId>
                  <version>2.19.1</version>
                  <configuration>
                      <suiteXmlFiles>
                          <suiteXmlFile>testng.xml</suiteXmlFile>
                      </suiteXmlFiles>
                  </configuration>
              </plugin>
          </plugins>
      </build>
      

Allure2測試報告框架

  • JUnit style xml報告
  • mvn surefire插件的html報告
  • allure2 多語言測試報告
Allure

1.Allure工作機制

  • 在測試框架中添加allure的依賴和配置
  • 執(zhí)行測試用例
  • 生成allure-results
  • allure generate allure-result -o allure-report

2.Allure2安裝

  • windows平臺借助scoop安裝 scoop install allure
  • macOS平臺借助brew安裝 brew install allure
  • 命令行輸入allure --version測試是否安裝成功

3.使用Allure2

在構建報告之前兵钮,您需要運行測試以獲取一些基本的測試報告數(shù)據(jù)蛆橡。通常,它可能是由幾乎每個流行的測試框架生成的junit樣式的xml報告掘譬。

  • 如使用surefire插件運行測試用例mvn surefire:test生成了target/surefire-reports
    使用命令:allure serve /home/path/to/project/target/surefire-reports/

  • 這是最簡單的使用方式泰演。

4.Allure報告結構
官網(wǎng)文檔介紹:https://docs.qameta.io/allure/#_report_structure

5.Allure2進階使用 (TestNG)
官方文檔:https://docs.qameta.io/allure/#_testng

  • pom.xml文件添加依賴:
    其中LAST_VERSION表示最新版本號,查閱文檔即可葱轩。
    <properties>
        <aspectj.version>1.8.10</aspectj.version>
    </properties>
    
    <dependencies>
        <dependency>
            <groupId>io.qameta.allure</groupId>
            <artifactId>allure-testng</artifactId>
            <version>LAST_VERSION</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
    
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.20</version>
                <configuration>
                    <argLine>
                      -javaagent:"${settings.localRepository}/org/aspectj/aspectjweaver/${aspectj.version}/aspectjweaver-${aspectj.version}.jar"
                    </argLine>
                </configuration>
                <dependencies>
                    <dependency>
                        <groupId>org.aspectj</groupId>
                        <artifactId>aspectjweaver</artifactId>
                        <version>${aspectj.version}</version>
                    </dependency>
                </dependencies>
            </plugin>
        </plugins>
    </build>
    
  • 運行構建mvn clean test,Allure結果將出現(xiàn)在target/allure-results文件夾中睦焕。
  • 生成html報告并在Web瀏覽器中自動打開它:allure serve target/allure-results

6.Allure的特性
官方文檔:

  • Description:通過@TestDescription給測試方法添加人性化的命名

  • @Description:通過@Description給每個測試方法添加詳細說明

  • @Link:將測試鏈接到某些資源,如測試管理系統(tǒng)

  • @Severity:用于按嚴重性確定測試方法的優(yōu)先級

  • @Steps:步驟是構成測試場景的任何操作靴拱。使用@Step注釋來注釋相應的方法垃喊,每個步驟都有一個名稱。

  • @Attachment:附件袜炕,使用@Attachment注釋的方法本谜,該方法返回String或byte [],更方便的是使用Allure輔助方法Allure.addAttachment()

    public class LoginTest {
      @Description("購物系統(tǒng)用戶登錄單元測試")
      @Issue("123")
      @Link("https://github.com/allure-framework/allure2/")
      @Test(dataProvider = "getUsers",dataProviderClass = LoginParams.class,description = "用戶登錄測試")
      @Step("步驟1")
      @Severity(SeverityLevel.NORMAL)
      public void testUserLogin(String name,String pwd,String expect){
          Login login = new Login();
          String actual = login.userLogin(name,pwd);
          //使用Allure.addAttachment
          try {
              Allure.addAttachment("demo pic","image/jpeg",new FileInputStream("/Users/leitianxiao/Downloads/02.jpeg"),".jpeg");
          } catch (FileNotFoundException e) {
              e.printStackTrace();
          }
          Assert.assertEquals(actual, expect);
      }
    }
    

7.生成靜態(tài)報告

  • 使用allure serve target/allure-results生成的是臨時報告
  • 命令allure generate -c allure-results偎窘,會在項目文件夾下生成新文件夾allure-report乌助,打開allure-report/widgets/index.html,即靜態(tài)測試報告

遺留問題待更新

  1. 還有什么方法獲取數(shù)據(jù)源陌知,比如需要2萬條測試數(shù)據(jù)他托?
    csv文件讀取、讀取數(shù)據(jù)庫仆葡、yaml文件讀取
最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末赏参,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子沿盅,更是在濱河造成了極大的恐慌把篓,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,826評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件腰涧,死亡現(xiàn)場離奇詭異纸俭,居然都是意外死亡,警方通過查閱死者的電腦和手機南窗,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評論 3 395
  • 文/潘曉璐 我一進店門揍很,熙熙樓的掌柜王于貴愁眉苦臉地迎上來郎楼,“玉大人,你說我怎么就攤上這事窒悔∥卦” “怎么了?”我有些...
    開封第一講書人閱讀 164,234評論 0 354
  • 文/不壞的土叔 我叫張陵简珠,是天一觀的道長阶界。 經(jīng)常有香客問我,道長聋庵,這世上最難降的妖魔是什么膘融? 我笑而不...
    開封第一講書人閱讀 58,562評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮祭玉,結果婚禮上氧映,老公的妹妹穿的比我還像新娘。我一直安慰自己脱货,他們只是感情好岛都,可當我...
    茶點故事閱讀 67,611評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著振峻,像睡著了一般臼疫。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上扣孟,一...
    開封第一講書人閱讀 51,482評論 1 302
  • 那天烫堤,我揣著相機與錄音,去河邊找鬼凤价。 笑死塔逃,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的料仗。 我是一名探鬼主播,決...
    沈念sama閱讀 40,271評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼伏蚊,長吁一口氣:“原來是場噩夢啊……” “哼立轧!你這毒婦竟也來了?” 一聲冷哼從身側響起躏吊,我...
    開封第一講書人閱讀 39,166評論 0 276
  • 序言:老撾萬榮一對情侶失蹤氛改,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后比伏,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體胜卤,經(jīng)...
    沈念sama閱讀 45,608評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,814評論 3 336
  • 正文 我和宋清朗相戀三年赁项,在試婚紗的時候發(fā)現(xiàn)自己被綠了葛躏。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片澈段。...
    茶點故事閱讀 39,926評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖舰攒,靈堂內(nèi)的尸體忽然破棺而出败富,到底是詐尸還是另有隱情,我是刑警寧澤摩窃,帶...
    沈念sama閱讀 35,644評論 5 346
  • 正文 年R本政府宣布兽叮,位于F島的核電站,受9級特大地震影響猾愿,放射性物質發(fā)生泄漏鹦聪。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,249評論 3 329
  • 文/蒙蒙 一蒂秘、第九天 我趴在偏房一處隱蔽的房頂上張望泽本。 院中可真熱鬧,春花似錦材彪、人聲如沸观挎。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,866評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽嘁捷。三九已至,卻和暖如春显熏,著一層夾襖步出監(jiān)牢的瞬間雄嚣,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,991評論 1 269
  • 我被黑心中介騙來泰國打工喘蟆, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留缓升,地道東北人。 一個月前我還...
    沈念sama閱讀 48,063評論 3 370
  • 正文 我出身青樓蕴轨,卻偏偏與公主長得像港谊,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子橙弱,可洞房花燭夜當晚...
    茶點故事閱讀 44,871評論 2 354

推薦閱讀更多精彩內(nèi)容