ZB-054-01自动化测试

自动化测试

为什么需要⾃动化测试

  • 你在接⼿⼀份前⼈留下来的代码的时候,你更喜欢:
    • ⼀份具有完善的⾃动化测试的代码,改完了跑⼀下⾃动化测试
    • ⼀份没有测试覆盖的代码,改完了听天由命

什么是⾃动化测试

  • ⼀段程序代码
    • 不提供具体的程序功能,只保证主要程序功能符合预期
      • 代码界的炊事班?
    • 编写需要(很⾼的)成本
  • 由机器负责的代码质量检查
  • 我们来看看著名的开源项⽬中的⾃动化测试

为什么需要⾃动化测试

  • 代码写完了
  • 怎么保证它是对的
  • 怎么保证它会永远对下去
  • ⻓久来看,没有测试的代码质量是⽆法保证的

测试的意义就是保证代码质量

测试种类有哪些?

常见的测试

  • ⼿⼯测试:最low的测试⽅式
    • 人肉点点点
  • 单元测试:快速检查⼀个类的功能
    • 只测这一个类的功能
  • 集成测试:检查整个系统的功能
    • 把手工的所有过程以代码的方式运行
  • 回归测试:检查新的代码有没有破坏旧的功能
    • 修一个bug把原来的bug触发了
  • 冒烟测试:快速检查代码有没有⼤问题
  • User Accepted Test:⽤户/甲⽅是否接受
    • 验收
  • ⿊盒测试:将整个系统看成⼀个⿊盒进⾏测试
    • 不关心内部细节
  • ⽩盒测试:根据系统的内部细节设计专⽤的测试
  • 压⼒测试:对系统施加⼀定的压⼒,确保系统的稳定性
    • 能扛住多少个用户,一个,10个,10000个?

丰富的自动化测试的仓库

Java中的测试形态

  • TestNG
  • JUnit(现在最流行的测试框架)
    • JUnit 4
    • JUnit 5

maven约定

  • src/main 生产代码
  • src/test 测试代码

IDEA里写一个测试demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.test.demo;

public class Add_Junit4 {
public static int add(int a, int b, int c){
return a + b + c;
}
}


// 按住快捷键 idea 默认的 (如果你没修改) 可以自动生成测试类
command + shift + t
此时你可以选择 测试框架的种类
这里只推荐
junit4 和 junit5

我们先用 junit4

junit4

  • 此时你可以添加 scope 了,因为这些代码仅在测试阶段使用
1
2
3
4
5
6
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>

测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.test.demo;

import org.junit.Assert;
import org.junit.Test;

// junit4 测试方式
public class Add_Junit4Test {

@Test
public void testAdd(){
// 断言 我期望值 3 和 Add_Junit4.add(1,1,1) 的结果是相等的
Assert.assertEquals(3, Add_Junit4.add(1,1,1));
}

@Test
public void testAddFail(){
// 断言 我期望值 4 和 Add_Junit4.add(1,1,1) 的结果是 "不相等的"
Assert.assertNotEquals(4, Add_Junit4.add(1,1,1));
}
}

junit5

1
2
3
4
5
6
7
8
9
10
11
12
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.4.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.4.2</version>
<scope>test</scope>
</dependency>

测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.test.demo;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

class Add_Junit5Test {
@Test
public void testAdd(){
// 断言 我期望值 3 和 Add_Junit5.add(1,1,1) 的结果是相等的
Assertions.assertEquals(3, Add_Junit5.add(1,1,1));
}

@Test
public void testAddFail(){
// 断言 我期望值 4 和 Add_Junit5.add(1,1,1) 的结果是 "不相等的"
Assertions.assertNotEquals(4, Add_Junit5.add(1,1,1));
}
}

junit深入

  • 上面的test类的里每个 @Test的方法不是 static 而是 实例方法
    • 这个对象谁创建的?
    • 这个对象在哪里创建的?怎么看不到

答案是: junit框架帮你做的

  • 首先 junit 通过反射扫描@Test注解的地方,然后通过把它包出来
    • 可以通过在构造器哪里打断点,来验证
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Add_Junit5Test {

public Add_Junit5Test() {
System.out.println("Created!");
}

@Test
public void testAdd(){
// 断言 我期望值 3 和 Add_Junit5.add(1,1,1) 的结果是相等的
Assertions.assertEquals(3, Add_Junit5.add(1,1,1));
}

@Test
public void testAddFail(){
// 断言 我期望值 4 和 Add_Junit5.add(1,1,1) 的结果是 "不相等的"
Assertions.assertNotEquals(4, Add_Junit5.add(1,1,1));
}
}

Q:为什么测试方法是实例方法

  • 答案是:junit 通过反射偷偷帮你创建该类的实例

Q:已知我现在有两个测试用例

  • 原始类:注意add()已经是实例方法了!!!
  • 原始类:注意add()已经是实例方法了!!!
  • 原始类:注意add()已经是实例方法了!!!
1
2
3
4
5
public class Add_Junit5_1 {
public int add(int a, int b, int c){
return a + b + c;
}
}
  • 测试类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Add_Junit5_1Test {

Add_Junit5_1 adder = new Add_Junit5_1();
@BeforeAll
public static void setUpAll(){
System.out.println("在所有的测试用例运行之前运行且只运行一次");
}

@BeforeEach
public void setUp(){
System.out.println("初始化测试类的实例");
}

@Test
public void testAdd(){
Assertions.assertEquals(3, adder.add(1,1,1));
}

@Test
public void testAddFail(){
Assertions.assertNotEquals(4, adder.add(1,1,1));
}
}
  • 这两个测试实例所用的Add_Junit5_1类的 adder 对象是同一个吗?
  • 这两个实例如果是同一个对象,那么它们的调用顺序是怎样的?从上到下?还是从下到上?还是其他顺序?我能改变这个顺序吗?
  • 如果不是同一个对象,那么它们又是什么时候被分别创建的?

答案是:

  • 每次你运行的时候,在测试函数执行之前,junit 会通过反射帮我们创建 Add_Junit5_1类的实例 adder
  • 而且里面所有测试方法用到的Add_Junit5_1类的实例 adder 都是新的
    • 为什么?因为不希望测试方法之间互相干扰
    • 每个测试方法开始之前:期望这个实例对象 是全新的。跟其他测试方法是隔离的,没有依赖关系
  • debug运行 你会发现运行时候测试方法的执行顺序不是按照声明顺序
    • 因为这是java反射顺序不是按照声明顺序

测试的⽣命周期

  • Maven约定:所有放在src/test下的都是测试代码(在 maven test 阶段会被自动识别)
  • Maven的⽣命周期:
    • test
    • integration-test(集成测试)
  • 测试的时候到底发⽣了什么?(mvn test 之后发生了什么)
    • 新的JVM
    • 每个测试⽤例新建⼀个测试类的实例
      • 额外的操作
      • before
      • forEach
      • beforeAll

mvn test 后发生了什么

  • 首先在当前运行环境里:寻找 mvn 命令(mvn 的本质就是 拼一个java命令)
    • java 所有工具都是 帮你拼一个命令行
  • mvn 本质就是一个 jvm
    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
    开始走一个 maven 生命周期 
    clean --> validate --> compile --> test --> ...

    首先会启动一个 jvm,当走到 test 周期的时候它会 重新启动一个 jvm
    原因是 你的测试可能会破坏 jvm 某个属性,因此放在一个新的 jvm 是更安全的 ,隔离的


    验证它:开启新的 jvm

    第一步:在一个测试函数内 睡 60秒
    @Test
    public void testAddFail() throws InterruptedException {
    Thread.sleep(60 * 1000);
    ...
    }


    第二步:命令行运行 jps

    22385 surefirebooter7222466039461078627.jar
    22404 Jps
    20090 KotlinCompileDaemon
    19628
    19965 RemoteMavenServer
    21983 Launcher


    surefirebooter 就是默认测试插件新开的 jvm

编写单元测试

  • @Test
  • Assertion 断⾔
  • Mock 模拟
  • 测试⼀个类/⽅法是否符合预期

编写集成测试

  • 启动测试环境
  • 保证测试环境的隔离
  • 使⽤各类⾃动化⼯具模拟外界输⼊

运⾏⾃动化测试

  • mvn verify/test
  • mvn test -DskipTests