功能测试

等价类划分法

通过科学的方法找到具有共同特性的测试输入的子集,能够从穷举测试中解放(大大减少了测试用例的数量,从而提升测试效率。)典型应用场景比如:输入框

  • 设计测试用例,需求分析,划分等价类
  1. 有效
  2. 无效
  3. 规则(需求本身)
  4. 长度
  5. 类型
  6. 是否为空(必填项)
  7. 是否重复

边界值

对等价类的补充,统计表明程序最容易出错的地方就是在边界附近典型应用场景:存在边界,>,>=,<,<=

  • 上点:边界之上的点
  • 内点:边界之内的点
  • 离点:离边界最近的左右两点

判定表

存在多个输入条件、多个输出结果,输入和输入之间有组合关系,输入和输出之间有依赖或制约关系

  • 条件桩:所有输入条件,如欠费状态、关机状态
  • 动作桩:所有的可能的输出结果,如打通、打不通
  • 条件项:单个条件的取值范围,一般都是有效等价类和无效等价类
- 字符:
  - 真/有效等价类/Y
  - 假/无效等价类/N
- 数字:
  - 真/有效等价类/1
  - 假/无效等价类/0
  • 动作项:基于每一种条件的组合,得到确认的结果,如打不通等

设计测试用例的步骤

  1. 明确条件桩(找到所有的输入条件)
  2. 明确动作桩(找到所有的输出结果)
  3. 对条件桩进行全组合
  4. 明确每个组合对应的动作项(基于每一种条件的组合情况,确定本组合下的输出结果。)
  5. 设计测试用例,每行数据对应一条测试用例

正交法

用最小的测试用例获得最大的测试覆盖率

11_因素与水平.png

  • k代表因素(输入参数)
  • m叫水平(输入参数的取值)
  • n代表测试用例数
  • 读法:k因素m水平
  • 比如如图所示3因素2水平代表一共有3个参数每个参数都对应2个值,一共有8种结果,根据这张表进行4次试验,可以获得最大的覆盖率,可以根据实际的情况对照不同的正交表

基于正交表设计测试用例

  • 需求分析
  • 确定因素与水平(因素:控件名称;水平:每个控件对应的取值)
  • 确定要采用的正交表
  • 将正交表中的字母用文字代替
  • 设计测试用例(一行就是一条测试用例)

场景法(流程图法)

场景法就是模拟用户操作软件时的场景,主要用于测试多个功能之间的组合使用情况

- 使用测试阶段

  - 集成测试
  - 系统测试
  - 验收测试

- 设计测试用例的步骤

  - 需求分析

  - 绘制流程图

  - 设计测试用例(一条流程路径就是一条测试用例)

- 流程图常用符号

  - 开始或结束:椭圆

  - 方向或路径:箭头

  - 处理或操作:长方形

  - 判断:菱形

  - 输入或输出:平行四边形

- 绘制流程图

  - 第1步:确认场景中关键业务步骤

  - 第2步:确定业务之间的先后顺序

  - 第3步:用箭头连接即可

测试用例设计方法总结

  • 具有输入功能,但输入之间没有组合关系==》【等价类】
  • 输入有边界 如长度、类型==》【边界值】
  • 多输入、多输出、输入与输入之间存在组合关系、输入与输出之间存在依赖或制约关系==》【判定表】
  • 用最少的测试用例获得最大测试覆盖率时 ==》【正交法】
  • 多个功能的组合测试 ==> 【场景法、流程图】
  • 最后推荐使用【错误推测法】来进一步补充测试用例

缺陷及缺陷管理

缺陷的定义

  • 产品实现不满足用户需求
  • 测试执行时,实际结果和预期结果不一致

缺陷的判定标准

  • 未达到需求说明书指明的功能
  • 出现了需求说明书指明不应该出现的错误
  • 实现了需求说明书之外的功能
  • 未达到需求说明书虽未明确提及但是应该实现的目标(如:性能要求等)
  • 用户角度发现的各种问题与错误

缺陷产生的原因及根本原因

缺陷产生的原因

  • 需求文档存在错误
  • 需求变更
  • 设计存在错误
  • 代码错误

缺陷产生的根本原因

  • 需求变更
  • 沟通不畅、信息不同步
  • 软件复杂
  • 进度压力

软件缺陷的核心内容

  • 标题:描述缺陷的基本信息,如(输入密码长度为5时,竟然注册成功。)
  • 前置条件:描述缺陷出现依赖的相关基础条件,如(未注册用户名)
  • 复现步骤:测试用例里面的执行步骤
  • 实际结果:执行被测试软件过程中,系统给出的结果
  • 预期结果:参照需求说明书,在测试用例中设计的预期结果
  • 附件:方便开发定位bug的关键信息,包含图片、日志log等

缺陷基本要素

  • ID编号:唯一
  • 模块:根据产品进行具体的划分,如登录、注册
  • 缺陷状态:表明缺陷处理进度
  • 严重程度:从技术维度来衡量,bug的破坏力
  • 优先级:从业务的角度,决定bug修改的先后顺序
  • 缺陷类别:用于分类整理缺陷

缺陷的状态

  • new:新建
  • open:打开
  • fix:已修复
  • close:关闭
  • reopen:重新打开
  • reject:已拒绝
  • postpone:延期

缺陷严重程度

  • 5-致命的
  • 4-非常高
  • 3-高
  • 2-中
  • 1-低

缺陷优先级

  • 5-紧急的
  • 4-非常高
  • 3-高
  • 2-中
  • 1-低

缺陷类别

  • 功能错误
  • UI界面错误
  • 兼容性
  • 易用性
  • 改进建议
  • 其他

缺陷书写规范

  • 标题:应保持简短、准确,提供缺陷的本质信息
  • 复现步骤:应包含如何使别人能够很容易的复现该缺陷的完整步骤
  • 实际结果:是执行复现步骤后软件的现象和产生的行为
  • 预期结果:通常需要列出期望的结果是什么
  • 附件:对缺陷描述的补充说明

缺陷跟踪流程

  • 场景1:确认BUG解决
  • 测试【new】==》开发【open】==》开发【fix】==》测试【close】
  • 场景2:验证未通过,缺陷仍存在
  • 测试【new】==》开发【open】==》开发【fix】==》测试【reopen】
  • 场景3:开发延期处理
  • 测试【new】==》开发【open】==》开发【postpone】
  • 场景4:拒绝处理
  • 测试【new】==》开发【open】==》开发【reject】

UnitTest单元测试框架

单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证,对于单元测试中单元的含义,要根据实际情况去判定其具体含义,一个单元可能是功能模块、类、方法(函数)等,不同的编程语言都有比较成熟的单元测试框架,语法规则有些差别,其核心思想都是相通的,常见的单元测试框架有:

  • Java语言:Junit、TestNG
  • Python语言:UnitTest、Pytest

UnitTest框架概念

UnitTest是Python自带的一个单元测试框架,用它来做单元测试。也经常应用到UI自动化测试和接口自动化测试中,用来管理和维护测试用例脚本

  • 使用UnitTest框架的好处
  1. 能够组织多个用例去执行(可以把多条测试用例封装成一个测试套件,实现批量执行测试用例)
  2. 提供了丰富的断言方法,方便对用例执行的结果进行判断
  3. 能够生成HTML格式的测试报告
  4. 使用Fixture功能可以减少代码的冗余

UnitTest使用

  • 定义一个实现加法操作的函数,并对该函数进行测试,使用pycharm在代码上点击鼠标右键,选择使用UnitTest运行
# test01_add.py
import unittest

def add(x, y):
    return x + y

# 必须要集成TestCase类
class TestAdd(unittest.TestCase):

    # 定义测试方法1
    def test01_add(self):
        result = add(1, 1)
        print('result1=', result)

    # 定义测试方法2
    def test02_add(self):
        print('result2=', result)
        result = add(2, 2)

QQ截图20230425154118.jpg

TestSuite

测试套件可以将多条测试用例集合在一起,就是一个TestSuite使用注:TestSuite需要配合TestRunner才能被执行

  1. 实例化:suite = unittest.TestSuite() (suite:为TestSuite实例化的名称)
  2. 添加用例:suite.addTest(ClassName("MethodName"))(ClassName:为类名;MethodName:为方法名)
  3. 添加扩展:suite.addTest(unittest.makeSuite(ClassName))(搜索指定ClassName内test开头的方法并添加到测试套件中)
import unittest

def ssuumm(a,b):
    return a+b

class Testssuumm(unittest.TestCase):
    def test_s1(self):
        res = ssuumm(1,1)
        self.assertEqual(res,2)
    def test_s2(self):
        res = ssuumm(3,1)
        self.assertEqual(res,4)
# 添加多个测试套件,运行测试用例
import unittest

from test import Testssuumm

#实例化测试套件对象
suite = unittest.TestSuite()
# 添加测试用例
suite.addTest(Testssuumm("test_s1"))
suite.addTest(Testssuumm("test_s2"))
#实例化运行器对象
runner = unittest.TextTestRunner()
#运行测试套件
runner.run(suite)

QQ截图20230425155243.jpg

Fixture

Fixture是对一个测试用例环境的初始化和销毁操作

  • 方法级别使用运行一次测试方法就会运行一次 setUp 和 tearDown
  • setUp(self):方法初始化时执行
  • tearDown(self):方法销毁时执行
  • 类级别的使用每个测试类只会 运行一次 setUpClass 和 tearDownClass
  • setUpClass(cls):类初始化时执行
  • tearDownClass(cls):类销毁时执行
import unittest

def ssuumm(a,b):
    return a+b

class Testssuumm(unittest.TestCase):

    # 类级别,初始化、前置处理,测试开始时只执行一次
    @classmethod
    def setUpClass(cls) -> None:
        print('类初始化')

    # 类级别,销毁、后置处理,测试结束时只执行一次
    @classmethod
    def tearDownClass(cls) -> None:
        print('类销毁')

    # 每个测试方法执行时都会执行此函数
    def setUp(self) -> None:
        print('方法初始化')

    # 每个测试方法结束时都会执行次函数
    def tearDown(self) -> None:
        print('方法销毁')

    def test_s1(self):
        res = ssuumm(1,1)
        self.assertEqual(res,2)
    def test_s2(self):
        res = ssuumm(3,1)
        self.assertEqual(res,4)

UnitTest断言

让程序代替人为判断测试程序执行结果是否符合预期结果的过程,UnitTest 中提供了非常丰富的断言方法,但是常用的也就那么几个,并且使用起来也比较简单

07_断言方法.png

import unittest

def ssuumm(a,b):
    return a+b

class Testssuumm(unittest.TestCase):

    def test_s1(self):
        res = ssuumm(1,1)
        self.assertEqual(res,2)
    def test_s2(self):
        res = ssuumm(3,1)
        self.assertIn(str(res),'123456')
    def test_s3(self):
        res = ssuumm(3,1)
        self.assertTrue(res==4)

UnitTest参数化

通过参数的方式来传递数据,从而实现数据和脚本分离,也可以把测试数据定义到数据文件或者数据库中,针对同一个测试方法,可以实现用例的重复执行,减少代码冗余,提高测试效率,unittest测试框架,本身不支持参数化,但是可以通过安装 unittest 扩展插件rameterized 来实现

  • 环境安装:pip3 install parameterized
import unittest
from parameterized import parameterized

def ssuumm(a,b):
    return a+b

def get_data():
    return [(1,2),(2,1)]

class Testssuumm(unittest.TestCase):

    @parameterized.expand([(1,2),(2,1)])
    def test_s1(self,a,b):
        print(a)
        print(b)
        res = ssuumm(a, b)
        self.assertEqual(res,3)

    @parameterized.expand(get_data())
    def test_s2(self,a,b):
        print(a)
        print(b)
        res = ssuumm(a, b)
        self.assertEqual(res,3)

生成HTML测试报告

测试脚本执行完后,可以生成以HTML格式的测试报告

import unittest

from test import Testssuumm
from HTMLTestRunner import HTMLTestRunner

suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(Testssuumm))

filename = "report-{}.html".format(time.strftime("%Y-%m-%d %H:%M:%S"))
with open(filename,'wb') as f:
    runner = HTMLTestRunner(f,title='testreport')
    runner.run(suite)

达达商城登录接口测试

08_达达商城登录模块测试用例.png

编写测试用例代码 - 未传参

import unittest
from dadatest import Testdada

suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(Testdada))
runner = unittest.TextTestRunner()
runner.run(suite)
  • 测试代码部分
import json
import unittest

import requests


class Testdada(unittest.TestCase):
    def setUp(self) -> None:
        self.url = 'http://127.0.0.1:8000/v1/tokens'

    def test_login1(self):
        data = {
            'username':'111111',
            'password':'111111',
            'cart':'null',
        }
        res = requests.post(self.url,data=json.dumps(data))
        self.assertEqual(res.json().get('code'),200)

    def test_login2(self):
        data = {
            'username':'eee',
            'password':'111111',
            'cart':'null',
        }
        res = requests.post(self.url,data=json.dumps(data))
        self.assertEqual(res.json().get('code'),20100)


    def test_login3(self):
        data = {
            'username':'111111',
            'password':'00',
            'cart':'null',
        }
        res = requests.post(self.url,data=json.dumps(data))
        self.assertEqual(res.json().get('code'),20200)

编写测试用例代码 - 传参

import unittest
import requests
import json
from parameterized import parameterized

# 构造测试数据
def build_data1():
    test_data = []
    file = './data/login.json'
    with open(file, encoding='utf-8') as f:
        json_data = json.load(f)
        for case_data in json_data:
            username = case_data.get('username')
            password = case_data.get('password')
            code = case_data.get('code')
            test_data.append((username, password, code))
    return test_data


def build_data2():
    db = pymysql.connect('localhost','root','123456','test_dadashop_db', charset='utf8')
    cur = db.cursor()
    sele = 'select * from dadashop_login_tab'
    cur.execute(sele)
    db_data = cur.fetchall()
    test_data = []

    for case_tuple in db_data:
        username = case_tuple[2]
        password = case_tuple[3]
        code = case_tuple[5]
        test_data.append((username, password, code))
    return test_data

class Testdada(unittest.TestCase):
    def setUp(self) -> None:
        self.url = 'http://127.0.0.1:8000/v1/tokens'

    @parameterized.expand(build_data1())
    def test_login1(self, username, password, code):
        data = {
            'username':username,
            'password':password,
            'cart':'null',
        }
        res = requests.post(self.url,data=json.dumps(data))
        self.assertEqual(res.json().get('code'),code)

    @parameterized.expand(build_data2())
    def test_login2(self, username, password, code):
        data = {
            'username':username,
            'password':password,
            'cart':'null',
        }
        res = requests.post(self.url,data=json.dumps(data))
        self.assertEqual(res.json().get('code'),code)
  • json数据
[
  {
    "desc": "登录成功",
    "username": "chaogege",
    "password": "123456",
    "carts": "null",
    "code": 200,
    "msg": "success"
  },
  {
    "desc": "用户名错误",
    "username": "chaoerror",
    "password": "123456",
    "carts": "null",
    "code": 20100,
    "msg": "username is wrong"
  },
  {
    "desc": "密码错误",
    "username": "chaogege",
    "password": "123error",
    "carts": "null",
    "code": 20101,
    "msg": "password is wrong"
  }
]
  • mysql数据库sql
create database test_dadashop_db default charset utf8;
use test_dadashop_db;
create table dadashop_login_tab(
id int primary key auto_increment,
case_desc varchar(100),
username varchar(100),
password varchar(100),
carts varchar(50),
code int,
msg varchar(200)
);
insert into dadashop_login_tab values(1,"登录成功","chaogege","123456","null", 200, "success"),(2,"用户名错误","chaoerror","123456","null", 20100, "username is wrong"),(3,"密码错误","chaogege","123error","null", 20101, "password is wrong");