锐单电子商城 , 一站式电子元器件采购平台!
  • 电话:400-990-0325

软件测试review 孙海英老师

时间:2022-09-11 20:00:00 gymb光纤连接器

软件测试与验证

期中考试范围 软件缺陷 逻辑覆盖 控制流测试

全是简答题,英文试卷

二、代码单元测试

动态代码测试:在开发环境中,通过运行被测代码来验证其是否符合预期目标,并尽快发现与目标不同的缺陷

面向代码的动态测试分为代码单元测试和代码接口测试两类

单元测试与集成测试的区别:一是规模,根据测试执行速度的速度来定义,不超过0.1s。另一种是独立性,单元测试不能依赖任何外部资源,使用测试替身

测试替身(Test Double):取代依赖于数据库、网络和文件系统的被替换为真实代码式没有内容,是假的

单元:功能相对独立、规模较小的代码

根据软件测试技术的发展规律,我们将代码测试内容分为两部分:测试技术、测试生成

image-20211011211058502

1.逻辑覆盖标准

逻辑测试:在代码中逻辑表达式结构为了发现代码逻辑结构的缺陷

逻辑结构缺陷:编写代码时错误的可视化反映在逻辑表达上;逻辑表达错误,程序行为不正确

逻辑表达式缺陷类型DNF:

基于逻辑覆盖准则的测试(Logical Coverage Criteria):用于测量代码中逻辑表达式测试的充分性

下图中的蓝色箭头表示A包含B,这意味着B可以发现的缺陷必须由A发现。如果满足上述要求,则必须满足以下要求,但确定覆盖和条件覆盖是特殊的

满足逻辑覆盖准则 ≠ 高质量测试

高质量测试是发现高质量缺陷的测试

语句覆盖Statement Coverage

用来衡量被测代码中句子的执行程度。如果测试集可以使被测代码中的每个句子至少执行一次,则测试集可以满足句子覆盖范围

语句覆盖:

语句覆盖测试案例:

语句覆盖定义在源代码上,所以看源代码

多个测试用例可以在测试集合中增加,使语句覆盖率达到100%,例如3

在逻辑测试中,句子覆盖率是最低的,因为它只是走每个句子,没有测试任何逻辑,如果是 if 里面的 && 改成 || 语句覆盖不能揭示错误

判定覆盖Decision Coverage

在代码中测量判断执行程度,如果测试集合可以使测试代码中的每个判断至少执行一次(每个判断的所有可能结果至少出现一次,例如 if((num1>1)&&(num2==0)) 在认为判决被执行之前,所有的真假结果都被执行了),所以测试集合满足了判断覆盖范围

条件:不包括布尔算子的逻辑表达,如关系表达、布尔变量等

判断:一个或多个条件通过一个或多个布尔算子连接的逻辑表达式

确定覆盖:

覆盖测试案例的判断:

注意每个判断的真假结果至少出现一次,称为执行

确定覆盖范围的缺点:主要取决于逻辑操作符合的正确性。逻辑操作符合的正确性并不意味着逻辑是正确的可能无法发现条件缺陷。 num > 1 写成了 num > -一、不能揭示错误

注意短路操作符 && 和 ||,即当出现了false或true就不走了,MC/DC标准的原因,所以上一个例子的正确结果表应该是

条件覆盖 Condition Coverage

如果测试集合能使测试代码中的每个条件至少执行一次,则测试集合满足条件覆盖

每个条件的含义被执行一次:每个条件的所有可能结果至少出现一次

条件覆盖:

条件覆盖试验用例:

notice:不一定符合判断条件,不一定符合判断条件

判断-条件覆盖 Decision-Condition Coverage(仅了解)

测量代码中的每个判断和构成判断的每个条件的程度。如果测试集可以使测试代码中的每个判断至少执行一次,并且构成判断的每个条件至少执行一次,则测试集满足判断-条件覆盖。

修正判断-条件覆盖

Modified Condition/Decision Coverage,MC/DC

期望构成每个判断的每个条件独立影响整个判断结果

方法:

  1. 用D表示判断,ci表示D的第一个条件

  2. D(ci=true)表示将D中的一切ci使用true替换后的判断表达式

  3. D(ci=false)表示将D中所有ci使用false替换后的判断表达式

  4. 逻辑表达式Dci= D(ci=true)⊕D(ci=false) 可用于计算ci独立影响判断时,其他条件的输入值测试

    不同的算法意味着两边的值不同,所以只有Dci = true证明独立影响

  5. 注意不是每个算法都能找到MC/DC结果

一个简单的例子:

分析:

  1. 第一个公式,c2的值没有改变,一直是真的,随着c1从真变成假,结果也从真变为了假,说明c影响了我们整个判断结果

  2. 第二个公式,c1的值没有变,只是变了c2的值,c2的值变了,结果也变了,c也能独立影响

以下是一个综合例子:

求D的mc/dc测试用例是满足c1,c2,c3测试用例结果合并?

对于上述判断,它可以独立影响表达式结果,因此应该有三个输入:真实性、真实性和真实性

请注意,我们要计算的是别人拿什么值?在不影响结果的情况下,我可以独立影响整个结果

求出后,将三种情况的测试情况并集

以下实际应用:

多条件覆盖 Multiple Condition Coverage

测试用例设计不是针对覆盖度,而是针对功能设计,然后根据覆盖度修改用例

2. 逻辑覆盖测试工具

两种工具:IntelliJ IDEA Code Coverage Runner/JaCoCo

IDEA Code Coverage Runner

IDEA branch coverage计算方法及Jacoco不同,似乎有缺陷

获取jacoco覆盖报告的方法

请注意,如果您想获得覆盖率结果,您必须在项目中编写单个测量代码,否则不会有结果

点击mvn test

就可以在target的site在目录下生成一个 index.html点击文件查看报告

覆盖报告分析

JaCoCo Coverage Counters

  • Instructions

  • Branches

    计算所有 if 和 switch 句子的分支覆盖率

    无覆盖:行中无分支(红菱)

    部分覆盖:行中只执行部分分支(黄色菱形)

    全覆盖:行中所有分支(绿色菱形)均已实施

  • Cyclomatic Complexity 环复杂度

    二值节点的数量 超过10的代码不能通过

    所有可能路径的最小路径数都可以在(线性)组合中生成

  • Lines 语句覆盖 跟这个语句相关的二进制指令执行

    当至少一条分配给该行的指令已被执行时,该行被视为已执行

    无覆盖:该行中未执行任何指令(红色背景)

    部分覆盖:只执行了该行中的一部分指令(黄色背景)

    全覆盖:该行中的所有指令都已执行(绿色背景)

  • Methods 方法覆盖

  • Classes 类覆盖

IDEA branch coverage的计算方法与Jacoco不一样,而且似乎有缺陷

3. 启发式规则

好的单元测试:r原则,自动化的,independent,可重复的repeatable

BCDE原则

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lBWx9DMW-1636267284634)(C:\Users\lenovo\AppData\Roaming\Typora\typora-user-images\image-20211106135205068.png)]

Heuristic Rules

没有理论基础,只是根据工程实践经验总结出来的,类似于头脑风暴

告诉你在设计测试用例时,可以遵循一个什么样的思路

Right—BICEP

  • Right

    找到happy path 就是满足下面所有条件的测试用例

  • B:边界条件(最重要)

    缺陷隐藏在代码里,一般聚集在边界上,程序员在边界处经常出问题

    虚假或不一致的输入值,格式错误的数据、错误的电话号码,可能导致数字溢出的计算,空值或缺失值

    年龄的边界很好找,但不是所有的边界条件都那么好找,所以我们引入了correct规则寻找边界条件

  • I:逆关系

    用“逆行为”测试被测试代码

    在数据库中插入一条记录后,查询该记录

    已经使用的款项总数 = 款项总数 – 剩余的款项数

  • C:交叉关系检查被测功能是否满足要求

    使用不同数据之间的关系进行测试

    已经使用的款项总数 = 款项总数 – 剩余的款项数

  • E:验错 Forcing Error Condition

    java中长生命周期对象引用了短生命周期对象,而短生命周期对象用完之后没有及时释放,就是内存泄露

  • P:查看性能

单元测试第一目标是要找缺陷,覆盖是顺带要达到的目标

4. Junit & Qualified 测试脚本

Junit5 Features

junit所有特性都通过allowcation(at notation?)这个功能来实现的,要记住每个at notation的含义是什么,每个功能对应哪个notation要记住,下面几个老师单独提了一下:

  • @displayname的作用,提高可读性

  • @disable 忽略执行

  • @test instance lifecycle ⭐️

@test instance lifecycle 具体用法参考 TestPerClass 和 TestPerMethod 两个文件

测试类不是你的被测类,测试类是testlifecycle,被测类是meetcalendar

junit实例化测试类(instance)的方法有两种生命周期模式:

一种是在每一个测试方法之前,都会实例化一个测试类(独立性,减少测试和测试之间的依赖关系)

一种是一个测试类就实例化一个测试类的实例

@TestInstance(TestInstance.Lifecycle.PER_METHOD)
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class TestPerClassDemo { 
        
    private int count = 0;

    @Test
    void addCount1(){ 
        
        count++;
        System.out.println("addCount1:" + count);
    }

    @Test
    void addCount2(){ 
        
        count++;
        System.out.println("addCount2:" + count);
    }
}

要是两个程序代码一样,只是@TestInstance的参数不同(以上面的程序为例):

第一种 MeetCalendarTestPerMethod

addcount1冒号后面是几

addcount2冒号后面是几

都是1,因为每个测试方法之前都会实例化一个testmethod实例

第二种 MeetCalendarTestPerClass

addcount1 addcount2 顺序不确定 但count是在累加的

配置junit5环境

junit需要java8以上的支持,确认java版本

在项目里新建test目录,并且标记为测试根目录

右键一个方法,generate test

窗口提示我们当前项目并没有junit的支持,点击fix,自动下载jar包

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-h2qyN9rn-1636267284638)(C:\Users\lenovo\AppData\Roaming\Typora\typora-user-images\image-20211106181613200.png)]

已经有了测试方法,至此测试环境已经准备就绪

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LD9TsFjM-1636267284639)(C:\Users\lenovo\AppData\Roaming\Typora\typora-user-images\image-20211106181833615.png)]

配置maven测试环境

setting maven runner jre改为1.8

project structure project sdk 也选1.8

Lifecycle

meetherejava里面有一个test lifecycle的测试类,每一个@test就是一个测试方法

例如,避免在每个方法之前都要实例化一个对象,我们可以用@beforeeach的方法进行初始化

@BeforeEach
void init() { 
        
    meet = new MeetCalendar();
}

@Test
void AddAnReservation() { 
        
    meet.addReservation("Sun1", "gymb1", "2020-09-20 18:00");
    List<UserReservation> reservations = meet.getReservations();
    ......
}

以上代码和以下原始代码是一致的

@Test
void AddAnReservation() { 
        
    meet = new MeetCalendar();
    meet.addReservation("Sun1", "gymb1", "2020-09-20 18:00");
    List<UserReservation> reservations = meet.getReservations();
    .......
}

下面的代码运行结果应该是

before all test, before each test, the first test, after each test, before each test, the second test, after each test, after all test

public class TestLifeCycle { 
        

    @BeforeAll
    public static void initAll(){ 
        
        System.out.println("Before all tests");
    }

    @BeforeEach
    void init(){ 
        
        System.out.println("Before each test");
    }

    @Test
    void testDemoMethod1(){ 
        
        System.out.println("The 1st test");
    }

    @Test
    void testDemoMethod2(){ 
        
        System.out.println("The 2nd test");
    }

    @AfterEach
    void tearDown(){ 
        
        System.out.println("After each test");
    }

    @AfterAll
   static void  tearAll(){ 
        
        System.out.println("After all tests");
    }
}

Assertions

assertAll():

分组不同的断言,所有断言是被执行,任何失败都会一起报告

assertEquals("Sun", inputReservation.getUserName());
assertEquals(Site.gymb1, inputReservation.getSite());
assertEquals("2019-10-28 18:00",
             inputReservation.getReservationDateTime().format(
                 DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")));

MeetHereMaven 项目中 MeetCalendarTest 的 addAnReservation() 中的以上代码,可以用assertAll()改写为

assertAll(
    () -> assertEquals("Sun", inputReservation.getUserName()),
    () -> assertEquals(Site.gymb1, inputReservation.getSite()),
    () -> assertEquals("2020-9-20 18:00",
                       inputReservation.getReservationDateTime().format(
                           DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"))));

assertThrows():

系统抛出期望的异常

注意两点,一点系统是否抛出了期望的异常类型,一点处理异常的行为是不是正确

MeetHereMaven 项目中 DateTimeConvertTest 中有异常处理的例子

第一个参数是抛出的异常类型,第二个参数是我们输入的拉姆达表达式

@Test
@DisplayName("转换无效格式的日期,系统抛出异常")
void convertInvalidDateString(){ 
        
    // 1. 测试系统是否抛出了期望的异常类型
    Throwable exception = assertThrows(RuntimeException.class,()->DateTimeConvert.convertStringToDateTime("2019-9-20 18:00"));
    // 2. 处理异常的行为是不是正确
    assertEquals("输入预约时间格式不正确: [2019-9-20 18:00], 输入时间格式为[yyyy-MM-ddHH:mm]",exception.getMessage());
}

Parameterized tests 参数化测试

@ParameterizedTest 与 @Test 相同的生命周期

这是Junit5中实现的数据驱动自动化测试技术,通俗的讲就是只写一个测试方法,可以跑不同的数据

将测试数据和测试行为进行了分离,测试数据源和数据脚本是分开的,数据不是写在代码里

使用不同的参数多次运行测试,一段代码可以执行很多测试数据

学参数化测试要学哪些东西至少三点,最重要的是测试数据源,一是为测试定义测试数据类型,二是参数转换,测试数据源如何处理变量类型,类型匹配,类型转化,三是自己的属性

一是 定义数据源

参数指的的测试方法里面的方法的参数,测试数据源是这一个集合,每一条叫一个测试数据argument

测试数据源(argument source 简称 AS)的基本使用原则:

AS规则1:每个@Parameterized测试方法可以使用多个测试数据源,但是至少需要有一个

@ParameterizedTest
@ValueSource(strings = { 
        "software testing","Junit5","Have fun!"}) 
// valuesource用一维数组的方式来定义参数化测试的数据源
void lowerCase(String candidate) { 
        
    assertTrue(StringUtils.isAllLowerCase(candidate));
}

@ParameterizedTest
void testMethodWithoutArgumentSource(int candidate) { 
        
    assertEquals(9,candidate);
}
// 第二个没有数据源会报错,org.junit.platform.commons.PreconditionViolationException: Configuration error: You must configure at least one set of arguments for this @ParameterizedTest

下面代码给了两个数据源,用了junit5提供的两种定义数据源的方式,一种是一维数组@ValueSource,一种是使用方法来指定数据源@MethodSource,这个方法的名字叫range,但是必须返回一个string兼容类型

下面这个方法会运行8次,range是包含0,不包含20,但是从第15个往后跳,只有15 16 17 18 19,运行5次

@ParameterizedTest
@ValueSource(ints = { 
        1,2,3})
@MethodSource("range")
void testMethodWithMultipleArgumentSource(int candidate) { 
        
    assertNotEquals(9,candidate);
}

AS规则2:每个测试数据源必须为测试方法的所有参数提供测试数据。例如,测试方法若有2个参数,参数1不能使用测试数据源1,而参数2使用其他数据源,即参数2也必须使用测试数据源1

比如下面这个程序是不对的会报错

@ParameterizedTest
@ValueSource(ints = { 
        1,2,3})
@MethodSource("range")
void testMethodWithMultipleArgumentSource(int candidate1,int candidate2) { 
        
    assertNotEquals(9,candidate1);
    assertNotEquals(-1,candidate2);
}

常用的定义数据源的七种方法:

@ValueSource 一维数组
@NullSource, @EmptySource, @NullAndEmptySource  测试Null and Empty Sources:
@EnumSource 枚举数据源类型
@MethodSource
@CsvSource 逗号分割数据源,用的最多,excel可以生成csv,也有公司放在数据库里
@CsvFileSource
@ArgumentsSource

csv定义数据源方法举例

@ParameterizedTest
@CsvSource({ 
        "apple,1","orange,2","'lemon,lime',0xF1"})
void testWithCsvSource(String fruit,int rank) { 
        
    assertNotNull(fruit);
    assertNotEquals(0, rank);
}

@ParameterizedTest
@CsvSource(delimiter= ';',value = { 
        "apple;1","orange;2","'lemon,lime';0xF1"})
void testWithCsvSourcebyAtt(String fruit,int rank) { 
        
    assertNotNull(fruit);
    assertNotEquals(0, rank);
}

求出基路径:找到被测代码中必须要执行的测试路径

收尾相连的边构成的节点序列

不是程序的完整通路

complete path 完整路径:这条路径的开始必须是开始节点,终止必须是终止节点

锐单商城拥有海量元器件数据手册IC替代型号,打造电子元器件IC百科大全!

相关文章