软件测试

就业方向(功能测试+任一种)

image-20240828111411247

python+测试工具

image-20240830143217121

image-20240830143348018

框架的完善

  1. 过程控制
  2. 日志记录
  3. 测试报告
  4. UI画面回放

技术栈

allure,yaml,jenkins,log

Charles,knife4j

工具类

自动化测试

  • WEB测试:Selenium,Robotramework:这是一个通用的自动化测试框架,支持关键字驱动的测试方法。它可以用于功能测试、验收测试和其他自动化测试任务,支持多种库和工具集成。

    App测试:Appium(app自动化测试),熟悉airtest框架,掌握常用adb命令

  • 接口测试工具:Postman,apifox,swagger

  • 性能测试工具:JMeter,LoadRunner,

    ​ locust (用于分布式性能测试,能够模拟大量用户对Web应用程序进行负载测试。基于Python,支持分布式测试,易于编写测试脚本和查看结果。),grafana监控平台

  • 缺陷管理工具:禅道,Azure DevOpsJira

  • 抓包工具:fiddler,Charles

熟悉测试流程,能够根据业务需求设计和输出测试用例,执行测试流程,并跟进缺陷修复;
熟练掌握python,java基础知识,能够使用pytest等测试相关框架编写自动化测试脚本;
熟悉Web测试,可以使用Selenium+Unittest+Python进行WEB端功能自动化测试及脚本维护;
熟悉接口测试,可以使用pytest+requests+allure报告进行接口测试;
熟悉性能测试,了解jmeter和locust性能测试和性能监控等命令
熟练使用Postman,apifox,fiddler,Charles,LoadRunner,Jenkins、禅道等测试及持续集成工具;
熟练使用XMind提取测试点,编写测试用例,保证较高的测试覆盖率;
熟悉Mysql数据库和对sql语句的编写,对事务,索引,锁都有进一步理解;
熟悉Linux常用的shell命令和git版本控制命令;
熟悉Spirng、SpringMVC、Mybatis等技术栈,拥有Springboot+MyBatisPlus的项目开发经验。
技术架构:Springboot+Mybatisplus+Springsecurity+Redis+Mysql+Vue+Swagger+Linux
项目介绍:宇你音乐是基于Springboot+Vue开发的音乐在线网站,项目分为客户端跟管理端。客户端拥有用户登录注册,歌曲跳转播放,歌曲收藏,歌词实时显示,歌单评论及点赞,歌单评分,个人信息修改等功能。管理端拥有管理员登录,用户管理,歌手管理,歌单管理,歌曲管理,评论管理等多个功能模块。
1. 使用MybatisPlus快速搭建对应控制层,业务实现层,数据访问层及实体类;
2. 利用 Redis 缓存用户信息,减少对数据库的频繁访问,提高查询效率;
3. 使用拦截器,用Cookie记录用户状态,解决 http 无状态问题,增强了系统安全性;
4. 利用spring提供的BeenUtils工具类实现集合元素映射以及单个对象映射;
5. 使用阿里云Oss对象存储服务上传所需的音乐文件,图片文件等;
6. 使用Hutool工具包生成图片验证码,增强用户登录安全性,防止恶意攻击;
7. 使用Springsecurity对数据库进行密码加密,确保密码存储在数据库中的安全性;
8. 将项目打成Jar包,在Linux上进行远程项目部署。

歌手模块自动化接口测试

使用knife4j生成的在线接口文档,详细分析Api接口并编写相关接口测试用例
使用selenium+pytest编写自动化测试用例,执行测试用例并重现测试中出现的bug
使用pytest的参数化功能,为测试用例提供多个用户数据,实现数据驱动测试
使用allure生成测试报告,包含测试用例的执行结果,断言详情等,便于缺陷的追踪与修复
有较强的自主学习能力,熟练使用ChatGPT、Google 等工具,能快速上手新技术;
工作细心,善于发现问题,有独立解决问题的能力;
对互联网行业有热情,善于总结,并时常发表博客记录学习;
抗压能力强,有较强的沟通技巧及团队合作精神。

考点

1引导问你会的

自我介绍+技术优势+总结尾
测试流程?
使用过的测试工具
为什么选择测试?
为什么写博客?博客的点击量有多少
在学校参加过的社团,哪个印象深刻,为什么?

1.退出的不同?

driver.close() # 关闭浏览器,窗口退出
driver.quit()	# 关闭窗口

2.selenium底层原理

webdriver协议(Http接口),webdriver是浏览器的驱动

对一个不确定的功能测试,首先要明确需求

升级测试

回归测试考虑旧数据是否正常

怎么展开测试

  1. 需求分析:在测试开始前,深入了解软件的功能需求和业务目标,确保测试覆盖所有关键点
  2. 测试计划:制定全面的测试计划,包括测试目标、范围、资源需求、时间表和风险评估。
  3. 测试设计:设计具体的测试用例,包含前置条件、预期结果和测试步骤。编写自动化测试脚本(如果适用)。
  4. 测试环境准备:搭建测试环境,确保测试与生产环境相似,配置相关的硬件、软件和网络条件。
  5. 测试执行:根据测试用例执行测试,记录实际结果,并与预期结果对比,发现并记录缺陷。
  6. 缺陷管理报告缺陷并跟踪修复进展,确认问题解决后重新测试。
  7. 测试总结:编写测试报告,总结测试结果,评估软件的质量和测试覆盖度,并提供改进建议。

测试必备工具

fiddler

Jenkins(github actions,gitlab CI)

  • 概述:Jenkins 是一个开源的自动化服务器,广泛用于持续集成和持续交付(CI/CD)。它可以自动化构建、测试、部署等软件开发流程。
  • 特点
    • 插件系统:Jenkins 拥有丰富的插件生态系统,支持各种功能扩展,如代码质量分析、自动化部署等。
    • 可定制性:通过配置文件和插件,可以高度定制 Jenkins 的工作流程。
    • 分布式构建:支持分布式构建,可以将构建任务分配到多台机器上,提高构建效率。

allure

Robotramework

Jira

基础概念

测试分类

测试阶段

image-20240828112632202

代码可见度

image-20240828113017170

质量模型

功能,性能,兼容,易用,安全,可靠性,移植性维护性。

测试流程

image-20240828114759784

测试用例

功能点用例编写:

  1. 分为正向,逆向(功能点较小的情况下);
  2. 分为页面所拥有的属性(功能页面较大);

image-20240924163150122

1.列出所有参数及其对应规则,包括必填参数及非必填参数(提取测试点)

2.编写正向,逆向测试用例

​ 正向:…成功(必填参数/全部参数填写正确)

​ 逆向:…失败(必填参数:空,长度不符,类型不符,规则不符)

作用:防止漏测,实施规范

提取测试点

image-20240909205228332

设计测试用例

正向用例:一条尽可能覆盖多条(数测试模块中最多正向方向的)

逆向用例:每一条都是单独用例

用例设计编写格式:

image-20240828141138704

测试方法

编写测试用例的时候,通常可以采用 条件组合、边界值、错误猜测 等方法。

4.1等价类划分(针对穷举场景)

适用场景:表单类页面元素测试使用(输入框、单选按钮、下拉列表)

步骤:

1.明确需求

2.确定有效等价(满足需求),无效等价(不满足需求)

3.根据上面设计数据,编写用例

image-20241015233529072

案例:验证6-10位qq合法性

image-20240828151252346

4.2边界值测试方法(针对边界限定问题)

边界范围结点(7个点)

image-20240828163139834

步骤:

1.明确需求

2.确定有效等价,无效等价

3.确定边界值

4.根据上面设计数据,编写用例

优化(7点化5点)

上点,内点是必须的,离点按照开内闭外,开区间选择内部离点,闭区间选择外部离点

单个输入框常用的方式是:边界+等价类

image-20241015235911820

4.3判定表法(针对多条件依赖测试)

步骤:

1.明确需求

2.画出判定表

  • 列出条件桩和动作桩
  • 填写条件项,
  • 根据条件项组合确定动作项
  • 简化,合并相似规则

3.设计数据,编写用例

image-20240828185835819

4.4场景法

业务测试覆盖需要使用流程图

先测试业务,再测试单功能,单模块,单页面

流程图使用椭圆做开始结束,使用棱形表判断,使用方框表语句

image-20240828192550313

4.5错误推荐法

缺陷

概念:软件中存在的各种问题不符合业务功能预期,称为缺陷,简称bug;

类型:

  1. 少功能
  2. 多功能
  3. 功能错误
  4. 隐形错误
  5. 易用性

产生原因:

image-20240829095205116

缺陷类型:

  • 功能错误
  • ui页面错误
  • 兼容性
  • 数据库
  • 易用性
  • 架构缺陷

工作流程:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

注册案例

image-20240829103324676

缺陷编写

发现bug后首先要确保缺陷可以复现

描述缺陷

image-20240829221322799

登录模块案例

3.1登录测试点

登录测试点分为

测试某一个点时(无论正向还是逆向),其他的点也应该默认是正向的

image-20240830094729465

非功能主要是测试五大浏览器的兼容性,兼容与UI布局 还有滑块的拖动。

3.2登录用例编写

用例标题写法:期望结果(实现依据原因)

优先级成功的写p0

项目,前置条件一般不变,测试数据跟前置条件可以先写

测试步骤即实际操作过程,若是边界值要使用次数表明,预期结果根据预测而定

用例-模块-编号用例标题项目/模块优先级前置条件测试步骤测试数据预期结果
login-01发送验证码成功(滑块到指定位置)滑块p01.输入手机号
2.打开滑块界面
1.拖动滑块到指定位置1.手机号:正确手机号手机号收到验证码成功
login-02发送验证码失败(滑块一次未到指定位置)滑块p11.输入手机号
2.打开滑块界面
1.拖动滑块一次未到指定位置1.手机号:正确手机号1.手机号没有收到验证码2.滑块抖动效果1次3.回到初始位置
login-03发送验证码失败(滑块三次未到指定位置)滑块p11.输入手机号
2.打开滑块界面
1.拖动滑块三次未到指定位置1.手机号:正确手机号1.手机号没有收到验证码2.滑块抖动效果3次3.回到初始位置
login-04发送验证码失败(滑块五次未到指定位置)滑块p11.输入手机号
2.打开滑块界面
1.拖动滑块五次未到指定位置1.手机号:正确手机号1.手机号没有收到验证码2.滑块抖动效果5次3.回到初始位置
login-05发送验证码失败(滑块错误次数达到6次)滑块p11.输入手机号
2.打开滑块界面
1.拖动滑块六次未到指定位置1.手机号:正确手机号1.手机号没有收到验证码2.滑块抖动效果5次3.滑块消失,提示重试

基础测试最后有总结,可以观看5-14-总结_哔哩哔哩_bilibili快速回顾


python

9/13

input("enter your name") # input函数只能输入str,所以输入数字时需要用int(str)强转
\ 表转义, # 表注释 ,e9表科学计数法中的10的9次方 , r '' 单引号中内容不转义 // 表整除
''' damage 
... kewu ''' # 表多行内容
a = 'ABC',创建了字符串'ABC'和变量a,并把a指向'ABC'
b = a,创建了变量b,并把b指向a指向的字符串'ABC'ord("A") 65 # ord()函数获取字符的整数表示,
chr(66) 'B'# chr()函数把编码转换为对应的字符
len('中文'.encode('utf-8')) 1个中文字符经过UTF-8编码后通常会占用3个字节,而1个英文字符只占用1个字节。
'Hello, %s' % 'world'  # 先正常写字符串,接着空格 % '替换字符'
'Hello, {0}, 成绩提升了 {1:.1f}%'.format('小明', 17.125)  # :后面的.1f指定了格式化参数(即保留1位小数)
print(f"hello, {x}") # f-string格式化,这里就输出值 
f"断言失败: {count=}" # 这里会将count变量及count值一起输出,是新特性

2.1list和tuple

python中一种内置的数据类型列表:list,list是可变的,而str是不可变,尽管指向可以改变

a = ['porter','damage'] # 通过a[-1] 或 a[len(a) - 1]获取到数组最后一个元素
a.append("fly") # 往数组末尾加元素,删除末尾元素用a.pop()
a.insert(1,"dd") #往指定索引处插入元素,删除指定索引元素a.pop(1),替换某个索引处元素直接赋值
p = ['asp', 'php'] s = ['python', 'java', p, 'scheme'] #要拿到'php'可以写p[1]或者s[2][1]

另一种有序列表叫元组:tuple,tuple一旦初始化就不能修改,因此比list更安全

t = (1, 2) # 基本定义 t = () 只有1个元素的tuple定义时必须加一个逗号,来消除歧义
# turple里面的元素也可以是列表,修改列表中元素是可以的,即指向元素不变

2.2条件判断

age = 3
if age >= 18: # 这里的: 相当于{},他会把下面的缩进内容一起执行了
    print('adult')
elif age >= 6:
    print('teenager')
else:
    print("kid")

2.3循环与匹配

匹配

score = "B"
match score:
	case 'A':
		print("A")
	case 'B':
		print("B")
	case _:
		print("表其他任何情况")

循环(for in ; while)

在循环过程中,也可以通过continue语句,跳过当前的这次循环,直接开始下一次循环。

range(101) # 生成0-100的list列表
for x in [1,2,3]:
    sum = sum + x;
print(sum)

sum = 0
n = 99
while n > 0:
    sum = sum + n
    n = n - 2
print(sum)

2.4字典

dict

字典即dict,即其他语言中的Map,使用键-值(key-value)存储,具有极快的查找速度

d = {'Michael': 95, 'Bob': 75, 'Tracy': 85} # 直接赋值,里面用逗号隔开
print(d['Michael']) 
print("Bob"in d ) # 判断是否有该key
d.get('BOb',10) # dict提供的get()方法,如果key不存在,可以返回None,或者自己指定的value:

set 也是一组key的集合,但不存储value.在set中,没有重复的key。

重复元素在set中自动被过滤:重复元素在set中自动被过滤

remove(key)方法可以删除元素:

2.5函数

hes(254) # 可以将十进制254转为16进制0xfe  255/16 = 15余14  
0x表16进制,余数先出来,先从右边开始,商大于16就一直除 15表f,14表e,从右边写起

定义函数

def my_abs(x): # 函数名(x):
	if not isinstance(x, (int, float)): # 对参数类型做检查,只允许整数和浮点数类型的参数
        raise TypeError('bad operand type')
    if x >= 0:
        return x
    else:
        return -x

空函数

def nop():
    pass # pass 起到占位作用,可以让代码运行起来

返回多个值

Python的函数返回多值其实就是返回一个tuple ,在语法上,返回一个tuple可以省略括号,而多个变量可以同时接收一个tuple,按位置赋给对应的值

import math
def quadratic(a, b, c):
    k = b*b - 4*a*c
    x1 = (-b + math.sqrt(k)) / (2*a)
    x2 = (-b - math.sqrt(k)) / (2*a)
    return x1, x2

2.5.1函数参数

函数的参数(默认参数必须指向不变对象)默认参数就是在参数后面赋值,如果没有传参就默认使用该默认值 例如:def power(x, n=2): def add(desc:str = None): #desc:str=None,即desc只能是str类型,并且是默认参数,若没有传递就是None

# 假设参数定义成L = [] ,因为默认参数L也是一个变量,它指向对象[],每次调用该函数,如果改变了L的内容,则下次调用时,默认参数的内容就变了,不再是函数定义时的[]了
def add_end(L=None): # 这里的None就是不变对象,使用之后就可以避免多次调用后出现end添加多次
    if L is None:
        L = []
    L.append('END')
    return L

可变参数, 直接在参数前面加*,这里的可变具体是指参数的数量

def calc(numbers):  # 这里的numbers可以变为*numbers,这样就可以直接calc(1,2,3)随意参数调用
    sum = 0
    for n in numbers:
        sum = sum + n * n
    return sum
calc([1, 2, 3]) # 组装一个list调用,假设已有一个l列表,我们也可以calc(*l)去调用该方法
calc((1, 3, 5, 7))  # 组装一个tuple调用

关键字参数,** kw

允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict(键值对)

可以扩展函数的功能,因此放于函数参数的最后一位

def person(name, age, **kw):
    print('name:', name, 'age:', age, 'other:', kw)
# 调用,假设已有一个现成字典d,那么可以则直接person('Adam', 45,**d)
person('Adam', 45, gender='M', job='Engineer') 
name: Adam age: 45 other: {'gender': 'M', 'job': 'Engineer'} # 输出

命名关键字参数 即使用*隔开,*后面的参数被视为命名关键字参数

def person(name, age, **kw):
    if 'city' in kw: # 检测是否传入city参数
        # 有city参数
        pass

如果函数定义中已经有了一个可变参数,面跟着的命名关键字参数就不再需要一个特殊分隔符*

命名关键字参数必须传入参数名,这和位置参数不同。如果没有传入参数名,调用将报错

def person(name, age, *args, city, job):

参数组合,顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数。

对于任意函数,都可以通过类似func(*args, **kw)的形式调用它,无论它的参数是如何定义的。

image-20240831132734594

2.5.2递归函数

使用递归函数的优点是逻辑简单清晰,缺点是过深的调用会导致栈溢出。

针对尾递归优化的语言可以通过尾递归防止栈溢出。

2.5.3匿名函数

# 匿名函数(lambda)
add = lambda x, y: x + y #lambda后接参数,冒号后面接方法
print(add(5, 3))

2.6高级特性

2.6.1切片(几到几)

L[0:3]表示,从索引0开始取,直到索引3为止,但不包括索引3。即索引012,正好是3个元素。还可以省略0,即L[:3]。

倒向切片:L[-3:] 表示取后面三个元素,由于倒数第一个元素的索引是-1,所以L[-3:-1]即取索引为-3.-2的元素。

L[:10:2] # 前10个数,每两个取一个
L[::5]   # 所有数,每5个取一个
L[:]   	 # 列出所有数
'abcde'[:3]  # 'abc' 对于字符串同样适用  
def trim(s):
	start = 0
	while start<len(s) and s[start].isspace():  # 找到非空白字符的索引
		start += 1
	end = len(s) -1
	while end >= start and s[end].isspace():
		end -= 1
	return s[start:end+1] # hello s[0:5],这里的end是索引

2.6.2迭代

通过collections.abc模块的Iterable类型判断一个对象是可迭代对象

from collections.abc import Iterable
isinstance(123, Iterable) # 整数是否可迭代
False

各种迭代

for key in dict # 遍历字典中的key
for value in d.values()	# 遍历字典中的value
for k,v in dict.item()  # 遍历字典中的key跟value
for ch in 'ABC': 		# 遍历字符串
 for i, value in enumerate(['A', 'B', 'C']):
 	print(i, value) 	# 可打印索引跟列表元素

小案例

def findMinAndMax(L):
    if not L:	# 判断是否为空列表
        return (None, None)
    
    min_val = max_val = L[0] # 设置第一个元素为最值
    for num in L[1:]:		 # 遍历接下来元素
        if num < min_val:
            min_val = num
        if num > max_val:
            max_val = num
    
    return (min_val, max_val)

2.6.3列表生成器

2.9模块

功能模块我们可以在里面写方法,接着我们在要使用的类中引入该类,

2.9.1sys模块

#!/usr/bin/env python3 
# -*- coding: utf-8 -*-
# 以上两行运行程序在linux,mac,unix上运行,并且编码为utf-8
' a test module '	# 模块注释

__author__ = 'Michael Liao'	# 作者

import sys 

def test():
    args = sys.argv 		# argv是sys中的一个属性,args则是一个列表,一般默认存储[hello,world]
    if len(args)==1:
        print('Hello, world!')
    elif len(args)==2:
        print('Hello, %s!' % args[1])
    else:
        print('Too many arguments!')

if __name__=='__main__':  # 编译时,会把特殊变量_name_变为_main_,并调用上述方法,故此我们可以在这里面添加一些测试代码
    test()

2.9.2安装第三方模块

通过python的包管理工具pip去安装各种第三方库

作用域

__xxx__ # 这样的变量是特殊变量
_xxx # 这样的函数或变量就是非公开的(private)

外部不需要引用的函数全部定义成private,只有外部需要引用的函数才定义为public。

2.10面向对象编程

2.10.1类和实例

类是创建实例的模板,而实例则是一个一个具体的对象,各个实例拥有的数据都互相独立,互不影响;创建实例是通过**类名+()**实现的;

如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__,在Python中,实例的变量名如果以__开头,就变成了一个私有变量(private)

变量名类似__xxx__的,也就是以双下划线开头,并且以双下划线结尾的,是特殊变量,特殊变量是可以直接访问的,不是private变量

不能直接访问__name是因为Python解释器对外把__name变量改成了_Student__name

eg: self._color = Color,
def get_score(self):
        return self.__score
def set_score(self, score):
        self.__score = score  # 通过get,set方法访问私有变量

image-20240831170824802

2.10.2获取对象信息

type(123)  # 直接获取类
isinstance('a', str) # 判断是否属于后面数据类型
hasattr(obj, 'y') # 有属性'y'吗?
False
>>> setattr(obj, 'y', 19) # 设置一个属性'y'
>>> hasattr(obj, 'y') # 有属性'y'吗?
True
>>> getattr(obj, 'y') # 获取属性'y'
19
>>> obj.y # 获取属性'y'
# 从文件流fp中读取图像,我们首先要判断该fp对象是否存在read方法,如果存在,则该对象是一个流,如果不存在,则无法读取。hasattr()就派上了用场。
def readImage(fp):
    if hasattr(fp, 'read'):
        return readData(fp)
    return None

2.12错误与异常处理d

2.12.1错误处理

概念:当我们认为某些代码可能会出错时,就可以用try来运行这段代码,如果执行出错,则后续代码不会继续执行,而是直接跳转至错误处理代码,即except语句块,执行完except后,如果有finally语句块,则执行finally语句块,至此,执行完毕。一般只需要在运行的程序处加上try except,他会层层往下调用,直至找到根源。

try: 
    print('try...')
    r = 10 / int('2')
    print('result:', r)
except ValueError as e: # 错误类型
    print('ValueError:', e)  # pass则表直接退出程序
except ZeroDivisionError as e:	# 如果其是上述子类,则该异常永远捕获不到,因被截胡
    print('ZeroDivisionError:', e)
else:		# 如果没有被上述异常捕获就会执行eles语句
    print('no error!')
finally:	# 这句是一定执行的,但可以没有
    print('finally...')
print('END')

记录错误

Python内置的logging模块,可以把错误堆栈打印出来,然后分析错误原因,同时,让程序继续执行下去

raise语句如果不带参数,就会把当前错误原样抛出。由于当前函数不知道应该怎么处理该错误,所以,最恰当的方式是继续往上抛,让顶层调用者去处理。

2.12.2调试

断言:python -O err.py,关闭后,你可以把所有的assert语句当成pass来看。(这里的 O 是大写字母)

assert condition, "error_message"
  • condition 是你希望测试的条件。它应该是一个布尔表达式(即 TrueFalse)。
  • "error_message" 是一个可选的错误消息,当 conditionFalse 时,将会显示这个消息。
def foo(s):
    n = int(s)
    assert n != 0, 'n is zero!' 	# 断言n!=0为true,如果fasle则会报assertError:n is zero!
    return 10 / n

logging

logging的好处,它允许你指定记录信息的级别,有debuginfowarningerror等几个级别,当我们指定level=INFO时,logging.debug就不起作用了。

另一个好处是通过简单的配置,一条语句可以同时输出到不同的地方,比如console和日志文件

import logging
logging.basicConfig(level=logging.INFO) # 设置级别
s = '0'
n = int(s)
logging.info('n = %d' % n)
print(10 / n)

pdb(启动Python的调试器pdb):让程序以单步方式运行,可以随时查看运行状态

pdb.set_trace() # 运行到这里会自动暂停
p n # 查看某个变量的值
c # 继续执行下一步

2.12.3单元测试

2.13IO编程d

image-20240831180341366

2.16快速回顾

# 列表
fruits = ["apple", "banana", "cherry"]
fruits.append("orange")  # 添加元素
print(fruits)

# 元组
point = (10, 20)
print(point)

# 集合
numbers = {1, 2, 3, 4}
numbers.add(5)  # 添加元素
print(numbers)

# 字典,直接 对象[key] 就可以获得value
person = {"name": "Alice", "age": 30}
print(person["name"])

# 文件读写,content = file.read() 
with open(filepath,'rb') as f:  # r是read,w是write,b是二进制形式
    json.dump(data,f,indent = 4) #将data以JSON格式写入到 file 对象中,并且使用 4 个空格缩进来美化格式
    content = json.load(file) #加载文件中的json数据,并且转化成python对象
      

Selenium

  • 9/12

5.1原理与安装

原理:

  1. 自动化程序调用Selenium 客户端库函数(比如点击按钮元素)
  2. 客户端库会发送Selenium 命令 给浏览器的驱动程序
  3. 浏览器驱动程序接收到命令后 ,驱动浏览器去执行命令
  4. 浏览器驱动程序获取命令执行的结果,返回给我们自动化程序
  5. 自动化程序对返回结果进行处理

总结:python------>selenium----->浏览器的驱动------>浏览器

安装浏览器驱动及pip安装客户端库

浏览器驱动根据浏览器的不同进行安装

pip安装客户端库只需要在命令行中输入pip install selenium

5.2选择元素

选择元素时需要选择到最深层的,如输入就是input,点击就是button

from selenium.webdriver.common.by import By
# 初始化代码 ....
# wd 是webdriver wd.find_element(By.XPATH,'') 获得的是webElement
wd.find_element(By.ID, 'username').send_keys('byhy')
wd.find_elements(By.CLASS_NAME, 'password').send_keys('sdfsdf')
wd.find_element(By.TAG_NAME, 'div').send_keys('sdfsdf')
wd.find_element(By.CSS_SELECTOR,'button[type=submit]').click()

使用 find_elements 选择的是符合条件的 所有 元素, 如果没有符合条件的元素, 返回空列表

使用 find_element 选择的是符合条件的 第一个 元素, 如果没有符合条件的元素, 抛出 NoSuchElementException 异常

image-20240908153236208

5.3等待元素

5.3.1隐式等待

隐式等待也叫全局等待,后续所有find_element都会遵循这个

隐式等待是指 WebDriver 在查找元素时,如果元素没有立即出现,则会等待最多指定的时间来查找元素

wd.implictly_wait(0)  # 暂时取消,先取到页面元素
wd.implicitly_wait(10) # 如果找不到元素, 每隔 半秒钟 再去界面上查看一次, 直到找到该元素, 或者 过了10秒 最大时长。

5.3.2强制等待

time.sleep(1)
webdriverwait(driver,10).untile(EC.element_to_be_clickable((By.ID,'') # 最多等待十秒除非

5.4操作元素

element.clear()		# 清除输入框中内容
element.send_keys('dd')	#输入框中输入内容
element.text		# 获取元素 展示在界面上的 文本内容
element.get_attribute('value') # 获取输入框中内容,value替换为innerText 和 textContent 的区别是,前者只显示元素可见文本内容,后者显示所有内容(包括display属性为none的部分)
element.get_attribute("class")# 获取元素的属性值
element.get_attribute('outerHTML')# 整个元素对应的HTML文本内容,内部的HTML文本内容则innerHTML
wd.quit()			# wd是webdrive对象,关闭浏览器窗口

from selenium.webdriver.common.action_chains import ActionChains
ac = ActionChains(driver) # 获取鼠标对象
# 鼠标移动到元素上
ac.move_to_element(
    driver.find_element(By.CSS_SELECTOR, '[name="tj_briicon"]')
).perform() # 执行上面的移动动作

5.5css表达式

直接子元素是直接包含在标签里面(只有一层),后代元素就是在标签里面。

在浏览器中的element中按住ctrl+f可以通过.class属性来验证选中的元素。

css 选择器支持通过任何属性来选择元素,语法是用一个方括号 []

a[href*="miitbeian"]  	# 选择a节点,里面的href属性包含了 miitbeian 字符串
a[href^="http"] 		#选择a节点,里面的href属性以 http 开头
a[[href$='.com']		#选择a节点,里面的href属性以 gov.cn 结尾
div[class=misc][ctype=gun] 	#选择标签有多个属性的

5.5.1组选择

elements = wd.find_elements(By.CSS_SELECTOR, 'div,#BYHY') #选择div标签中id为BYHY
wd.find_element(BY.CSS_SELECTOR,'.animal','plant') # 选择所有class 为 plant 和 class 为 animal 的元素

5.5.2按次序选择子节点

span:nth-child(2) 	# span标签下的第二个节点
p:nth-last-child(2) # p标签下的倒数第二个节点
span:nth-of-type(1) # 按类型
p:nth-last-of-type(2) # 按类型倒数
p:nth-child(even) 	# 父元素的偶数结点
p:nth-of-type(odd) 	# 父元素的奇数结点

5.6frame窗口切换

iframe 元素非常的特殊, 在html语法中,frame 元素 或者iframe元素的内部 会包含一个 被嵌入的 另一份html文档。

在我们使用selenium打开一个网页是, 我们的操作范围 缺省是当前的 html , 并不包含被嵌入的html文档里面的内容。

切换元素选择范围

wd.switch_to.alert # 切换到弹出框
wd.switch_to.frame(wd.find_element(By.TAG_NAME, "iframe")) # 切换到该层窗口	
wd.switch_to.default_content() # 切换为默认html标签中	

切换窗口

mainWindow = wd.current_window_handle # mainWindow变量保存当前窗口的句柄
wd.switch_to.window(mainWindow) # 切回老窗口

for handle in wd.window_handles: # 遍历所有已经打开的窗口
    # 先切换到该窗口
    wd.switch_to.window(handle)
    # 得到该窗口的标题栏字符串,判断是不是我们要操作的那个窗口
    if 'Bing' in wd.title:
        # 如果是,那么这时候WebDriver对象就是对应的该该窗口,正好,跳出循环,
        break

5.7选择框

select_by_value('')
select_by_index(0)
select_by_visible_text('') # 根据选项的 可见文本 ,选择元素。
deselect_by_value() # 根据选项的value属性值, 去除 选中元素,value可替换为index...
deselect_all # 去除选中的所有元素

多选

# 导入Select类
from selenium.webdriver.support.ui import Select
# 创建Select对象
select = Select(wd.find_element(By.ID, "ss_single"))
# 清除可选框
select.delect_all();
# 通过 Select 对象选中小雷老师
select.select_by_visible_text("小雷老师") 
select.select_by_value("小红老师") 

5.8实战技巧

from selenium import webdriver
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()
driver.implicitly_wait(5)

driver.get('https://www.baidu.com/')
from selenium.webdriver.common.action_chains import ActionChains
ac = ActionChains(driver) # 获取鼠标对象

# 鼠标移动到 元素上
ac.move_to_element(
    driver.find_element(By.CSS_SELECTOR, '[name="tj_briicon"]')
).perform() # 执行上面的移动动作

调整元素到可见区

driver.execute_script("arguments[0].scrollIntoView({block:'center',inline:'center'})", job) 
# js对象的 scrollIntoView 方法,就是让元素滚动到可见部分
#block:'center' 指定垂直方向居中 inline:'center' 指定水平方向居中

冻结窗面(在控制台输入后,过五秒会进入debugger模式,这五秒内可以把鼠标放在会消失的元素上)

setTimeout(function(){debugger}, 5000) 

5.8.0案例

# 让页面最大化的方法
driver.maximize_window()
# 获取页面的大小 	{'width': 1552, 'height': 840}
print(driver.get_window_size())
# 获取当前网站路径 	 https://www.bilibili.com/	
print(driver.current_url) 
# 获取所有窗口代数,并存储在列表中
print(driver.window_handles)
print(driver.current_window_handle) # 当前窗口
driver.get_screenshot_as_file('1.png') # 保存当前图片
time.sleep(3)

# 先定位到上传文件的 input 元素
ele = wd.find_element(By.CSS_SELECTOR, 'input[type=file]')
# 再调用 WebElement 对象的 send_keys 方法(可多次)
ele.send_keys(r'h:\g02.png')

入门案例

image-20240904162502185

import time
from selenium import webdriver
from selenium.webdriver.common.by import By
driver = webdriver.Edge()        # 启动浏览器
driver.get("https://baidu.com")  # 进入测试页面
A = driver.find_element(By.XPATH,'//*[@id="kw"]') # 输入框元素
B = driver.find_element(By.XPATH,'//*[@id="su"]') # 按钮元素
A.send_keys("北京时间") # 输入内容,还可以导入Keys包,里面封装了ctrl键
B.click() # 点击按钮
time.sleep(1) # 强制等待网页渲染
html = driver.page_source # 获取网页源码
count = html.count('damag') # 统计次数
assert count > 1,f"断言失败:{count=}"
driver.get_screenshot_as_file('page.png') # 截屏
driver.close() # 关闭浏览器

测试页面(web自动化框架封装)

Pom(page object model)+Pytest

对页面进行类的封装,页面中有三个元素,一个登录操作,故class类中有三个属性及一个行为

属性需要我们去先选中元素,然后对其实例化,实例化是为了后面能更好的使用

pages.py

class LoginPage(): 
    ipt_username = LazyElement(BY.CssSelector, '#TANGRAM__PSP_11__userName')
    ipt_password = LazyElement(By.XPath,'//*[@id="TANGRAM__PSP_11__password"]')
    # 开始时不检查是否存在该元素,而且该元素时有参数变化,所以在可以在控制台术定位该元素$x('//p[@id='']')
    btn_login = LazyElement(By.XPATH,'//*[@id="TANGRAM__PSP_11__submit"]',check_on_init=False) 

    def login(self,username,password):
        self.sendkeys(self.ipt_username,username) # 操作封装在一起,这里是以ipt_username.sendkeys(username),即在sendkeys中先写调用对象,后接传递内容信息
        self.sendkeys(self.ipt_password,password)
        self.click(self.btn_login)

test_web

from selenium import webdriver
from selenium.webdriver.common.by import By
from pages import LoginPage   # 从文件中引入类名(class)
dirver = webdriver.Edge()
dirver.get("http://baidu.com")
dirver.find_element(By.XPATH, '//*[@id="s-top-loginbtn"]').click()
def test_login(): # test_开头表测试方法
    page = LoginPage(dirver) # 实例化登录对象
    page.login("damage","123456") # 对象调用方法行为
    input("please enter")

5.8.1对话框

from selenium import webdriver
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()
driver.implicitly_wait(5)
driver.get('https://cdn2.byhy.net/files/selenium/test4.html')


# --- alert ---
driver.find_element(By.ID, 'b1').click()

# 打印 弹出框 提示信息
print(driver.switch_to.alert.text) 

# 点击 OK 按钮 ,如果是取消就是accept改为dismiss()
driver.switch_to.alert.accept()

Prompt 弹出框:需要用户输入一些信息,提交上去

from selenium import webdriver
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()
driver.implicitly_wait(5)
driver.get('https://cdn2.byhy.net/files/selenium/test4.html')
# --- prompt ---
driver.find_element(By.ID, 'b3').click() # 点击后弹出

# 获取到弹出的alert 对象
alert = driver.switch_to.alert 

# 打印 弹出框 提示信息
print(alert.text)

# 输入信息,并且点击 OK 按钮 提交
alert.send_keys('web自动化 - selenium')
alert.accept()

5.9XPATH

绝对路径:从根结点,按照/分割找到目标元素

相对路径:// div 表所有标签名为 div 的元素

选择 所有的 div 元素里面的 直接子节点 p , xpath,就应该这样写了 //div/p

如果使用CSS选择器,则为 div > p

通配符 *

eg: //div/* 表div标签下的所有直接子节点

5.9.1根据属性选择

标签名[@属性名=‘属性值’]

// *[@id = 'west'] # 根据id属性选择
// p[@class="capital huge-city"] p标签中的类

xpath也有组选择, 是用 竖线 隔开多个表达式

要选所有的 class 为 single_choice 和 class 为 multi_choice 的元素,可以使用

 //*[@class='single_choice'] | //*[@class='multi_choice']

等同于CSS选择器

.single_choice , .multi_choice

要选择 class 为 single_choice 的元素的 所有 前面的兄弟节点,这样写

//*[@class='single_choice']/preceding-sibling::*

要选择后续节点中的div节点, 就应该这样写 //*[@class='single_choice']/following-sibling::div

要在某个元素内部使用xpath选择元素, 需要 在xpath表达式最前面加个点

elements = china.find_elements(By.XPATH, ‘.//p’)

5.10快速回顾

创建页面对象类,封装页面元素和操作

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.keys import Keys

# 初始化 WebDriver(这里使用 Chrome 浏览器)
driver = webdriver.Chrome()
# css selector可以通过 .引用类,#引用id
# 直接子元素(直接包含)在引用时用箭头如 e1 > e2 表引用 e2
# 后代子元素(间接包含)在引用时用空格如 e1  e2 表引用 e2
# 根据属性选择元素,[]这个在 css表达式中可以引用任何一种属性
element = wd.find_element(By.CSS_SELECTOR, '[href="http://www.miitbeian.gov.cn"]')
# p:nth-chile(3) 子元素的第3个
# span:nth-last-of-type(2) 倒数父元素的第二个类型

try:
    # 打开网页
    driver.get('https://example.com')
    
    # 查找元素(使用 ID 定位)
    element = driver.find_element(By.ID, 'element_id')
    element = driver.clear()    #清除 文本框中内容
    
    # 输入文本
    element.send_keys('text')
    
    # 点击元素
    element.click()
    
    # 获取元素的文本
    text = element.text
    print(f"Element text: {text}")
    
    # 获取元素的属性值
    attribute = element.get_attribute('attribute_name')
    print(f"Element attribute: {attribute}")
	#获取整个元素对应的 html
	outer = wd.find_element(By.CSS_SELECTOR,'div[id="inner11"] > span').get_attribute('outerHTML')
	print(outer) # 输出<span>内层11</span>
	#获取整个元素对应的 内部html
	element.get_attribute('innerHTML')
	#input输入框的元素
	element.get_attribute('value')
	#显示所有内容(包括display属性为none的部分)
	element.get_attribute('textContent')
    
    # 等待元素出现(最长等待 10 秒)
    wait = WebDriverWait(driver, 10)
    element = wait.until(EC.presence_of_element_located((By.ID, 'element_id')))
    
    # 执行 JavaScript 脚本(例如:滚动到底部)
    driver.execute_script('window.scrollTo(0, document.body.scrollHeight);')
    	
    # 文件上传
    upload_element = driver.find_element(By.ID, 'upload')
    upload_element.send_keys('/path/to/file')
    
    # 处理弹窗
    alert = driver.switch_to.alert
    alert.accept()  # 确认弹窗
    
    # 选择框操作(选择下拉菜单中的选项)
    from selenium.webdriver.support.ui import Select
    select = Select(driver.find_element(By.ID, 'select_id'))
    select.select_by_visible_text('Option Text')
    
    # 截图
    driver.save_screenshot('screenshot.png')

    # 切换到新的窗口或标签页
    driver.switch_to.window(driver.window_handles[1])
    
    # 关闭当前窗口
    driver.close()
    
except Exception as e:
    print(f"An error occurred: {e}")
finally:
    # 关闭浏览器并释放资源
    driver.quit()

pytest

打卡次数

  • 9/12

安装pytest与pytest-html

pip install pytest
pip install pytest-html

注意事项

导入pytest,自动执行__init__.py文件
执行pytest,自动执行__main__.py文件
pytest插件中有hock,fixture,类和函数	

入门坑点

  • self 参数是实例方法中的参数,用于表示类的实例。在fixture中不应该定义self,但是如果在类中定义fixture函数,他又会提示你应该添加self参数。一般写测试.py不要随便定义类,如果不定义类,则方法不用写self

1.基本用法

1.1测试用例代码

  • 文件必须以必须以 test_ 开头,或者以 _test 结尾,例如以下的:test_错误登录.py
  • 类名必须以 Test 为前缀的
  • 用例对应的必须以 test 为前缀的方法或函数
def test_C001003(self): # 函数
        print('\n用例C001001')
        assert 1 == 2

class Test_错误密码:

    def test_C001001(self): # 类的内部就是方法
        print('\n用例C001002')
        assert 1 == 1

    def test_C001003(self):
        print('\n用例C001003')
        assert 3 == 2

1.2运行测试

运行测试时方法:

  • 在终端运行pytest cases (cases表目标测试文件目录)
  • 在cmd中进入项目根目录后输入pytest cases
python pytest cases - sv # s表输出测试用例中的打印语句,而v则是更详细的包含方法名称
pytest --maxfail=1 在第一个失败后停止测试。
pyteset -m <marker> # 运行指定标记

image-20240905190908833

1.3allure测试报告

python -m pytest cases --html=report.html --self-contained-html #会生成一个html文件,里面记录了详细信息

allure-pytest下载

命令行:pip install allure-pytest

下载后:pip show allure-pytest

allure环境下载与配置

需要有jdk的前提下,在国内一个allure网站下载版本。之后把bin目录添加到系统变量上去。

2.初始化清除

不是很重要,是unnitest方法的清除。

  • 模块级别:模块级别 的初始化、清除 分别 在整个模块的测试用例 执行前后执行,并且 只会执行1次
  • 类级别:在执行前后执行,但是第二个类时直接连在清除类之后,而不是再次初始化类
  • 方法级别:在方法执行前后都执行,但只限于该方法初始化方法有写在对应class类上
  • 目录级别:在对应测试文件的目录下新建一个conftest.py文件
import pytest 

@pytest.fixture(scope='package',autouse=True)
def st_emptyEnv():
    print(f'\n#### 初始化-目录甲')
    yield

    print(f'\n#### 清除-目录甲')
def setup_module():
    print('\n *** 初始化-模块 ***')
def teardown_module():
    print('\n ***   清除-模块 ***')

class Test:
    @classmethod  
    def setup_class(cls):
        print('\n === 初始化-类 ===')
    @classmethod
    def teardown_class(cls):
        print('\n === 清除 - 类 ===')
    def setup_method(self):
        print('\n --- 初始化-方法  ---')
    def teardown_method(self):
        print('\n --- 清除  -方法 ---')
    def test_b(self):  # 类的内部就是方法
        print('\n用例C001002')
        assert 1 == 1
    def test_c(self):
        print('\n用例C001003')
        assert 3 == 2
class Test2:

    def test_2(self):
        print('\ndamage')
        assert 1 == 1
    def test_3(self):  # 类的内部就是方法
        print('\n用例3')
        assert 1 == 1

3.挑选用例执行

平时写的pytest前面省略了python -m ,-m 这种执行模块的方式运行python, 会自动把当前工作目录作为模块搜索路径,避免Python解释器 搜索不到 库文件的问题

python -m pytest cases\登录\test_错误登录.py 	#指定模块
pytest cases1  cases2\登录	#指定多个目录
pytest cases\登录\test_错误登录.py::Test_错误密码::test_01 # 指定模块中类时,在模块后面直接加::,指定类中方法也是::
pytest -k "not 错误 or not 01" -s #指定名字,名字间有并列关系
pytest cases -m webtest -s #根据标签mark指定,其直接在对应类,方法上加上@pytest.mark.XXX
例如
@pytest.mark.网页测试
class Test_错误密码2:
    def test_C001021(self):
        print('\n用例C001021')
        assert 1 == 1
        
# 定义一个全局变量 pytestmark 为 整个模块文件 设定标签
import pytest
pytestmark = pytest.mark.网页测试

4.案例实战

封装登录逻辑

from selenium import webdriver # 每个项目都得重新pip install selenium及pytest
from selenium.webdriver.common.by import By
import time
def loginAndCheck(username,password):
    driver = webdriver.Edge()
    driver.get('http://localhost/mgr/sign.html')
    driver.implicitly_wait(3) # 等待元素出现的最长时间
    if username is not None:
        driver.find_element(By.ID,"username").send_keys(username)
    if password is not None:
        driver.find_element(By.ID,"password").send_keys(password)	    		      			    driver.find_element(By.XPATH,'/html/body/div/div[2]/div[1]/div[3]/div/button').click()
    time.sleep(2) # 确保弹出消息有足够时间加载
    alertText = driver.switch_to.alert.text # 驱动转到alert,并获得弹出信息
    print(alertText)
    driver.quit() #退出窗口
    return alertText # 返回信息

测试用例调用模块方法

from lib.login import loginAndCheck
class Test:
    def test_UI_0001(self):  # 类的内部就是方法
        print('\n用例UI-0001')
        alerttext = loginAndCheck(None,'88888888')
        assert alerttext == "请输入用户名"

    def test_UI_0002(self):  # 类的内部就是方法
        print('\n用例UI-0002')
        alerttext = loginAndCheck('poter',None)
        assert alerttext == "请输入密码"        

数据驱动

可优化上述步骤:把测试数据从测试用例代码中 分离 开来,以后增加新的测试用例,只需要修改数据。

class Test_错误登录:
    @pytest.mark.parametrize('username, password, expectedalert', [
        (None, '88888888', '请输入用户名'),
        ('byhy', None, '请输入密码'),
        ('byh', '88888888', '登录失败 : 用户名或者密码错误'),
        ('byhy', '8888888', '登录失败 : 用户名或者密码错误'),
        ('byhy', '888888888', '登录失败 : 用户名或者密码错误'),
    ]
                             )
    def test_UI_0001_0005(self, username, password, expectedalert):
        alertText = loginAndCheck(username, password)
        assert alertText == expectedalert

直接新建一个json文件,然后再列表中放入一个对象,即[{‘username’:‘porter’},{}]

import json
# 读取json文件
def build_data(json_file):
	test_data = [] # 定义空列表
	with open(json_file,'r') as f: #打开json文件
		json_data = json.load(f)  #加载数据
		for case_data in json_data:
			# 转换数据格式[{},{}] => [(),()]
			username = case_data.get("username")
			status = case_data.get('status')
			message = case_data.get('message')
			test_data.append(username.staus,message) #存入空列表
	return test_data
	
@pytest.mark.parametrize('username, status, message',build_data(json_file='../login.json'))
def test_01_login(username,status,message):
	login_data = {'username': username,'status': status}

5.调试

配置

image-20240906105902146

测试

image-20240906110427765

6.fixture

6.1使用fixture

import pytest

# 定义一个fixture函数
@pytest.fixture
def createzhangSan():
    # 这里代码实现了使用API创建用户张三账号功能
    print('\n *** createzhangSan ***')
    zhangSan = {
        'username'   : 'zhangsan',
        'password'   : '111111',
        'invitecode' : 'abcdefg' # 这是系统创建用户产生的其它信息
    }
    return zhangSan


# 这里测试张三账号登录的功能
def test_A001001(createzhangSan):
    print('\n用例 A001001')
    print('\ninvitecode is', createzhangSan['invitecode'])

运行结果

若测试用例中有fixture函数,会先执行fixture函数, 然后把fixture函数返回的结果 作为参数的值,又叫依赖注入

如果在运行之后清除数据,则可把return zhangsan 修改为 yield zhangSan print('\n *** 执行清除张三账号的代码 ***')

修改之后会在测试用例执行完后,执行yield 后语句

6.2fixture参数

多个参数的情况下可以使用数据驱动,来创建多个参数(需要加上indirect=True条件,)

  1. indirect=True,表示这些参数应该传递给 createUser fixture 而不是直接传递给测试函数。
  2. request是一个字典
# fixture 函数用于创建用户,并通过参数化的方式为测试用例提供不同的用户数据。
@pytest.fixture
def createUser(request):
    # 这里代码实现了使用API创建用户账号功能
    print('\n *** createUser ***')
    user = {
        'username'   : request.param[0],
        'password'   : request.param[1],
        'invitecode' : 'abcdefg' # 这是系统创建用户产生的其它信息
    }
    return user
# 将测试用例参数化。
@pytest.mark.parametrize("createUser",
    [("zhangsan", "111111"),("lisi", "111")],
    indirect=True)  # 表示这些参数应该传递给 createUser fixture 而不是直接传递给测试函数。
# 测试用例
def test_A00100X(createUser): #检查到有fixture函数名一致的,先执行函数内容
    print('\nusername is', createUser['username'])
    print('测试行为')

image-20240906114437529

总结:

  • createUser fixture 用于创建用户信息,并作为参数传递给测试用例。
  • pytest.mark.parametrize 用于为测试用例提供多个用户数据。
  • 测试用例 test_A00100X 通过 createUser fixture 接受不同的用户数据,并输出相关信息。

6.3scope范围(清除元素)

scope = ‘function’ # class ,module,session(整个测试环节)

目录级别则是新建一个conftest.py 的文件

@pytest.fixture(scope='function',autouse = true)
def createzhangSan():
    print('\n ***  创建 ***')
    zhangSan = {
        'username'   : 'zhangsan',
        'password'   : '111111',
    }

    yield zhangSan

    print('\n ***  清除 ***')

7.Elife实战项目

7.1登录功能

登录.py

from lib.webUI_smp import smp
from selenium.webdriver.common.by import By
import pytest,time
class Test01:
    def test_SMP_login_001(self,clearAlert): 	# 正确用例测试
        smp.login('byhy','sdfsdf') # 直接调用
        nav = smp.driver.find_element(By.TAG_NAME, 'nav')  # 1.通过标签寻找nav
        assert nav != []
        # count = driver.page_source.count('nav') # 2.通过源码查找是否有对应字段
        # assert count > 1 , f'登录失败,{count=}'

    @pytest.fixture()
    def clearAlert(self):
        yield # 前是方法执行前,后是方法执行后
        try: # 异常处理:在系统出现问题,弹框没有出现时,也不会报错
            smp.driver.switch_to.alert.accept() # 点击
        except Exception as e:
            print(e)
	# '参数1,参数2' ,[(),()]
    @pytest.mark.parametrize('username,password,expectedalert',[     #列表里面放turple
        (None,'sdfsdf','请输入用户名'),
        ('byhy', None,'请输入密码'),
        ('byhy', 'sdfsdfa','登录失败: 用户名或者密码错误'),
        ('byhy', 'sdfsd','登录失败: 用户名或者密码错误'),
        ('byhys', 'sdfsdf','登录失败: 用户名不存在'),
        ('byh', 'sdfsdf','登录失败: 用户名不存在')
    ])
	# 异常用例测试
    def tests_002_007(self,username,password,expectedalert,clearAlert): #这里clearAlert参数与fixture方法一致,故会在方法执行后点击'确认'按钮
        smp.login(username,password)
        time.sleep(1) # 等待提示框出现
        alert = smp.driver.switch_to.alert.text
        assert alert == expectedalert

web_UI.py(放通用方法)

from selenium import webdriver
from selenium.webdriver.common.by import By
from cfg import *
import time
class SMP_UI:
    def __init__(self): # 魔法方法
        self.driver = webdriver.Edge() # 启动浏览器并设置隐式等待时间
        self.driver.implicitly_wait(5)
    def login(self,username,password):
        self.driver.get(SMP_URL_LOGIN) # 登录地址放在cfg配置类中
        time.sleep(1)
        if username is not None:
            self.driver.find_element(By.ID, 'username').send_keys(username)
        if password is not None:
            self.driver.find_element(By.ID, 'password').send_keys(password)
        time.sleep(1)
        self.driver.find_element(By.ID, 'loginBtn').click()
smp = SMP_UI() # 直接在类中实例化对象,后面直接引入该实例

7.2设备型号

webUI.py(自动化测试脚本)

def addDevice(self, devType, model, desc):
    try:
        time.sleep(2) # 确保页面按钮出现
        # 点击添加
        addBtn = smp.driver.find_element(By.CSS_SELECTOR, 'body > main > div.add-one-area > span')
        if addBtn.text == '添加':
            addBtn.click()
        # 选择框,创建Select对象
        select = Select(smp.driver.find_element(By.ID, "device-type"))
        select.select_by_visible_text(devType)
        # 让驱动最长等待10s,除非期望元素变成可点击元素
        ele = WebDriverWait(smp.driver, 10).until(EC.element_to_be_clickable((By.ID, "device-model")))
        ele.clear()  # 清除残留内容
        ele.send_keys(model)

        ele = smp.driver.find_element(By.XPATH, '//*[@id="device-model-desc"]')
        ele.clear()
        ele.send_keys(desc)
        time.sleep(2)
        smp.driver.find_element(By.XPATH, '/html/body/main/div[1]/div/div[4]/span').click()
    except Exception as e:
        print(e)

def get_first_device(self):
    time.sleep(1)  # 暂停1秒,确保页面完全加载或有足够时间等待异步操作完成
    self.driver.implicitly_wait(0)  # 关闭隐式等待,立即获取元素,不等待
    values = self.driver.find_elements(By.CSS_SELECTOR, '.field-value')  # 查找所有具有'class'为'field-value'的元素
    self.driver.implicitly_wait(10)  # 恢复隐式等待,设置等待时间为10秒,以便处理后续操作
    deviceModels = []  # 初始化空列表,用于存储设备信息
    for idx, value in enumerate(values):  # enumrate生成迭代元素的序号及值
        if (idx + 1) % 3 == 0:  # 每三个元素为一组
            deviceModels.append(
                [values[idx-2].text, values[idx-1].text, values[idx].text]  # 将当前元素及其前两个元素的文本值作为一组添加到列表中
            )
    return deviceModels  # 返回包含设备信息的列表

def del_first_item(self) -> bool: # 表返回的bool值
    self.driver.implicitly_wait(0)  # 临时禁用隐式等待以确保立即获取元素
    # 查找删除按钮,使用指定的 XPath
    delBtn = smp.driver.find_elements(By.XPATH, '/html/body/main/div[3]/div[1]/div[2]/span[1]')
    self.driver.implicitly_wait(5)  # 恢复隐式等待,设定为5秒
    if not delBtn:  # 如果没有找到删除按钮,则返回 False
        return False
    delBtn[0].click()  # 点击找到的第一个删除按钮
    # 处理弹出的确认对话框并接受
    smp.driver.switch_to.alert.accept()
    return True  # 返回 True,表示删除操作成功

设备.py,测试流程如下:

  1. 登录进去之后跳转到device_page(登录跳转到测试页面)方法执行之前初始化
  2. 点击添加按钮
  3. 输入信息并提交(添加过程)
  4. 验证页面第一行是否为刚输入的信息(断言)
  5. 验证之后,删除添加的数据(删除)方法执行之后初始化
from cfg import *
from lib.webUI_smp import smp
import pytest, time

@pytest.fixture(scope='module')
def inDevicePage():
    print("isDevicePage setup")
    smp.login('byhy', 'sdfsdf')
    smp.driver.get(SMP_URL_DEVICE_PAGE)  # 登录进去之后跳转到device_page
    yield
    print("isDevicePage teardown")
    return

@pytest.fixture(scope='function')
def delAddedDevice():
    print("删除添加的测试数据 setup")
    yield
    print("删除添加的测试数据 teardown")
    smp.del_first_item()  # 删除添加的数据,保证测试前后数据的一致

# 参数写成字符串形式,后面跟列表,列表里面是一个个turple()
@pytest.mark.parametrize('devType, model, desc',
                         [
                             ("汽车充电站", '10-20-40', '南京e生活'),("gaoshiqing", 'kkk', '上号'), ("存储柜", '李' * 100, 'colorful')
                         ])
# 数据驱动(参数之后,注意要登录及初始化清除)
def test_SMP_device_model_001_003(devType,model,desc,inDevicePage, delAddedDevice):
    smp.addDevice(devType,model,desc)
    devices = smp.get_first_device()  # 获取添加的元素列表
    assert devices[0] == [devType,model,desc]  # 断言

def test_SMP_device_model_001(inDevicePage, delAddedDevice):
    smp.addDevice("汽车充电站", '10-20-40', '南京e生活')  # 添加
    devices = smp.get_first_device()  # 获取添加的元素列表
    assert devices[0] == ['汽车充电站', '10-20-40', '南京e生活']  # 断言

def test_SMP_device_model_002(inDevicePage, delAddedDevice):
    smp.addDevice("存储柜", '李' * 100, 'colorful')  # 添加
    devices = smp.get_first_device()  # 获取添加的元素列表
    assert devices[0] == ["存储柜", '李' * 100, 'colorful']  # 断言

7.3业务规则

# 选择框,创建Select对象
    select = Select(smp.driver.find_element(By.XPATH, '//*[@id="rule_type_id"]'))
    select.select_by_visible_text(ruleType)
    if ruleType != '后付费-上报业务量':
        ele = smp.driver.find_element(By.XPATH,'/html/body/main/div[1]/div/div[3]/input')
        ele.clear() # 清除上次的
        if mimFee: #如果存在的话发送
            ele.send_keys(mimFee)

    for one in feeRate: #one 即feeRate中的元素。若feeRate是[[a,b]],则one为[a,b]
    self.driver.find_element(By.CSS_SELECTOR,'.fee-rate-list button').click() # 点击添加费率
    entry = self.driver.find_element(By.CSS_SELECTOR, 'div.fee-rate:nth-last-child(2)')# 获取新增的行输入对象,由于是可变的
    # 业务码
    entry.find_element(By.CSS_SELECTOR, 'input:nth-of-type(1)').send_keys(one[0]) # 新对象中找元素输入框,直接标签加第几个元素        

7.4allure报告

1.在项目根目录下新建pytest.ini配置文件

[pytest]
addopts = -s --alluredir report
testpaths=./cases/登录  #测试用例目录
python_files=test*.py
python_classes = Test* # 如果没有类,可以省略
python_function=test*

2.在终端直接输入pytest运行,如果报错module not found ,原因是python跟pytest冲突

可以直接再新建一个配置文件conftest.py

import os
import sys
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))

3.在终端输入allure serve report生成测试报告html

8.高级用法

主要是mark,fixture,hook。

[pytest] markers = api,ui,ut # 注册标记
@pytest.mark.api #使用
@pytest.fixture() #用户测试环境的构建与销毁
hook #改变pytest原有的运行方式,运行我们进入核心

yaml文件

test_name
steps:
  - a: 2
    b: 3
    c: 5 #a+b

9.pytest框架封装

框架封装,

9.1日志文件

在pytest.ini中添加以下代码

log_file = ./pyteset.log
log_file_level = info 
log_file_format = 

10.快速回顾

  1. 基本用法
    • 使用 def test_function() 定义测试函数。
    • 运行测试:pytestpytest <filename>.py
  2. 断言
    • 使用 assert 语句验证条件,例如 assert a == b
  3. 测试夹具 (Fixtures)
    • 使用 @pytest.fixture 装饰器定义夹具,提供测试所需的数据或状态。
    • 通过函数参数传递夹具,例如 def test_func(fixture_name)
  4. 标记 (Markers)
    • 使用 @pytest.mark.<marker> 标记测试,例如 @pytest.mark.slow
    • 运行特定标记的测试:pytest -m <marker>
  5. 参数化
    • 使用 @pytest.mark.parametrize 传递多个参数进行测试,例如 @pytest.mark.parametrize("input,expected", [(1, 2), (3, 4)])
  6. 测试套件 (Test Suites)
    • 通过创建测试类并将测试方法添加到类中组织测试。
  7. 跳过和预期失败
    • 使用 @pytest.mark.skip 跳过测试。
    • 使用 @pytest.mark.xfail 标记预期会失败的测试。
  8. 命令行选项
    • pytest -v 以详细模式运行测试。
    • pytest --maxfail=1 在第一个失败后停止测试。
    • pytest --tb=short 自定义跟踪回溯的格式。
  9. 生成测试报告
    • 使用 pytest --html=report.html 生成 HTML 格式的测试报告。
  10. 测试用例的组织
    • 将测试函数组织在不同的模块或类中,以保持测试的结构清晰。

接口测试(hytest三集)

接口文档首先要执行业务接口,再接着测试单个接口(后端慢写好的情况下,整体已完成就先测业务)

测试业务接口:

  • 根据业务流程图梳理业务路径
  • 设计测试用例覆盖每一条业务路径
  • 使用软件工具,直接通过消息接口 对 被测系统 进行消息收发
  • 验证被测系统行为是否正确。

接口测试步骤:

  1. 理解 API 文档: 阅读 API 规格文档,理解接口的输入、输出、请求方法(GET, POST, PUT, DELETE等)及其参数。
  2. 构建测试用例: 编写测试用例以验证接口功能、边界条件、错误处理等。
  3. 设置测试环境: 配置测试环境,包括 API 服务器、数据库等。
  4. 发送请求: 使用工具或编程语言发送请求到 API。
  5. 验证响应: 检查 API 返回的响应是否符合预期,包括状态码、响应时间、返回数据等。
  6. 处理异常: 捕捉并处理接口调用中的异常情况,如网络问题、API 错误等。

1.HTTP

HTTP 协议 全称是 超文本传输协议(网页、视频、图片),通讯双方 分为 客户端服务端

请求信息:请求行,请求头,请求体

Post /mgr/login.html HTTP/1.1    # 添加资源 到 服务器地址 /api/medicine , 使用的 协议 是 HTTP/1.1
Host: www.baiyueheiyu.com  		# 请求头
User-Agent: Mozilla/6.0 (compatible; MSIE5.01; Windows NT)
Content-Type: application/x-www-form-urlencoded
Content-Length: 51
Accept-Language: zh-cn
Accept-Encoding: gzip, deflate

name=qingmeisu&sn=099877883837&desc=qingmeisuyaopin # 空行之后就是请求体,一般是json,xml,www-form

响应信息:状态行,响应头,响应体

状态码:2xx :成功,3xx:重定向,4xx:前台客户端问题,5xx:后台服务端问题

HTTP/1.1 200 OK # 状态行由 协议版本+状态码+描述信息
Date: Thu, 19 Sep 2019 08:08:27 GMT # 响应头包含时间
Server: WSGIServer/0.2 CPython/3.7.3
Content-Type: application/json
Content-Length: 37
X-Frame-Options: SAMEORIGIN
Vary: Cookie

{"ret": 0, "retlist": [], "total": 0} #响应体跟请求体差不多

2.request库

pip install requests # 安装
pip show requests # 验证
requests.get/put(url,params=None,data=None,json=None,headers=None,files = None)

2.1fiddler

简介:fiddler 是 代理式 抓包。设置自己作为系统代理,监听在 8888 端口上。

HTTP客户端需要设置 fiddler 作为代理, 把HTTP请求消息 发送给 fiddler, fiddler再转发HTTP消息给服务端。

requests程序抓包

import requests  
proxies = { # 启用代理
  'http': 'http://127.0.0.1:8888',
  'https': 'http://127.0.0.1:8888',
}
response = requests.get('http://mirrors.sohu.com/', proxies=proxies)
print(response.text)

image-20240909145113899

手机抓包

首先在fidder的option中connections中allow remote computers to connect.接着再保证手机跟电脑同个网络下,手机连接wifi开启代理,输入电脑ip地址与fidder代理服务器端口即可

2.2构建http请求

url参数(两种方式)params

response = requests.get('https://www.baidu.com/s?id = 1')

urlpara = {
    'wd':'iphone&ipad',
    'rsv_spt':'1'
}

response = requests.get('https://www.baidu.com/s',params=urlpara)

请求头headers

headers = {
    'user-agent': 'my-app/0.0.1', 
    'auth-type': 'jwt-token'
}

r = requests.post("http://httpbin.org/post", headers=headers)

请求体:data = payload

xml格式消息体

payload = ''' # 直接用‘’‘把xml包起来
<?xml version="1.0" encoding="UTF-8"?>
<WorkReport>
    <Overall>良好</Overall>
    <Progress>30%</Progress>
    <Problems>暂无</Problems>
</WorkReport>
'''

r = requests.post("http://httpbin.org/post",
                  data=payload.encode('utf8'))
print(r.text)

urlencoded 格式消息体

payload = {'key1': 'value1', 'key2': 'value2'}
r = requests.post("http://httpbin.org/post", data=payload)
print(r.text)

json格式消息体 data=json.dumps(payload)

import requests,json
payload = {
    "Progress":"30%",
    "Problems":[
        {
            "No" : 1,
            "desc": "问题1...."
        },
        {
            "No" : 2,
            "desc": "问题2...."
        },
    ]
}
r = requests.post("http://httpbin.org/post", data=json.dumps(payload))
obj = response.json(r) # 即可将json形式转为对象

2.3检查http响应

print(response.status_code) # 状态码
print(response.headers['Content-Type']) # 存储的是字典,可以直接获取响应头的值response.encoding='utf8'
print(response.text) 

2.4session

用户使用客户端登录,服务端进行验证(比如验证用户名、密码)。验证通过后,服务端系统就会为这次登录创建一个session对话。session就是一个数据结构,保存该客户这次登录操作相关信息。

服务端是通过 HTTP的响应头 Set-Cookie 把产生的 sessionid 告诉客户端的。

客户端的后续请求,是通过 HTTP的请求头 Cookie 告诉服务端它所持有的sessionid的。

2.5接口手动测试

import requests
# 打印HTTP响应消息的函数
def printResponse(response):
    print('\n\n-------- HTTP response * begin -------')
    print(response.status_code)

    for k, v in response.headers.items():
        print(f'{k}: {v}')

    print('')

    print(response.content.decode('utf8'))
    print('-------- HTTP response * end -------\n\n')

# 创建 Session 对象
s = requests.Session()

# 通过 Session 对象 发送请求
response = s.post("http://127.0.0.1/api/mgr/signin",
       data={
           'username': 'byhy',
           'password': '88888888'
       })

# 通过 Session 对象 发送请求
response = s.get("http://127.0.0.1/api/mgr/customers",
      params={
          'action'    :  'list_customer',
          'pagesize'  :  10,
          'pagenum'   :  1,
          'keywords'  :  '',
      })

printResponse(response)

我们可以把上述用到的方法封装到一个webapi.py库中,使用的时候直接调用,例子如下

def mgr_login(self,username,password):
        self.s = requests.Session() # 获取session对象
        response = self.s.post("http://127.0.0.1/api/mgr/signin",
                                 data={
                                     'username': username,
                                     'password': password
                                 }
                                 ) #网址后加json格式消息体

        self._printResponse(response)
        return response

新增课程案例

1.接口类封装(api)

image-20240910104552450

2、测试脚本(script)

class TestCourse # 测试类
def setup(self): # 前置处理
	self.course_api = CourseAPi() #实例化对象
def test_add_course(self):
	add_data = {'name':'success','price':100}
	response = self.course_api.add_course(test_data=add_data,token = TestCourse.token)

3.配置类(config.py)

import os
BASE_URL = 'http://www.' # 设置项目环境域名,使用时导入config.BASE_URL + '' 进行字符串拼接
BASE_PATH = os.path.dirname(__file__) # 获取根项目路径

3.postman

3.1自动关联

假如其他接口调用时需要用到某个值,而该值又得其他接口生成。那么可以将两者关联起来。

  • 首先要创建一个environment,并且命名,以便项目可以找到该环境;
  • 接着在值生成的接口中的tests中添加以下代码获取值
var jsonData = pm.response.json() #获取响应数据
pm.environment.set("token",jsonData.uuid) #放进环境变量中,之后便可以在所用处调用

image-20240909175944676

  • 接着在调用处直接{{}},调用{{uuid}}

3.2批量执行

只需要将接口放在一个文件夹下面,最后运行测试集就可以了。

3.3断言

image-20240909201735540

3.4参数化

image-20240909203340798

快速回顾


app测试

1.原理

appium(客户端库)->appium server -> device

Appium Server 会在手机上 安装一个 自动化代理程序, 代理程序会等待自动化指令,并且执行自动化指令

执行过程

  • 自动化程序 调用客户端库相应的函数, 发送 点击元素 的指令(封装在HTTP消息里)给 Appium Server
  • Appium Server 再转发这个指令给 手机上的自动化代理
  • 手机上的自动化代理 接收到 指令后,调用手机平台的自动化库,执行点击操作,返回点击成功的结果给 Appium Server
  • Appium Server 转发给 自动化程序
  • 自动化程序了解到本操作成功后,继续后面的自动化流程

2.环境

百度网盘链接:https://pan.baidu.com/s/19C9fGmoXne8DgfXhrTB2TQ

提取码:kgwb

pip install appium-python-client #安装client库
# 安装Appium Server,注意选中对应的操作系统	
# 安装1.8jdk,并设置进环境变量
# 安装 Android SDK,解压androidsdk.zip,并配置一个ANDROID_HOME
# 添加adb所在目录进环境变量
# 连接手机(注意要用数据线,即插入后,手机会提示授予权限)
进入 手机设置 -> 关于手机 ,不断点击 具体版本号Miui 菜单(7次以上),
退出到上级菜单,在开发者模式中,启动USB调试

3.入门案例

注意:一定要先运行 Appium Server,还有开发者权限中的禁止权限监控的设置启用

from appium import webdriver
from selenium.webdriver.common.by import By
from appium.webdriver.extensions.android.nativekey import AndroidKey
from appium.options.android import UiAutomator2Options

desired_caps = {
    'platformName': 'Android',  # 被测手机是安卓
    'platformVersion': '13',  # 手机安卓版本,如果是鸿蒙系统,依次尝试 12、11、10 这些版本号
    'deviceName': '努比亚',  # 设备名,安卓手机可以随意填写
    'appPackage': 'tv.danmaku.bili',  # 启动APP Package名称
    'appActivity': '.MainActivityV2',  # 启动Activity名称
    'unicodeKeyboard': True,  # 自动化需要输入中文时填True
    'resetKeyboard': True,  # 执行完程序恢复原来输入法
    'noReset': True,  # 不要重置App
    'newCommandTimeout': 9000,
    'automationName': 'UiAutomator2'
    # 'app': r'd:\apk\bili.apk',
}
# 连接Appium Server,初始化自动化环境
driver = webdriver.Remote('http://localhost:4723/wd/hub',
                         options=UiAutomator2Options().load_capabilities(desired_caps))
# 设置缺省等待时间
driver.implicitly_wait(5)
# 如果有`青少年保护`界面,点击`我知道了`
iknow = driver.find_elements(By.ID, "text3")
if iknow:
    iknow.click()
# 根据id定位搜索位置框,点击
driver.find_element(By.ID, 'expand_search').click()

# 根据id定位搜索输入框,点击
sbox = driver.find_element(By.ID, 'search_src_text')
sbox.send_keys('白月黑羽')
# 输入回车键,确定搜索
driver.press_keycode(AndroidKey.ENTER)

# 选择(定位)所有视频标题
eles = driver.find_elements(By.ID, 'title')

for ele in eles:
    # 打印标题
    print(ele.text)

input('**** Press to quit..')
driver.quit()

4.查找

4.1发现元素

from appium.webdriver.common.appiumby import AppiumBy

driver.find_element(AppiumBy.ACCESSIBILITY_ID, 'byhy')  # appium特有的查找方式
driver.find_element(AppiumBy.ANDROID_UIAUTOMATOR, code)
driver.find_element(By.ID,'username') # 基础的跟selenium的一样
# 这样也可以根据ID
wd.find_element(AppiumBy.ID, 'username').send_keys('byhy')

4.2查找package,activity

apk(Android Package Kit):Android 操作系统中用于分发和安装应用程序的文件格式,即相当于window的exe安装包。

4.2.1没有apk
adb shell dumpsys activity recents | find "intent={" # 进入到应用中后执行该命令
cmp=tv.danmaku.bili/.MainActivityV2 # 这个是运行上面语句后出现于第一句中最后面的
# 应用的package名称就是 tv.danmaku.bili
# 应用的启动Activity就是 .MainActivityV2
4.2.2有apk

如果你已经获取到了 apk,在命令行窗口执行

d:\tools\androidsdk\build-tools\29.0.3\aapt.exe dump badging d:\tools\apk\bili.apk | find "package: name="

输出信息中,就有应用的package名称

package: name='tv.danmaku.bili' versionCode='5531000' versionName='5.53.1' platformBuildVersionName='5.53.1' compileSdkVersion='28' compileSdkVersionCodename='9'

在命令行窗口执行

d:\tools\androidsdk\build-tools\29.0.3\aapt.exe dump badging d:\tools\apk\bili.apk | find "launchable-activity"

输出信息中,就有应用的启动Activity

launchable-activity: name='tv.danmaku.bili.MainActivityV2'  label='' icon=''

5.定位元素

5.1界面元素查看工具

查看界面元素的特征:web是通过开发者工具F12;

常用的app查看元素工具是: Android Sdk包中的 uiautomateviewer 和 Appium Desktop 中的 Appium Inspector

Appium Inspector:可以直接验证 选择表达式是否能定位到元素

5.2定位元素方法

五个定位方式

  1. 根据id
  2. 根据CLass NAME
  3. 根据ACCESSIBILITY ID
from appium.webdriver.common.appiumby import AppiumBy
driver.find_element(AppiumBy.ID, 'expand_search') # 这里的id是resource-id

driver.find_element(  # 一般是多个,相当于tagName
  AppiumBy.CLASS_NAME, 'android.widget.TextView')

性能测试

性能测试是:验证 在 各种性能负载场景下 ,系统的表现是否符合预期。(往往需要模拟 多种 用户行为, 每种行为的用户有多少个。)一个性能测试场景test plan ,中文翻译为 测试计划

作用:评估系统的能力,识别系统的瓶颈,检查系统的稳定性与可靠性,检查系统的隐藏问题。

线程组:把相同的用户行为合并为一组,一个线程代表一个用户。
`Ramp-Up 时间` 意思是 所有用户上线的总时间,以秒为单位。 5 即总共耗时5秒,如果是 5个用户,那就意味着 每隔1.25秒上线一个: 5秒/(5-1)
`循环次数` 意思是 每个用户 做线程组里面定义的动作行为 `多少轮` 。 缺省就做一轮,缺省即默认。
TPS用于衡量系统在一定时间内能够处理的事务数 = 总的事务数/总时间
QPS每一秒的查询率,如果单个接口就是一个业务,那其还等于Rps
资源使用率:服务器,cpu,内存,磁盘,网络。
吞吐量:衡量网络成功传输的数据量

1.GUI模式运行

运行JMeter 有2种运行模式: GUI 图形界面模式CLI 命令行模式,前者是开发调试用的,后者是压力测试用的

添加Listener监听器,最常用的就是查看结果树,显示HTTP请求具体的细节

Load time:4	#从发出请求前 到 接收完所有响应的时间
Connect Time:0 #jmeter和被测建立TCP连接的时间,包括3路握手时间,如果连接复用, 值为0
Latency:3 # 从发出请求前到接收完第一个响应的时间
Size in bytes:22591 # 整个响应消息大小 = Headers size in bytes + Body size in bytes
Sent bytes:357 # 请求消息大小
Headers size in bytes:287
Body size in bytes:22304
Sample Count:1
Error Count:0
Data type ("text"|"bin"|""):text
Response code:200
Response message:OK

Http请求默认值

设置取样器的默认服务器访问地址及使用的网络协议;直接在

项目实战

金融项目

6.1梳理(p15)

基础知识

image-20240905093931407

业务模块

image-20240905105221644

6.2提取测试点

image-20240905114213423

借款业务

image-20240905142632561

投资业务

image-20240905145407567

6.3测试流程

image-20240905163907643

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐