• 如何利用 xUnit 框架对测试用例进行维护?


    本文为霍格沃兹测试学院优秀学员 Junit 学习笔记。测试开发技能进阶,文末加群。

    1、xUnit 是什么

    先看 Wikipedia 上的解释:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0Jm3tXGg-1656582570014)(upload://8jWz3HKy5I2etJ9vjD2dbbEetbj.png)]

    xUnit 是一系列测试框架的统称,最开始来源于一个叫做 Smalltalk 的 SUnit 框架,现在各种面向对象的语言,如 Java、Python 的鼻祖就是 Smalltalk,后来这些语言都借助了 Sunit 框架的理念,有很多通用的规范和特征,也就统称为 xUnit。

    1.1 xUnit 框架体系
    • Java : JUnit、TestNG
    • Python : UnitTest、PyTest
    1.2 xUnit 的共同特征
    • Test Runner :测试的运行器
    • Test Case :测试用例
    • Test Fixtures : 测试夹具 / 治具,用来管理测试用例的执行
    • Test Suites :测试套件,用来编排测试用例
    • Test Execution:测试执行,以何种顺序执行
    • Test Result Formatter:测试结果,具备相同的格式,可被整合
    • Assertions:断言

    2、从 Junit4 开启 xUnit 框架之旅

    2.1 为何从 Junit4 开始
    • Junit4 仍然是 99% 的研发工程师的首选框架,方便测试工程师与研发工程师交流(拉关系~~);
    • TestNG 的使用多用于测试工程师;
    • Junit5 还未大规模普及(最推荐的框架,成熟、好用、研发测试通用);
    • 很多框架基于 Junit4 定制;
    2.2 测试用例的核心元素
    • 测试用例的名字:特性方法名
    • 测试用例描述与标签:注解
    • 测试用例的容器:类或者套件
    • 测试过程
      • 单元测试
      • Web 自动化测试 Selenium
      • App 自动化测试 Appium
      • 接口自动化测试 RestAssured
    • 测试断言
    2.3 基本 demo 运行

    1)创建 maven 工程 XUnit,pom.xml 中添加 Junit 依赖;

    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    2)src/test/java 下创建测试类 Junit4DemoTest

    注意

    • 测试类要以 Test 开头或者结尾
    • maven auto-import
    • src/main/java 存放应用实现代码
    • src/test/java 存放单元测试
    • 单元测试的原则之一:用例可以独立运行

    基本测试 demo 运行:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ubp1GqGk-1656582570015)(upload://zYDXBDKdc06b8PaduzztmAsbEE8.jpeg)]

    运行结果:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1d162j50-1656582570016)(upload://oiiQ0PkfpF9Fc8mGMb8tDOZkHWX.png)]

    2.4 用例间的执行顺序

    Junit4:

    • Default 取决于反射方法获得的列表,顺序固定(不保险)
    • @FixMethodOrder(MethodSorters.JVM) 顺序可能变化
    • @FixMethodOrder(MethodSorters.NAME_ASCENDING) 按照名字 ASCII 顺序(稳定常用,建议使用)

    TestNG、Junit5:

    • 可以通过注解设置顺序 Order

    顺序演示

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CBvFO3Yu-1656582570016)(upload://msjUplbkg01rWq37cHhSUzUl0t4.png)]

    运行结果:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bIF72WSf-1656582570016)(upload://1Abj7o2tq54zCBiTyWzEArJtTCi.png)]

    2.5 测试套件的执行顺序支持

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UpFcTne0-1656582570016)(upload://iaTb7N3vkcqE4i1BYsL5nFpEuBN.png)]

    • Junit4:
      • @BeforeClass、@AfterClass
      • @Before、@After
    • TestNG:
      • @BeforeClass
      • @BeforeMethod
      • BeforeGroup、@BeforeSuite
    • Junit5:
      • @BeforeClass
      • @BeforeEach

    实操演示 1

    • 在用例执行前后增加 @Before 和 @After:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-snGUEenv-1656582570017)(upload://q6kbgulKtspFvzyhZKhpzz8xHpU.png)]

    运行结果:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hHnEkwd5-1656582570017)(upload://3Tc15AvkIxjXVSsfGpNDNIsHMEL.png)]

    实操演示 2

    • 再增加 @BeforeClass 和 @AfterClass

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j7rpQfjE-1656582570017)(upload://5ca75hIUQOExWC92GpgCESlhs5X.png)]

    运行结果:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LCu7BzlM-1656582570018)(upload://lzyWt6d5iw4A0MlGwd22kPaF6jY.png)]

    2.5 用例管理的实际应用举例——App 自动化测试用例管理
    • 基类的 @BeforeClass:
      • 配置读取、配置 Capability、初始化 driver、安装 App,PageObject 初始化
    • 集成的子类执行流程
      • @Before:启动并进入特定界面
      • @Test:测试用例执行
      • @After:回退到入口
      • @BeforeClass:进图特定的 tab 子功能页面
      • @AfterClass:关闭 app
    • 基类的 @AfterClass
      • driver.quit
    2.6 继承关系下的测试流程

    流程顺序:

    • 父类 @BeforeClass
    • 子类 @BeforeClass
    • 父类 @Before
    • 子类 @Before
    • 子类 @Test
    • 父类 @Test
    • 子类 @After
    • 父类 @After
    • 子类 @AfterClass
    • 父类 @AfterClass

    实操演示 1

    • 现在创建一个子类 Junit4DemoChildrenTest,继承 Junit4DemoTest,然后实现和父类一样的方法并运行子类:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-a0RQMTCU-1656582570018)(upload://8frvbr4gvqZ55R13geeHw2UAPpH.png)]

    运行结果:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WD4QzSPj-1656582570018)(upload://jkbT7odg8Orc0XpZGTEAalnyDiR.png)]

    从运行结果中我们可以看到,子类会将与父类中一样的方法进行覆盖,只执行子类中的方法

    实操演示 2

    • 现在将子类中的方法名进行修改,使其与父类方法名不同,再运行子类:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-A6md8fLw-1656582570018)(upload://kCoxnAJG9V3mgzrRt6yMhBqWFpC.png)]

    运行结果:

    我是 @BeforeClass,我是第一步
    我是 Children@BeforeClass,我是第一步
    
    我是 @Before,用例执行前先到我这
    我是 Children@Before,用例执行前先到我这
    Children testDemoB
    我是 Children@After, 用例执行后到我这
    我是 @After, 用例执行后到我这
    
    
    
    我是 @Before,用例执行前先到我这
    我是 Children@Before,用例执行前先到我这
    Children testDemoA
    我是 Children@After, 用例执行后到我这
    我是 @After, 用例执行后到我这
    
    
    
    java.lang.AssertionError
        at org.junit.Assert.fail(Assert.java:86)
        at org.junit.Assert.assertTrue(Assert.java:41)
        at org.junit.Assert.assertTrue(Assert.java:52)
        ...
        at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
        at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
        at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
    
    
    
    我是 @Before,用例执行前先到我这
    我是 Children@Before,用例执行前先到我这
    Children testDemoC
    我是 Children@After, 用例执行后到我这
    我是 @After, 用例执行后到我这
    
    
    
    我是 @Before,用例执行前先到我这
    我是 Children@Before,用例执行前先到我这
    testDemoA
    我是 Children@After, 用例执行后到我这
    我是 @After, 用例执行后到我这
    
    
    
    java.lang.AssertionError
        at org.junit.Assert.fail(Assert.java:86)
        at org.junit.Assert.assertTrue(Assert.java:41)
        at org.junit.Assert.assertTrue(Assert.java:52)
        ...
        at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
        at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
    
    
    
    我是 @Before,用例执行前先到我这
    我是 Children@Before,用例执行前先到我这
    testDemoB
    我是 Children@After, 用例执行后到我这
    我是 @After, 用例执行后到我这
    
    
    
    我是 @Before,用例执行前先到我这
    我是 Children@Before,用例执行前先到我这
    testDemoC
    我是 Children@After, 用例执行后到我这
    我是 @After, 用例执行后到我这
    
    我是 Children@AfterClass,我是最后一步
    我是 @AfterClass,我是最后一步
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    2.7 测试套件
    • RunWith
    • SuiteClasses
    • class

    实操演示

    • 新建一个子类 Junit4DemoChildren2Test,继承 Junit4DemoTest

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YiG4Synz-1656582570019)(upload://xgCCKtSCs4yhIwNvlm6XD7NHshU.png)]

    • 再建一个测试类 SuitesTest, 写上注解 @RunWith(Suite.class), 表明这是一个测试套件,是多个测试类的一个集合,一个容器;
    • 然后利用注解 @Suite.SuiteClasses 来设置测试类集合,设置测试类执行的顺序

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-A8G42KnZ-1656582570019)(upload://z2R0uLXJxd30u6jrZZfIo1YfRkz.png)]

    运行结果:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-spUDPrXE-1656582570019)(upload://ccGp7txjiWN4D7NcCQReXf5CqFT.png)]

    我是 @Before,用例执行前先到我这
    我是 Children2@Before,用例执行前先到我这
    Children2 testDemoC
    我是 Children2@After, 用例执行后到我这
    我是 @After, 用例执行后到我这
    
    
    我是 @Before,用例执行前先到我这
    我是 Children2@Before,用例执行前先到我这
    Children2 testDemoB
    我是 Children2@After, 用例执行后到我这
    我是 @After, 用例执行后到我这
    
    
    我是 @Before,用例执行前先到我这
    我是 Children2@Before,用例执行前先到我这
    Children2 testDemoA
    我是 Children2@After, 用例执行后到我这
    我是 @After, 用例执行后到我这
    
    
    
    java.lang.AssertionError
        at org.junit.Assert.fail(Assert.java:86)
        at org.junit.Assert.assertTrue(Assert.java:41)
        ...
        at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
        at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
    
    
    我是 @Before,用例执行前先到我这
    我是 Children2@Before,用例执行前先到我这
    testDemoA
    我是 Children2@After, 用例执行后到我这
    我是 @After, 用例执行后到我这
    
    
    
    java.lang.AssertionError
        at org.junit.Assert.fail(Assert.java:86)
        at org.junit.Assert.assertTrue(Assert.java:41)
        ...
        at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
        at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
    
    
    我是 @Before,用例执行前先到我这
    我是 Children2@Before,用例执行前先到我这
    testDemoB
    我是 Children2@After, 用例执行后到我这
    我是 @After, 用例执行后到我这
    
    
    我是 @Before,用例执行前先到我这
    我是 Children2@Before,用例执行前先到我这
    testDemoC
    我是 Children2@After, 用例执行后到我这
    我是 @After, 用例执行后到我这
    
    我是 Children2@AfterClass,我是最后一步
    我是 @AfterClass,我是最后一步
    我是 @BeforeClass,我是第一步
    
    
    我是 @Before,用例执行前先到我这
    testDemoA
    我是 @After, 用例执行后到我这
    
    
    
    java.lang.AssertionError
        at org.junit.Assert.fail(Assert.java:86)
        at org.junit.Assert.assertTrue(Assert.java:41)
        ...
        at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
    
    
    我是 @Before,用例执行前先到我这
    testDemoB
    我是 @After, 用例执行后到我这
    
    
    我是 @Before,用例执行前先到我这
    testDemoC
    我是 @After, 用例执行后到我这
    
    我是 @AfterClass,我是最后一步
    我是 @BeforeClass,我是第一步
    我是 Children@BeforeClass,我是第一步
    
    
    我是 @Before,用例执行前先到我这
    我是 Children@Before,用例执行前先到我这
    Children testDemoB
    我是 Children@After, 用例执行后到我这
    我是 @After, 用例执行后到我这
    
    
    我是 @Before,用例执行前先到我这
    我是 Children@Before,用例执行前先到我这
    Children testDemoA
    我是 Children@After, 用例执行后到我这
    我是 @After, 用例执行后到我这
    
    
    
    java.lang.AssertionError
        at org.junit.Assert.fail(Assert.java:86)
        at org.junit.Assert.assertTrue(Assert.java:41)
        at org.junit.Assert.assertTrue(Assert.java:52)
        ...
        at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
    
    
    我是 @Before,用例执行前先到我这
    我是 Children@Before,用例执行前先到我这
    Children testDemoC
    我是 Children@After, 用例执行后到我这
    我是 @After, 用例执行后到我这
    
    
    我是 @Before,用例执行前先到我这
    我是 Children@Before,用例执行前先到我这
    testDemoA
    我是 Children@After, 用例执行后到我这
    我是 @After, 用例执行后到我这
    
    
    
    java.lang.AssertionError
        at org.junit.Assert.fail(Assert.java:86)
        at org.junit.Assert.assertTrue(Assert.java:41)
        ...
        at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
    
    
    我是 @Before,用例执行前先到我这
    我是 Children@Before,用例执行前先到我这
    testDemoB
    我是 Children@After, 用例执行后到我这
    我是 @After, 用例执行后到我这
    
    
    我是 @Before,用例执行前先到我这
    我是 Children@Before,用例执行前先到我这
    testDemoC
    我是 Children@After, 用例执行后到我这
    我是 @After, 用例执行后到我这
    
    我是 Children@AfterClass,我是最后一步
    我是 @AfterClass,我是最后一步
    
    我是 @BeforeClass,我是第一步
    我是 Children2@BeforeClass,我是第一步
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154

    由测试结果可以看到使用套件后,测试过程为 Junit4DemoChildren2Test、Junit4DemoTest、Junit4DemoChildrenTest 的顺序执行

    2.8 分组测试-@Category

    有时候我们需要对一些特定的用例进行分组测试,这个时候就可以用 @Category 来实现
    另外在套件执行类上运用注解:

    @RunWith(Categories.class)  :  固定写法,指明以 Category 方式分组
    @Categories.IncludeCategory(SlowGroup.class) : 指明要执行的测试分组包含哪些
    @Categories.ExcludeCategory(FastGroup.class) : 指明要执行的测试分组不包含哪些
    @Suite.SuiteClasses({ : 指明要执行的测试类
        TestDemo.class
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • @Category 分组需要给定一个标签,以类或者接口都可以,这里创建连个接口 SlowGroupFastGroup
    public interface FastGroup {
    }
    public interface SlowGroup {
    }
    
    • 1
    • 2
    • 3
    • 4
    • 在用例上分别分组为 SlowGroupFastGroupSlowGroup+FastGroup

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0WOYQmtv-1656582570019)(upload://n6QdoTdIy2up4IKNE9sy2FZjoRW.png)]

    • 指明 SlowGroup 组测试执行, FastGroup 组的测试不执行:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AAmOTSBp-1656582570020)(upload://nfEgokppF4mLaLWRb2DfLisAssg.png)]

    测试结果:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Eh7FCv6z-1656582570020)(upload://n8zCx8ZiHJKTOhEBurkbfCpuA5u.png)]

    • 仅指明 SlowGroup 组测试执行

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hucJVimv-1656582570021)(upload://wtwTFWEIySVpVyddixqJBFpXVRP.png)]

    测试结果:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-g8ERo2JH-1656582570021)(upload://eTZVx1X1YlSpuHoAHxJhrXcCLc2.png)]

    • 仅指明不执行的组为 FastGroup

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2gvlVFtF-1656582570021)(upload://tLXtD5E9xT9BD8CDI2q6aPMWqlr.png)]

    测试结果:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4v1YllWF-1656582570022)(upload://6JAXK4DdP9Liru9WGCSM9ANrhoo.png)]

    2.9 参数化 @Paramterized

    有时候我们需要传入测试数据,且数据可能是多组,这个时候就需要使用参数化来传入多组数据进行测试
    Junit4 的参数化稍微有点麻烦:

    1)先在类名上加入注解 @RunWith(Parameterized.class) 表明要以参数化运行

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Stpm5QpX-1656582570022)(upload://84wRy0ZSCyxZ0eVR6ocCFwqXi3M.png)]

    2)用注解 @Parameterized.Parameters 来设定数据源

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7HvcqX8v-1656582570023)(upload://Xk81C3Q6jwrHSE2oO2z4qAqKce.png)]

    3)最后用注解 @Parameterized.Parameter 来指定数据源数据对应的参数

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LSUyLEM5-1656582570023)(upload://tsvQHxvgfEYr6pWFK3jXWnHFL8y.png)]

    4)总览

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0ViR1Kul-1656582570023)(upload://Ad9BU8mxzqynk8K84dntITod6re.png)]

    测试结果:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xjpdcqpI-1656582570024)(upload://z8ccLXiAK5BJQBqsHn450hUQEU8.png)]

    从测试结果可以看到 3 组参数分别传入方法中,方法各执行了一次,完成参数化测试

    3、总结-测试用例的顺序

    • 测试用例之间的顺序
    • test fixtures 的顺序
    • 继承顺序
    • 套件之间的顺序

    参考文档链接

    JUnit4 单元测试框架 [https://junit.org/junit4/]
    JUnit5 单元测试框架 [https://junit.org/junit5/]

  • 相关阅读:
    STL::string简单介绍
    【DVWA】19. Insecure CAPTCHA 不安全的验证码(全等级)
    P6698-[BalticOI 2020 Day2]病毒【AC自动机,dp,SPFA】
    MSVCR80.DLL 丢失修复方法:完美解决你的问题!
    python+flask计算机毕业设计RTY个人记账管理系统(程序+开题+论文)
    C++初阶 日期类的实现(上)
    广东省2022下半年软考报名时间已定!
    MySQL——表数据的增、删、改操作
    Python排序算法
    Linux (五)- mv 命令
  • 原文地址:https://blog.csdn.net/hog_ceshiren/article/details/125545477