基于xUnit的单元测试管理(Java语言)

读书笔记,更新到学完为止

什么是xUnit

  • xUnit是各种代码驱动测试框架的统称,这些框架可以测试软件的不同内容
  • 主要优点:提供了一个自动化测试的解决方案,不需要多次编写重复的测试代码,也不必记录测试的结果。
  • Java语言中典型的xUnit是JUnit和TestNG,Python语言中是UnitTest、PyTest。

xUnit的组成

  • 底层是xUnit的framwork,xUnit的类库,提供了对外的功能方法、工具类、api等
  • TestCase(具体的测试用例)去使用framwork
  • TestCase执行后会有TestResult
  • 使用TestSuite控制TestCase的组合
  • TestRunner执行器,负责执行case
  • TestListener过程监听,监听case成功失败以及数据结果,输出到结果报告中

xUnit用于测试的四要素

  • 测试目标(对象)
  • 测试集
  • 测试执行(过程)
  • 断言

JUnit和TestNG

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


  • TestSuite
  • TestCase
  • TestRunner
  • TestResult
  • TestListener

常用断言

  • assertTrue 验证是或否
  • assertSame 验证是否匹配、相似、模糊匹配
  • assertEquals 验证是否相等

运行策略配置

  • JUnit

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

    • 统一配置文件 Testng.xml
    • 套件 <suite><test></test></suite>

参数化和数据驱动

  • JUnit

    • @Parameterized.Parameters()
    • JUnit参数化是类级别的,即每循环一次都将执行这个类,包括@Before和@After
  • TestNG

    • <Parameter name="" value="">
    • @DataProvider(name="param")
    • @Test(dataprovider="param")
    • TestNG参数化是在测试级别的
  • 数据驱动能力

    • 第一级能力参数化
    • 第二级能力数据化
    • 第三级能力业务逻辑数据化
    • 第四级能力测试框架数据化

监听

  • ITestNGListener
  • IReporter

拓展性

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

与Maven结合的项目实践

  • 应用实战

    • 开发写了一个用户登陆的方法,也就是我们的测试对象
    //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;
          }
      }
    //开发自测
       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();
          //执行用例
          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();
         //执行用例
         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>
      
    • 监听

      • 配置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

    • 参数化

      • 测试用例代码抽象化
      //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);
        }
      }
      
      • 参数化
        借助 @Parameters读取testng.xml中参数

        //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>    
        
    • 复杂测试数据使用@DataProvider注解传参

      • 创建一个专门存放测试数据的package:XunitDemo/src/test/java/LoginData

      • 在DataParams中新建一个存放测试数据的class:LoginParams.java,固定格式,每个花括号对应一条测试用例数据的name、pwd、expect

        //test/java/DataParams/LoginParams.java
        public class LoginParams {
           /**
           * 小技巧:打/** 然后command+回车,写注释,方便自己和别人查看代码
           *
           * 提供用户登陆测试数据
           * @return
           */
          @DataProvider
          public Object[][] getUsers(){
              return new Object[][]{
                    {"zhangsan","123456","欢迎zhangsan"},
                    {"","","用户名或密码不能为空"},
                    {"admin","","用户名或密码不能为空"},
                    {"admin","12345","欢迎管理员"}
                };
            }
        }
        
      • 测试数据关联到测试用例

        //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>
        

实战进阶(关联性测试)

  • 开发写的一个新方法
//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);
    }
}
  • 写了一个枚举类,假装是数据库
//src/main/java/DemoXunit/Products.java
//模拟数据库
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;
    }

  • 如何将登录关联到getPrice方法的测试

    • 方式一:通过配置执行顺序
      @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);
        }
    }
    

    参数化

    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 生命周期的测试阶段的插件,可以通过一些参数设置方便的在testNG或junit下对测试阶段进行自定义。
      • testng是对测试用例管理,Surefire是对testng进行管理、对测试用例集(多个suite)的管理。
      • 在jenkin上做持续集成,命令mvn surefire:test,执行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的依赖和配置
  • 执行测试用例
  • 生成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

在构建报告之前,您需要运行测试以获取一些基本的测试报告数据。通常,它可能是由几乎每个流行的测试框架生成的junit样式的xml报告。

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

  • 这是最简单的使用方式。

4.Allure报告结构
官网文档介绍: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结果将出现在target/allure-results文件夹中。
  • 生成html报告并在Web浏览器中自动打开它:allure serve target/allure-results

6.Allure的特性
官方文档:

  • Description:通过@TestDescription给测试方法添加人性化的命名

  • @Description:通过@Description给每个测试方法添加详细说明

  • @Link:将测试链接到某些资源,如测试管理系统

  • @Severity:用于按严重性确定测试方法的优先级

  • @Steps:步骤是构成测试场景的任何操作。使用@Step注释来注释相应的方法,每个步骤都有一个名称。

  • @Attachment:附件,使用@Attachment注释的方法,该方法返回String或byte [],更方便的是使用Allure辅助方法Allure.addAttachment()

    public class LoginTest {
      @Description("购物系统用户登录单元测试")
      @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.生成静态报告

  • 使用allure serve target/allure-results生成的是临时报告
  • 命令allure generate -c allure-results,会在项目文件夹下生成新文件夹allure-report,打开allure-report/widgets/index.html,即静态测试报告

遗留问题待更新

  1. 还有什么方法获取数据源,比如需要2万条测试数据?
    csv文件读取、读取数据库、yaml文件读取
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,287评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,346评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,277评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,132评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,147评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,106评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,019评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,862评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,301评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,521评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,682评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,405评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,996评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,651评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,803评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,674评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,563评论 2 352

推荐阅读更多精彩内容