软件测试蓝桥杯

一、单元测试的定义和目的
单元测试是针对单元内部的测试,一般在编码完成后由测试工程师或测试工程师与开发工程师共同完成。单元测试只关心模块内部是否存在问题,不关心模块之间的接口,这就好比工厂组装电脑,在组装之前会对所有零部件进行测试,确定零件都没有问题后再进行组装。

1、单元测试的定义
单元测试是对软件的基本组成单元进行的测试,如函数、类或类的方法。单元测试的依据是模块的详细设计文档,主要关注一个单元是否正确实现了规定的功能、代码逻辑是否正确、输入输出是否正确、代码是否符合相关的标准规范等。

单元测试是对软件的最小可测试单元(即可独立编译或汇编的程序模块)进行的测试活动,也称为模块测试。单元可以是程序的模块或功能模块,具有一些基本属性,如:名称、明确的功能、规格定义,使用的数据与其他单元的联系等。在不同的编程语言中,单元的表现形式有所不同:在 C 等结构化编程语言中,单元一般是一个函数或过程;在 C++ 、Java 等面向对象的语言中,单元一般是指类或类的方法。

2、单元测试的目的
软件单元测试的目的是:

检查每个软件单元能否正确地实现设计说明中的功能、性能、接口和其他设计约束等要求;
发现单元内部可能存在的各种差错;
提高软件质量。
二、单元测试过程
单元测试是软件测试过程中的一个关键环节,它与集成测试、系统测试一样,分为测试策划、测试设计、测试执行和测试总结几个阶段。

单元测试过程中每个阶段需要完成的主要工作如下:

三、测试方法 
1、语句覆盖
语句覆盖法是指设计适当数量的测试用例,使被测程序中的每条语句至少被执行一次。语句覆盖率的计算方法为:至少被执行一次的语句数量 / 程序中可执行的语句总数。 

如下:

    public static int test(int a,int b,int c) {
1         int result = 0;
2         if(a == 0 or b > 2) {
3             result = b - a;
          }
4         if(a > 0 and c > 0 ) {
5             result = c * a;
        }
6        return result;
    }
第 1 步:分析待测试程序,画出程序的流程图。上述代码的参考流程图如下图所示(为了方便后面的讲解,笔者在流程图中标上了序号)。如果对语句覆盖法比较熟悉或代码逻辑比较简单,也可以省略画流程图这一个步骤。

第 2 步:分析流程图,编写测试用例。

根据语句覆盖法的定义,我们需要设计一些测试用例,使程序中的每条语句至少被执行一次。通过对第一步中的流程图进行分析,我们设计如下表中的一个测试用例即可以将所有语句全部覆盖。

测试用例编号    输入数据    预期结果    语句覆盖情况
testcase_01    a = 1 , b = 3 , c = 9    result = 9    覆盖语句 1,2,4,3,5, 6
总结:语句覆盖可以使程序中的语句都被测试到,但是它也是覆盖最弱的一种逻辑覆盖方法,无法发现程序中的很多逻辑错误,需要和别的覆盖方法结合起来使用才能保证覆盖更为全面。 

2、分支覆盖法
 分支覆盖,也叫判定覆盖,是指运行代码进行测试时,程序中的所有判定语句的真、假分支至少都被执行过一次。分支覆盖率的计算方法为:测试时覆盖到的判定语句真、假分支的总数 / 程序中判定语句真、假分支的总数。例如,判定语句 if a > 0 有真、假两个分支,如果设计一个测试用例 a = 3 ,则该判定语句的真分支可以被覆盖,分支覆盖率为 1 / 2 = 50% ; 如果设计两个测试用例 a = 3 ,a = -1 ,分别使用这两个测试用例数据执行被测试程序,则该判定语句的真、假分支都可以被覆盖到,分支覆盖率为 2 / 2 = 100% 。

例题如下: 

    public static int test(int a,int b,int c) {
1         int result = 0;
2         if(a == 0 or b > 2) {
3             result = b - a;
          }
4         if(a > 0 and c > 0 ) {
5             result = c * a;
        }
6        return result;
    }
第 1 步:分析待测试代码,画出程序的流程图。上述代码的参考流程图如下图所示:

第 2 步:分析流程图,编写测试用例。

根据分支(判定)覆盖的定义,我们需要设计一些测试用例,使程序中的每个判定条件至少被执行一次,即上图中的判断语句 ② 和 ③ 的真、假分支都需要至少被执行一次。因此,我们可以设计如下表中的两个测试用例覆盖所有的真、假分支。

测试用例编号    输入数据    预期结果    分支覆盖情况    语句覆盖情况
testcase_01    a = 0 , b = 5 , c = 9    result = 5    覆盖判断语句 ② 的真分支和判断语句 ③ 的假分支    1,2,4,3,6
testcase_02    a = 5 , b = -2 , c = 3    result = 15    覆盖判断语句 ② 的假分支和判断语句 ③ 的真分支    1,2,3,5,6
总结: 分支(判定)覆盖比语句覆盖的的覆盖效果要强一些,但是分支(判定)覆盖可能还是无法发现程序中的一些逻辑错误,仍需结合其他白盒测试用例设计方法才能覆盖全面。

 3、条件覆盖法
条件覆盖是指运行代码进行测试时,程序中所有判断语句中的条件取值为真值为假的情况都被覆盖到,即每个判断语句的所有条件取真值和假值的情况都至少被经历过一次。条件覆盖率的计算方法为:测试时覆盖到的条件语句真、假情况的总数 / 程序中条件语句真、假情况的总数。

 例题如下:

    public static int test(int a,int b,int c) {
1         int result = 0;
2         if(a == 0 or b > 2) {
3             result = b - a;
          }
4         if(a > 0 and c > 0 ) {
5             result = c * a;
        }
6        return result;
    }
第 1 步:分析待测试代码,画出程序的流程图。上述代码的参考流程图如下图所示:

第 2 步:分析流程图。

通过分析流程图,我们可以知道:这段代码中有两个判断语句,分别是上图中标识为 ② 和 ③ 的语句,语句 ② 中有两个条件,分别为 a == 0 和 b > 2 ;语句 ③ 中也有两个条件,分别为 a > 0 和 c > 0 。为了使后续的分析过程更加清晰明了,我们先来梳理一下流程图中的条件语句,并进行相应的标识,具体如下:

条件    取值    标识
a == 0    真    Y1
a == 0    假    N1
b > 2    真    Y2
b > 2    假    N2
a > 0    真    Y3
a > 0    假    N3
c > 0    真    Y4
c > 0    假    N4
第 3 步:使用条件覆盖法编写测试用例。

根据条件覆盖法的定义,我们需要设计一些测试用例,使程序中所有判定语句中的每个条件为真和为假的情况都至少被执行一次,即上表中列出的 8 种情况都需要至少被执行一次。因此,我们可以设计如下表中的测试用例来对程序中的四个条件进行覆盖:

测试用例编号    输入数据    预期结果    条件覆盖情况    分支覆盖情况
testcase_01    a = 0 , b = 5 , c = 9    result = 5    Y1、Y2、N3、Y4    判断语句 ② 的真分支和③ 的假分支
testcase_02    a = 5 , b = 1 , c = -3    result = 0    N1、N2、Y3、N4    判断语句 ② 的假分支和 ③ 的假分支
总结:条件覆盖法可以使程序中判断语句的每个条件都至少被覆盖一次,但满足了条件覆盖却不一定会满足分支(判定)覆盖,对于有些程序判定的错误仍无法发现。 

 4、分支-条件覆盖
分支(判定)覆盖是设计一定量的测试用例使程序中的每个判断语句的真假分支都得到覆盖,但是分支覆盖不能保证判断语句中每个条件的真、假分支都得到覆盖。那么,使用条件覆盖是否可以解决覆盖不全面的问题呢?通过上一小节的学习,我们发现:条件覆盖虽然可以覆盖判断语句中每个条件的真、假分支,但可能没有将所有判断语句的真、假分支覆盖全,仍然做不到对程序的 100% 的覆盖。所以,我们需要把分支(判定)和条件覆盖一起进行综合考虑。

代码如下:

    public static int test(int a,int b,int c) {
1         int result = 0;
2         if(a == 0 or b > 2) {
3             result = b - a;
          }
4         if(a > 0 and c > 0 ) {
5             result = c * a;
        }
6        return result;
    }
第 1 步:分析待测试代码,画出程序的流程图。上述代码的参考流程图如下图所示:

第 2 步:分析流程图。

通过分析流程图,我们可以知道:这段代码中有两个判断语句,分别是上图中标识为 ② 和 ③ 的语句,语句 ② 中有两个条件语句,分别为 a == 0 和 b > 2 ;语句 ③ 中也有两个条件,分别为 a > 0 和 c > 0 。为了使后续的分析过程更加清晰明了,我们先来梳理一下流程图中的判断语句及语句中的条件项,并进行相应的标识。

判断语句:

判断语句    取值    标识
a == 0 or b > 2    真    T1
a == 0 or b > 2    假    F1
a > 0 and c > 0    真    T2
a > 0 and c > 0    假    F2
条件:

条件    取值    标识
a == 0    真    Y1
a == 0    假    N1
b > 2    真    Y2
b > 2    假    N2
a > 0    真    Y3
a > 0    假    N3
c > 0    真    Y4
c > 0    假    N4
第 3 步:使用分支-条件覆盖法编写测试用例。

根据分支-条件覆盖法的定义,这种方法是将分支覆盖和条件覆盖结合起来,设计一些测试用例,使程序中每个判定语句中的每个条件为真和为假的情况都至少被执行一次,并且每个判断语句本身为真、为假的情况也至少被执行一次。因此,我们可以设计如下表中的测试用例来对程序中的两个判断语句及其四个条件进行覆盖:

测试用例编号    输入数据    预期结果    条件覆盖情况    分支覆盖情况
testcase_01    a = 0 , b = 5 , c = 0    result = 5    Y1、Y2、N3、N4    T1、F2
testcase_02    a = 5 , b = 1 , c = 3    result = 15    N1、N2、Y3、Y4    F1、T2
通过执行以上两个用例可以实现程序的分支-条件 100% 覆盖。那么,是不是实现了分支-条件覆盖就代表程序已经被覆盖全面了呢?下图是该程序标识了执行路径的流程图:

 总结:分支-条件覆盖可以使程序中的判断语句以及判断语句中的条件的真、假分支都得到覆盖,但是分支-条件覆盖达到 100% 仍然强度不够,程序中的某些逻辑运算等错误仍然可能不会被发现。

5、条件组合覆盖
条件组合覆盖又称为多条件覆盖,是指设计足够数据的测试用例,使每个判定语句中的所有判定条件的各种可能的组合都至少被执行一次。

代码如下:

    public static int test(int a,int b,int c) {
1         int result = 0;
2         if(a == 0 or b > 2) {
3             result = b - a;
          }
4         if(a > 0 and c > 0) {
5             result = c * a;
        }
6        return result;
    }
第 1 步:分析待测试代码,画出程序的流程图。上述代码的参考流程图如下图所示:

标识了路径的流程图如下:

第 2 步:分析流程图。

由流程图可知:这段代码中有两个判断语句,分别是上图中标识为 ② 和 ③ 的语句,语句 ② 中有两个条件语句,分别为 a == 0 和 b > 2 ;语句 ③ 中也有两个条件,分别为 a > 0 和 c > 0 。为了使后续的分析过程更加清晰明了,我们先将条件语句进行相应的标识,具体如下:

条件    取值    标识
a == 0    真    Y1
a == 0    假    N1
b > 2    真    Y2
b > 2    假    N2
a > 0    真    Y3
a > 0    假    N3
c > 0    真    Y4
c > 0    假    N4
根据条件组合覆盖法的定义,我们需要设计一些测试用例分别使判断语句 ② 中两个条件的四种组合及判断语句 ③ 中两个条件的四种组合都至少执行一次,如下表所示:

组合编号    判断语句    条件组合
1    a == 0 or b > 2    Y1 + Y2
2    a == 0 or b > 2    Y1 + N2
3    a == 0 or b > 2    N1 + Y2
4    a == 0 or b > 2    N1 + N2
5    a > 0 and c > 0    Y3 + Y4
6    a > 0 and c > 0    Y3 + N4
7    a > 0 and c > 0    N3 + Y4
8    a > 0 and c > 0    N3 + N4
第 3 步:使用条件组合覆盖法编写测试用例。

分别设计测试用例覆盖上表中的各种组合,具体如下:

测试用例编号    输入数据    预期结果    条件组合覆盖    条件覆盖    路径覆盖
testcase_01    a = 0 , b = 3 , c = 1    result = 3    组合 1 、组合 7    Y1、Y2、N3、Y4    B - C
testcase_02    a = 0 , b = 1 , c = 0    result = 1    组合 2 、组合 8    Y1、N2、N3、N4    B - C
testcase_03    a = 1 , b = 5 , c = 3    result = 3    组合 3 、组合 5    N1、Y2、Y3、Y4    B - D
testcase_04    a = 2 , b = 0 , c = -1    result = 1    组合 4 、组合 6    N1、N2、Y3、N4    A - C
通过对上表的分析我们可以发现 :条件组合覆盖可以使程序判断断语句中的条件组合都至少被执行一次,但是,满足了条件组合覆盖也不能保证所有的路径都已经得到覆盖,在本例中,四个测试用例实现了条件组合的 100% 覆盖,但是仍有路径 A - D 未被覆盖到。如果要将路径全部覆盖,需要再增加一个覆盖路径 A - D 的测试用例,即使用条件组合 + 分支覆盖的全部测试用例如下:

测试用例编号    输入数据    预期结果    条件组合覆盖    条件覆盖    路径覆盖
testcase_01    a = 0 , b = 3 , c = 1    result = 3    组合 1 、组合 7    Y1、Y2、N3、Y4    B - C
testcase_02    a = 0 , b = 1 , c = 0    result = 1    组合 2 、组合 8    Y1、N2、N3、N4    B - C
testcase_03    a = 1 , b = 5 , c = 3    result = 3    组合 3 、组合 5    N1、Y2、Y3、Y4    B - D
testcase_04    a = 2 , b = 0 , c = -1    result = 0    组合 4 、组合 6    N1、N2、Y3、N4    A - C
testcase_05    a = 2 , b = 0 , c = 5    result = 10    组合 4 、组合 5    N1、N2、Y3、Y4    A - D
总结:条件组合覆盖可以使程序所有判断语句中的条件组合都被覆盖,但是仍然不能保证覆盖所有路径,需要再补充用例进行路径覆盖。另外,如果程序中的条件和分支比较多,需要设计的测试用例数量会很庞大。 

6、基本路径覆盖
路径覆盖可以使程序中的路径都被测试到,但是,要对程序中的路径做到完全覆盖经常是无法实现的。为了解决这一难题,我们需要在保证测试质量的前提下把测试的路径数量压缩到一定的范围内,基础路径覆盖法正好可以解决该问题。

代码如下:

    public static int test(int a,int b,int c) {
1         int result = 0;
2         if(a == 0 or b > 2) {
3             result = b - a;
          }
4         if(a > 0 and c > 0 ) {
5             result = c * a;
        }
6        return result;
    }
使用基本路径覆盖法设计白盒测试用例的具体步骤如下:

第 1 步:分析待测试代码,画出程序的流程图。参考流程图如下图所示,如果对该方法比较熟练或对程序流程比较清晰,可省略这个步骤。

在第二章的“ 2.2.2 控制流分析”小节中曾介绍过“在控制流图中如果含有复合条件,需要改为单条件嵌套的形式”,为了后续的讲述更加清晰,这里先把上述流程图中的复合条件按控制流图的要求进行拆分,具体如下:

第 2 步:根据流程图画出控制流图。

控制流图的画法在第二章的“ 2.2.2 控制流分析”小节中已详细介绍,此处不再详细介绍,只进行简单的回顾。在控制流图中,圆形符号○称为“节点”,表示一个基本的代码块;包含条件的节点称为“判断节点”;箭头称为“边”,表示控制流路径,反向边则表示可能存在循环。按照控制流图的规则,上述流程图可以画成下面的控制流图:

第 3 步:计算圈复杂度。

圈复杂度 V(G) 有三种计算方法,在第二章的“复杂度分析”小节已详细介绍过,读者可以任选其中一种方法来进行计算。下面简单介绍一下用这三种方法计算本例的圈复杂度:

方法一:V(G) = A + 1,其中 A 代表控制流图中的封闭区域数量。从下图可以看出,程序的控制流图中共有 4 个封闭区域,所以,圈复杂度 V(G) = 4 + 1 = 5 。

方法二:V(G) = P + 1,其中 P 代表控制流图中的判定节点数。从下图可以看出,程序的控制流图中共有 4 个判定节点,所以,圈复杂度 V(G) = 4 + 1 = 5 。

方法三:V(G) = e - n + 2,其中 e 代表控制流图中的边的数量,即控制流中的箭头数量;n 代表控制流图的节点数量,即控制流图中的圆圈数量。从下图中可以看出,程序的控制流图中有 11 条边(11个箭头),8个节点(8个圆圈),所以,圈复杂度 V(G) = 11 - 8 + 2 = 5 。

第 4 步:确定基本路径的集合。

基本路径又称为独立路径,是指至少包含一条其他独立路径中未包含的路径。例如,在上图中,路径 1 - 2 - 3 - 5 - 8 是一条基本路径,1 - 2 - 4 - 3 - 5 - 8 则可以看成了另外一条基本路径,因为这条路径中经过 4 节点的路径在第一条基本路径中未包含。

圈复杂度是指程序中的独立路径数量,是确保程序中每个可执行语句至少执行一次需要的测试用例数量的最小值。根据第 3 步的计算结果,本例中我们需要确定 5 条基本路径,具体如下:

路径 1 :1 - 2 - 3 - 5 - 8

路径 2 :1 - 2 - 4 - 3 - 5 - 8

路径 3 :1 - 2 - 4 - 5 - 8

路径 4 :1 - 2 - 4 - 5 - 6 - 8

路径 5 :1 - 2 - 4 - 5 - 6 - 7 - 8

第 5 步:根据基本路径编写测试用例。

根据基本路径覆盖法的定义,我们需要设计测试用例分别覆盖第 4 步中的 5 条基本路径,即设计合理的输入数据使程序运行时经过指定的路径。因此,我们可以设计如下表中的 5 个测试用例来覆盖这 5 条基本路径。

测试用例编号    输入数据    预期结果    路径基本覆盖情况
testcase_01    a = 0 , b = 1 , c = 9    result = 1    路径 1
testcase_02    a = 0 , b = 3 , c = 5    result = 3    路径 2
testcase_03    a = -2 , b = 1 , c = 3    result = 0    路径 3
testcase_04    a = 1 , b = 0 , c = -1    result = 0    路径 4
testcase_05    a = 5 , b = -3 , c = 2    result = 10    路径 5
 总结:使用基本路径覆盖法设计用例进行测试时,可以使程序中的每条独立路径都至少执行一次。如果程序中的基本路径达到了 100% 覆盖,则分支(判定)覆盖、条件覆盖也能达到 100% 覆盖。如果使用基本路径覆盖法后程序中仍有未覆盖到的路径,可使用逻辑覆盖法补充测试用例保证覆盖全面。

7、循环语句覆盖--简单循环
循环是反复运行同一段代码的语法结构,是代码中常见的一种结构。在白盒测试中,循环结构的测试也是我们需要掌握的内容。循环结构测试主要的侧重点是验证循环结构的有效性,一般可以结合条件覆盖、基本路径覆盖以及黑盒测试方法中的等价类、边界值等方法来设计测试用例。

简单循环是最简单的循环,即只有一个循环且没有嵌套,例如,一个 while 循环、一个do-while 循环、一个 for 循环。下图是两种简单循环的示意图:

代码如下:

public static int getFactorial(Integer num) {  
      int result = 0; 
      if (num >= 1 && num <= 10){
          result = 1; 
          int i = 1;
          while (i < num){
            result = result * i; 
            i++;
         }
           System.out.println(num + "的阶乘为:" + result);       
       }
      else{
          System.out.println("请输入1~10的整数!"); 
      }   
        return result;
    }

第 1 步:分析源代码,画出流程图。

这个步骤主要是帮助我们理清思路,为后面的测试用例设计打下基础。如果代码比较简单,或是对测试用例设计方法比较熟练以后,可以简化流程图,也可以省略这一步,直接进行测试用例设计。本例的参考流程图如下:

第 2 步:设计测试用例。

循环测试的侧重点是测试循环结构的有效性,主要考虑循环的边界和运行界限执行循环体的情况,所以设计简单循环结构的测试用例主要需要考虑循环变量的初始值、增量、最大值,以及边界取值的情况下代码处理是否正确。我们可以结合黑盒测试用例设计方法中的等价类边界值的方法来设计测试用例,即”如果输入/输出条件规定了值的个数,则用最大个数、最小个数、比最小个数少 1 ,比最大个数多 1 的值作为测试数据“。一般来说,简单循环的测试用例需要考虑下列几种情况(设最大循环次数为 n ):

(1)循环 0 次:测试跳过整个循环的场景;

(2)循环 1 次:目的是检查循环的初始值是否正确;

(3)循环 2 次:目的是检查多次循环是否正确;

(4)循环 m 次(其中 2 < m < n - 1):目的是检查多次循环是否正确,这里我们也可以用等价类的思想来理解,即:可以把大于 2 次、小于 n - 1 次看成是一个等价类,m 可以是这个范围中的任意一个值,根据等价类的思想,如果这个范围中的任意一个值是不会发现程序的问题,那么,我们可以认为这个等价类中所有的值都不会发现程序的问题;

(5)循环 n - 1 次:目的是检查边界值是否正确;

(6)循环 n 次:目的是检查边界值是否正确;

(7)循环 n + 1 次:目的是检查边界值是否正确。这里读者可能会有疑问,一个循环的最大循环次数是 n ,我们要怎么让它循环 n + 1 次呢?这不是一个伪命题吗?通过对边界值方法的理解,我们可以知道,等于、大于、小于边界值的地方是最容易出现 bug 的,如,“差 1 错”,即不正确的多循环或者少循环了一次。在循环结构的测试中设计循环 n + 1次的测试用例,就是为了检查代码是否会出现多循环一次的错误。在实际的测试过程中,我们可以通过分析代码结构决定是否能设计出循环 n + 1次的测试用例。

在本例中,根据以上原则我们可以设计如下测试用例数据:

循环次数    0 次    1 次    2 次    m 次    n-1 次    n 次    n+1 次
测试用例( num 的值)    0    1    2    5    9    10    11
转化为测试用例,如下表所示:

测试用例编号    输入    预期输出
testcase_01    0    result=0,输出:请输入1~10的整数!
testcase_02    1    result=1,输出:1的阶乘是1
testcase_03    2    result=2,输出:2的阶乘是2
testcase_04    5    result=120,输出:5的阶乘是120
testcase_05    9    result=362880,输出:9的阶乘是362880
testcase_06    10    result=3628800,输出:10的阶乘是3628800
testcase_07    11    result=0,输出:请输入1~10的整数!
第 3 步:执行测试用例。

白盒测试用例一般使用专门的测试工具(如:Junit)来执行,使用这些工具可以非常方便的编写测试用例、判断测试用例执行结果是否正确。在没有学习测试工具之前,我们先使用调用被测函数的方法来执行测试用例。具体执行方法为:

1)依次使用测试用例的输入值调用被测对象;

2)比较被测对象的实际返回值与测试用例的“预期输出”是否一致:如果一致,则测试用例执行通过;如果不一致,则测试用例执行失败。

具体的测试代码如下:

package test;
 
public class simpleTest {
    // 执行测试用例
    public static void main(String[] args) {
        // 执行用例 testcase_01
        if(getFactorial(0) == 0){
            System.out.println("testcase_01执行通过\n");
        }
        else{
            System.out.println("预期输出为:0 ");
            System.out.println("testcase_01执行失败\n");
        }
        
        // 执行用例 testcase_02
        if(getFactorial(1) == 1){
            System.out.println("testcase_02执行通过\n");
        }
        else {
            System.out.println("预期输出为:1 ");
            System.out.println("testcase_02执行失败\n");
        }
        
        // 执行用例 testcase_03
        if(getFactorial(2) == 2){
            System.out.println("testcase_03执行通过\n");
        }
        else{
            System.out.println("预期输出为:2 ");
            System.out.println("testcase_03执行失败\n");
        }
        
        // 执行用例 testcase_04
        if(getFactorial(5) == 120){
            System.out.println("testcase_04执行通过\n");
        }
        else{
            System.out.println("预期输出为:120 ");
            System.out.println("testcase_04执行失败\n");
        }
        
        // 执行用例testcase_05
        if(getFactorial(9) == 362880){
            System.out.println("testcase_05执行通过\n");
        }
        else{
            System.out.println("预期输出为:362880 ");
            System.out.println("testcase_05执行失败\n");
        }
        
        // 执行用例testcase_06
        if(getFactorial(10) == 3628800){
            System.out.println("testcase_06执行通过\n");
        }
        else{
            System.out.println("预期输出为:3628800 ");
            System.out.println("testcase_06执行失败\n");
        }
        
        // 执行用例 testcase_07
        if(getFactorial(11) == 0){
            System.out.println("testcase_07执行通过\n");
        }
        else{
            System.out.println("testcase_07执行失败\n");
        }
   }
    
 

 总结:简单循环的测试重点是验证循环结构的有效性,主要考虑循环的边界和运行界限执行循环体的情况。对于最多为 n 次的简单循环,一般需要设计跳过循环、循环 1 次、2 次,m 次(2<m<n-1)、n - 1 次、n 次、n + 1 次的测试用例,重点测试循环变量的初值、最大值、增量以及退出循环的情况。如果循环的最大循环次数不确定,一般设计跳过循环、循环 1 次、2 次,m 次的测试用例即可。

8、循环语句覆盖---嵌套循环
嵌套循环是指一个循环语句的循环体内含有其他的循环语句的语法结构,while、for 等循环语句都可以进行嵌套。最常见的嵌套循环是 for 循环中嵌套 for 循环。嵌套循环执行时,外层循环每执行一次内层循环会执行多次,循环的总次数等于外层循环次数与内层循环次数的积。下面是一个嵌套循环的示意图:

代码如下:

//冒泡排序
public static int[] bubble_sort(int[] numbers){  
    for (int i = 0; i < numbers.length - 1;i++ ){
        boolean flag = false; 
        for (int j = 0;j < numbers.length - 1 - i;j++){
            if (numbers[j] > numbers[j+1]){
                int temp = 0;
                temp = numbers[j];
                numbers[j] = numbers[j+1];
                numbers[j+1] = temp;
                flag = true;
            }
        }
        if (flag == false){
            break;
        }
    }
    return numbers;
}

第 1 步:分析源代码,画出流程图。

从上面的代码可以看出,冒泡排序有两个 for 循环,外层循环根据数组的长度控制外层循环次数,内层循环则是将大数下沉,实现冒泡的过程。本例的参考流程图如下:

第 2 步:设计测试用例。

嵌套循环和简单循环的测试侧重点是相同的,都是侧重于验证循环结构的有效性。但是我们也不能直接将简单循环的测试方法应用于嵌套循环,因为如果按照简单循环的思路,测试用例的数量将随着嵌套层次的增加而成几何级增长,让测试变得非常困难。那么,怎样设计测试用例才能让嵌套循环的测试既能尽可能覆盖全面、又能减少测试用例数量呢?我们可以从以下几方面进行考虑:

1)按简单循环的方法对最内层循环进行测试,其他循环次数设置为最小值;

2)由内向外逐步对每一层循环进行测试,直到所有各层循环都测试完成。测试时将当前循环的所有外层循环的循环次数设置为最小值,所有内层循环的循环次数设置为典型值;

3)对各层循环同时取最小循环次数进行测试,如果有最大次数,再同时取最大循环次数进行测试。

下面我们根据这几个测试用例设计的原则来设计本实验中冒泡排序的测试用例:

1)设计内层循环的测试用例:用简单循环的方法对下面的最内层循环进行测试,将外层循环的循环次数设置为最小值 。

for (int j = 0;j < numbers.length-1-i;j++){
    if (numbers[j] > numbers[j+1]){
            int temp = 0;
            temp = numbers[j];
            numbers[j] = numbers[j+1];
            numbers[j+1] = temp;
            flag = true;
            }
        }


在本例中,根据以上原则我们可以对内层循环设计如下测试用例:

跳过循环:只有一个数时,内层循环不会执行,如:{3}

循环 1 次:当数组中有两个数字时,内层循环会循环一次,如:{21,2}

循环 2 次:当数组中有三个数字,且是按从小到大的顺序排列时,外层循环只会循环 1 次,为该层循环的最小值,而内层循环会循环两次,如:{1,2,21}

循环 m 次:根据简单循环测试用例设计的原则,如果循环没有最大循环次数,我们可以选择任意一个大于 2 的循环次数设计一个测试用例测试多次循环是否正确。这里我们设计一个循环 5 次的测试用例 ,通过分析代码的循环结构,我们可以知道,当传入6个数字,且数字是按从小到大的顺序排列时,外层循环只循环 1 次,内层循环会循环 5 次,如:{1,4,7,11,23,65}

2)设计外层循环的测试用例:

跳过循环:只有一个数时,外层循环不会执行,如:{3}

循环 1 次:当传入的数字都是按从小到大的顺序排列时,外层循环只会循环一次,如:{1,3,5,9}

循环 2 次:当数组中的数字需要交换一次位置时,外层循环会循环两次,如:{3,9,5,48,90}

循环 m 次:可以选择任意一个大于 2 的循环次数设计一个测试用例测试外层多次循环是否正确,如:{76,2,22,59,5,155,1,90,18}

3)对各层循环同时取最小循环次数或最大循环次数进行测试。在本例中内层和外层循环最小次数 1 都已有相关用例覆盖,这里不再重复设计。

综上所述,去重后冒泡排序的测试用例如下:

测试用例编号    输入    预期输出
testcase_01    {3}    {3}
testcase_02    {21,2}    {2,21}
testcase_03    {1,2,21}    {1,2,21}
testcase_04    {1,4,7,11,23,65}    {1,4,7,11,23,65}
testcase_05    {1,3,5,9}    {1,3,5,9}
testcase_06    {3,9,5,48,90}    {3,5,9,48,90}
testcase_07    {76,2,22,59,5,155,1,90,18}    {1,2,5,18,22,76,90}
第 3 步:执行测试用例。

在这里还是使用调用被测函数的方法来执行测试用例,在简单循环中我们使用的是单个用例输入测试数据,判断预期结果的方法,这里换一种更简洁的方式,把所有测试用例输入和预期输出数据都初始化以后一起执行用例、判断实际执行结果与预期输出是否一致。具体的测试代码如下:

package test;
 
import java.util.Arrays;
 
public class NestingloopTest {
    // 执行测试用例
    public static void main(String[] args){
        //初始化测试用例输入数据
        int[][] input_testcase = {
                {3},
                {21,2},
                {1,2,21},
                {1,4,7,11,23,65},
                {1,3,5,9},
                {3,9,5,48,90},
                {76,2,22,59,5,155,1,90,18}
        };
        //初始化测试用例预期结果
        int[][] expect_result = {
                {3},
                {2,21},
                {1,2,21},
                {1,4,7,11,23,65},
                {1,3,5,9},
                {3,5,9,48,90},
                {1,2,5,18,22,59,76,90,155}
        };
        
        //执行测试用例
        for (int i = 0; i < 7; i++){
            int[] execute_result = bubble_sort(input_testcase[i]);
            //比较的执行结果与测试用例的预期输出是否一致:如果一致则用例执行通过,如果不一致则用例执行失败
            Boolean test_result = Arrays.equals(execute_result, expect_result[i]);
            if (test_result){
                System.out.println("testcase_0" + (i + 1) + "执行通过!\n");
            }
            else{
                System.out.println("预期结果为:" + Arrays.toString(expect_result[i]));
                System.out.println("实际结果为:" + Arrays.toString(execute_result));
                System.out.println("testcase_0" + (i + 1) + "执行失败!\n");
            }    
        }    
    }
    
//冒泡排序
public static int[] bubble_sort(int[] numbers){  
    for (int i = 0; i < numbers.length - 1; i++ ){
        boolean flag = false;
        for (int j = 0; j < numbers.length - i - 1; j++){
            if (numbers[j] > numbers[j+1]){
                int temp = 0;
                temp = numbers[j];
                numbers[j] = numbers[j+1];
                numbers[j+1] = temp;
                flag = true;
            }
        }
        if (flag == false){
            break;
        }
    }
    return numbers;    
}
}

 总结:嵌套循环结构的测试侧重点与简单循环相同,都是侧重于验证循环的有效性,不同点在于嵌套循环结构设计测试用例时需考虑用尽可能少的用例覆盖更全面的场景,降低测试成本,提高测试效率。

9、循环语句覆盖---串接循环
 

串接循环是指两个或多个循环连接在一起的循环结构,也称连锁循环。串接循环分为两种:第一种是各个循环体彼此独立、相互之间没有关联关系,这种循环我们可以使用简单循环的方法,依次对每个独立的循环体进行测试;第二种串接循环是各个循环体之间有关联关系,第二个循环的输入来自于第一个循环的输出,对于这种串接循环,我们可以考虑使用嵌套循环的测试方法来进行测试。

下图是串接循环的示意图:

代码如下:

    public static String test(int[] numbers){  
        int max_number = 0;
        int factor = 2;
        String result = "";
        //求数据组中的最大值
        for (int i = 0; i < numbers.length - 1; i++){
            if (max_number < numbers[i]){
                max_number = numbers[i];
            }
        }
        //将最大值分解质因数
        int tmp = max_number;
        while(factor <= tmp){
            if(factor == tmp){
                result = result + Integer.toString(factor);
                break;
            }
            else if(tmp % factor == 0){
                result = result + factor + "*"; 
                tmp = tmp / factor;
            }
            else{
                factor++;
            }    
        }    
        System.out.println(max_number + "分解质因数的结果为:" + result);
        return result;    
    }    

第 1 步:分析代码结构。

通过观察我们可以发现:这段代码中一共有两个循环,第一个 for 循环的输出是第二个 while 循环的输入,也就是上面所说的第二种串接循环,即各个循环体之间有关联关系的串接循环。本例的流程图与实验介绍中典型的串接循环流程图类似,所以此处不再重复。

第 2 步:设计测试用例。

串接循环与嵌套循环、简单循环的测试侧重点一样,也是侧重于验证循环结构的有效性。对于循环体之间有关联关系的串接循环,我们可以使用嵌套循环的测试方法来进行测试,即:

1)按简单循环的方法对下层循环进行测试,其他循环次数设置为最小值;

2)由下至上逐步对每一层循环进行测试,直到所有循环都测试完成。测试时将当前循环的所有上层循环的循环次数设置为最小值,所有下层循环的循环次数设置为典型值;

3)对各层循环同时取最小循环次数进行测试,如果有最大次数,再同时取最大循环次数进行测试。

下面我们根据这几个原则来设计测试用例:

1)设计第一个 for 循环的测试用例:

//求数据组中的最大值
for (int i = 0; i < numbers.length - 1; i++){
        if (max_number < numbers[i]){
            max_number = numbers[i];
        }
    }


用简单循环的方法对下面的最内层循环进行测试,将外层循环的循环次数设置为最小值 ,可以设计如下测试用例:

跳过循环:当传入的数组为空时会跳过 for 循环,即:{}

循环 1 次:当传入的数组中只有一个数字时,for 循环只会循环一次,如:{6}

循环 2 次:当传入的数组中有两个数字时 for 循环会循环两次,如:{75,11}

循环 m 次:因为这个循环没有最大循环次数,所以可以选择任意一个大于 2 的循环次数设计一个测试用例测试多次循环是否正确。这里我们设计一个循环 6 次的测试用例 ,如:{20,6,90,21,45,76}

2)设计第二个 while 循环的测试用例:

while(factor <= tmp){
    if(factor == tmp){
        result = result + Integer.toString(factor);
        break;
    }
    else if(tmp % factor == 0){
        result = result + factor + "*"; 
        tmp = tmp / factor;
    }
    else{
        factor++;
    }    
}


跳过循环:当传入的数组为空或传入的数组中只有数字 1 时,会跳过 while 循环,因为测试 for 循环的时候我们已经设计了数组为空的测试用例,所以这里我们选择数组中只有数字 1 的用例,即:{1}

循环 1 次:当数组中的最大值为 2 时,while 循环只会执行 1 次,如:{2,1}

循环 2 次:当数组中的最大值为 3 或 4 时,while 循环会执行 2 次,如:{4,1,3,2}

循环 m 次:可以选择任意一个大于 2 的循环次数设计一个测试用例测试 while 循环是否正确,如:{27,5,50,2,100,11,21}

3)对各个循环同时取最小循环次数或最大循环次数进行测试。在本例中两个循环的最小次数 1 都已有相关用例覆盖,这里不再重复设计。

综上所述,测试用例如下:

测试用例编号    输入    预期输出
testcase_01    {}    空
testcase_02    {6}    2 * 3
testcase_03    {75,11}    3 * 5 * 5
testcase_04    {20,6,90,21,45,76}    2 * 3 * 3 * 5
testcase_05    {1}    空
testcase_06    {2,1}    2
testcase_07    {4,1,3,2}    2 * 2
testcase_08    {27,5,50,2,100,11,21}    2 * 2 * 5 * 5
第 3 步:执行测试用例。

调用被测函数执行测试用例,判断实际执行结果与预期输出是否一致。具体的测试代码如下:

package test;
 
public class CascadeCycleTest {
    // 执行测试用例
    public static void main(String[] args){//初始化测试用例输入数据
        int[][] input_testcase = {
                {},
                {6},
                {75,11},
                {20,6,90,21,45,76},
                {1},
                {2,1},
                {4,1,3,2},
                {27,5,50,2,100,11,21},
        };
        //初始化测试用例预期结果
        String[] expect_result = {
                "",
                "2*3",
                "3*5*5",
                "2*3*3*5",
                "",
                "2",
                "2*2",
                "2*2*5*5",
        };
        
        //执行测试用例
        for (int i = 0; i < 8; i++){
            String execute_result = test(input_testcase[i]);
            //比较的执行结果与测试用例的预期输出是否一致:如果一致则用例执行通过,如果不一致则用例执行失败
            Boolean test_result = execute_result.equals(expect_result[i]);
            if (test_result){
                System.out.println("testcase_0" + (i + 1) + "执行通过!\n");
            }
            else{
                System.out.println("预期结果为:" + expect_result[i+1]);
                System.out.println("实际结果为:" + execute_result);
                System.out.println("testcase_0" + (i + 1) + "执行失败!\n");
            }    
        }    
    }
    
    public static String test(int[] numbers){  
        int max_number = 0;
        int factor = 2;
        String result = "";
        //求数据组中的最大值
        for (int i = 0; i < numbers.length - 1; i++){
            if (max_number < numbers[i]){
                max_number = numbers[i];
            }
        }
        //将最大值分解质因数
        int tmp = max_number;
        while(factor <= tmp){
            if(factor == tmp){
                result = result + Integer.toString(factor);
                break;
            }
            else if(tmp % factor == 0){
                result = result + factor + "*"; 
                tmp = tmp / factor;
            }
            else{
                factor++;
            }    
        }    
        System.out.println(max_number + "分解质因数的结果为:" + result);
        return result;    
    }    
}

以上代码在 Eclipse 中执行的结果如下图所示:

从上图中可以看出,测试用例 testcase_02 的执行结果为失败。分析该用例输出的实际结果,可以发现待分解质因数的数为空,也就是第一个循环(数据组中的最大值)没有正确地计算出数组 {6} 中的最大值。通过分析第一个循环的代码,我们可以发现问题出在 for 循环的最大循环次数,代码中 for 的最大循环次数是数组的长度减 1 ,导致只有一个数字的数组无法计算出最大值。

根据以上分析,我们可以将这段代码修改如下:

//求数据组中的最大值
for (int i = 0; i < numbers.length; i++){
    if (max_number < numbers[i]){
        max_number = numbers[i];
    }
}


修改代码后再次执行测试用例进行回归测试的结果如下:

总结:各个循环体彼此独立的串接循环可以使用简单循环的方法单独测试每一个循环,循环体之间有关联关系的串接循环则可以考虑使用嵌套循环的测试方法来进行测试。

文章知识点与官方知识档案匹配,可进一步学习相关知识
算法技能树首页概览59917 人正在系统学习中

没工作的小马
已关注

19


26

1


1 条评论
CSDN-Ada助手
热评
恭喜用户在软件测试领域不断深耕,第5篇博客“软件测试之单元测试----蓝桥杯”内容丰富,希望能够继续保持创作的热情和耐心。或许在下一篇博客中,可以探讨一下单元测试在实际项目中的应用案例,或者分享一些优秀的单元测试工具和技巧,相信会给读者带来更多启发和帮助。期待您的下一篇作品!愿您在创作路上不断进步,共同探讨、分享技朶,谢谢!
写评论
华为机考-软件测试试题.docx
04-30
华为机考-软件测试试题
MyBatis-Plus 如何单元测试的实现
08-24
主要介绍了MyBatis-Plus 如何单元测试的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
辅助软件 USB-TTL测试工具
06-16
辅助软件 USB-TTL测试工具辅助软件 USB-TTL测试工具辅助软件 USB-TTL测试工具辅助软件 USB-TTL测试工具辅助软件 USB-TTL测试工具辅助软件 USB-TTL测试工具辅助软件 USB-TTL测试工具辅助软件 USB-TTL测试工具辅助软件 USB-TTL测试工具辅助软件 USB-TTL测试工具辅助软件 USB-TTL测试工具辅助软件 USB-TTL测试工具辅助软件 USB-TTL测试工具辅助软件 USB-TTL测试工具辅助软件 USB-TTL测试工具辅助软件 USB-TTL测试工具辅助软件 USB-TTL测试工具辅助软件 USB-TTL测试工具辅助软件 USB-TTL测试工具辅助软件 USB-TTL测试工具辅助软件 USB-TTL测试工具辅助软件 USB-TTL测试工具辅助软件 USB-TTL测试工具辅助软件 USB-TTL测试工具辅助软件 USB-TTL测试工具辅助软件 USB-TTL测试工具辅助软件 USB-TTL测试工具辅助软件 USB-TTL测试工具辅助软件 USB-TTL测试工具辅助软件 USB-TTL测试工具辅助软件 USB-TTL测试工具辅助软件
软件工程-软件测试.pptx
11-14
软件工程-软件测试.pptx
软件测试岗位具体是做什么的?
神秘博客
 4396
软件测试相对一些开发的专业来说需要了解的东西比较多,拿一些招聘岗位要求来看,大部分都要求熟悉网络、linux系统、数据库、软件测试流程及理论、Java或者python等脚本开发语言、自动化测试理论及工具使用、接口测试、安全测试等。每个人对一件事的理解不同,同理不同人员对需求的理解可能会存在差异,所以适当的时候要检查下代码是否有业务逻辑错误和代码逻辑错误,当然达不到检测程序的,可以通过手工测试来做。二、测试技能及方法:包括测试基本概念及方法、对测试工具的掌握、对专业测试标准的熟悉程度等;
软件测试学习(蓝桥杯备赛)
m0_62835291的博客
 1115
软件测试进行到一定程度就要进行测试评估了。通过测试评估生成的软件测试报告来确定测试是否达到了出口准则。
15届蓝桥杯(软件测试)单元测试专项练习解析【python篇】
m0_62835291的博客
 258
15届蓝桥杯(软件测试)单元测试专项练习解析【python篇】
第十五届蓝桥杯软件测试类备考(Python)
m0_62835291的博客
 641
第十五届蓝桥杯软件测试组竞赛说明
大气扁平化家居装修设计企业网页模板5773.zip
04-07
大气扁平化家居装修设计企业网页模板5773.zip
Java 或大数据开发者找工作必备材料
最新发布
04-07
计算机网络、操作系统、数据库、数据结构和Java是计算机科学领域的五个关键技术领域,对于IT专业人士来说,精通这些技术是职业发展的重要基石。 计算机网络是实现数据交换和资源共享的技术基础,关键在于理解网络协议、系统架构和网络安全等概念,以确保信息流动的高效与安全。 操作系统作为计算机系统的核心,负责管理和协调硬件资源与应用软件,其知识包括进程管理、内存管理和文件系统等,对于提高系统效率和稳定性至关重要。 数据库技术涉及数据的存储、查询和维护,掌握SQL语言和数据模型能够帮助高效地处理和分析数据,对于数据驱动的决策制定具有显著意义。 数据结构是算法设计的核心,通过学习数组、链表、树等结构,可以提高编程效率,优化算法性能,是解决复杂问题的技术前提。 Java作为一种流行的编程语言,以其跨平台和面向对象的特性广泛应用于企业级应用开发。掌握Java基础和高级特性,能够助力开发者构建稳定、可扩展的软件解决方案。 综合这些技术领域,可以构建坚实的计算机科学知识体系,为职业发展和技术创新打下坚实基础。
实验课代码.zip
04-07
实验课代码.zip
python实现基于联邦学习和NSL-KDD数据集的网络入侵检测源码+文档说明(高分项目).zip
04-07
python实现基于联邦学习和NSL-KDD数据集的网络入侵检测源码+文档说明(高分项目).zip本资源中的源码都是经过本地编译过可运行的,资源项目的难度比较适中,内容都是经过助教老师审定过的能够满足学习、使用需求,如果有需要的话可以放心下载使用。 python实现基于联邦学习和NSL-KDD数据集的网络入侵检测源码+文档说明(高分项目).zip本资源中的源码都是经过本地编译过可运行的,资源项目的难度比较适中,内容都是经过助教老师审定过的能够满足学习、使用需求,如果有需要的话可以放心下载使用。 python实现基于联邦学习和NSL-KDD数据集的网络入侵检测源码+文档说明(高分项目).zip本资源中的源码都是经过本地编译过可运行的,资源项目的难度比较适中,内容都是经过助教老师审定过的能够满足学习、使用需求,如果有需要的话可以放心下载使用。 python实现基于联邦学习和NSL-KDD数据集的网络入侵检测源码+文档说明(高分项目).zip本资源中的源码都是经过本地编译过可运行的,资源项目的难度比较适中,内容都是经过助教老师审定过的能够满足学习、使用需求,如果有需要的话可以放心下载
汇编语言程序设计汇编语言
04-07
本章讲述: 4.1 汇编语言程序格式 4.2 伪指令语句 4.3 DOS系统功能调用和BIOS功能调用 4.4 程序设计方法 4.5 宏汇编和条件汇编
连续PID.zip
04-07
连续PID.zip
Clustering_Coefficient.m.zip
04-07
Clustering_Coefficient.m.zip
全屏漂亮左栏固定摄影商务网站模板6050.zip
04-07
适合于前端新人,也适用于基于模板快速制作网站
模型M-BERT-Base-ViT-B.zip
04-07
模型M-BERT-Base-ViT-B.zip
软件测试自动化模式--混合模式
06-13
在软件测试自动化中,混合模式通常是指将多种测试自动化模式结合起来,以达到更好的测试覆盖率、更高的测试效率和更好的测试质量。 混合模式可以结
————————————————

                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
                        
原文链接:https://blog.csdn.net/weixin_60728436/article/details/137286018

  • 20
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型的编程语言。它旨在成为一门简单、高效、安全和并发的编程语言,特别适用于构建高性能的服务器和分布式系统。以下是Go语言的一些主要特点和优势: 简洁性:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特性,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 高性能:Go语言具有出色的性能,可以媲美C和C++。它使用静态类型系统和编译型语言的优势,能够生成高效的机器码。 并发性:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建高性能的服务器和分布式系统时具有天然的优势。 安全性:Go语言具有强大的类型系统和内存管理机制,能够减少运行时错误和内存泄漏等问题。它还支持编译时检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库非常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、加密解密等。这使得开发者可以更加专注于业务逻辑的实现,而无需花费太多时间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、高效、安全、并发的编程语言,特别适用于构建高性能的服务器和分布式系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值