第一部:
1、接口(api)测试在什么时候使用的?
1、新接口:当开发的后台程序开发完毕,交付给测试之后(没有测试测试之前,可能还没开发好这个接口),交付是有前提的,
一般情况下,开发会提供接口文档,后面就用 postman....等接口测试工具测试。
2、老接口:新接口测试(新功能测试),做回归测试,这个时候,新老接口一起做,目的是为了验证新的接口是否对老的接口
产生影响。
3、api测试就是 接口测试
2、还有断言 在讲一遍(jemeter)
断言:某行代码或者某行脚本 之前加上 断言,如果不出错,则程序继续往下执行。如果出错呢?那程序就会抛出异常提示,
告诉你,程序出错,并且终止此次执行。
3、SVN能做哪些事情,是干嘛用的?
svn:版本管理工具,它和git本质上一样,它们两者的区别,就相当于禅道和jira的区别。他两本质上都是文档管理。
代码:
java 代码:xxxx.java
python 代码:xxxx.py
普通文件:
word、excel、ppt、text....
本质:都是文件,都是存储字符串,只是存储的方式不一样。
SVN:存放代码、存放普通文件。git也是一样的功能。
svn在什么时候使用?
1、提交代码的话,当开发人员代码开发完成,就会把代码提交到svn上。
2、提交文件,比如测试用例编写完毕(excel)、测试报告(word)....
为什么要使用svn(git)工具?
1、方便统一管理
2、实现资源共享
3、版本管理的功能、这个只能在代码管理,体现比较大
4、有权限控制,每个team都有对应的文件夹权限
4、敏捷测试?
1、为什么要有敏捷测试?
因为每个公司的项目,不是一次做完就扔掉,后续还会有需求更新和bug。当项目前期做完之后,如果后续还有新的需求或者
需求变更,或者bug,会再次开发,bug修复,再上线。
2、在敏捷测试里面,会有看板?
看板在哪里看到:小公司在物理看板(搞一个白板)
看板进度的追踪:项目经理
看板状态:待开发、开发中(开发人员);待测试、测试中(测试人员);产品验收、关闭(产品经理)
5、有一个客户,买了一百种物品,但是反应有一个单品算错了。
(1)怎么在后台找到这个bug
1、第一反应:我单独测试这个单品,看是否正确,如果正确,看是不是跟其他商品组合的时候,才会算错
如果单品出错的话,那就是这个商品的算法有问题
如果是组合出错的话,那么这个单品在组合的时候算法有问题。
2、考虑业务:
1、看此单品是否参加促销活动,如果促销活动,跟开发确认是否把促销活动的优惠算进去。
2、本身存在的bug,去数据库查看,是否是因为数据的原因的导致,可能就是脏数据
(2)怎么用接口测出来这bug
1、用fiddler抓取这个接口,结算接口
2、结算:参数:商品,商品数量、商品的价格
3、然后根据上述提供的思路测试一下(postman)
6、项目难点在哪里
项目的难点:工作流(核心)
1、一个系统操作流程,一个系统如果没有工作流的话,她跟excel的功能没有区别。
2、什么是工作流?
顾名思义:就是业务流程
电商系统:
1、前台的数据和后台公用
2、后台的数据依赖前台
3、测试环境的数据可以直接在数据库中去造,利用insert into语句
项目问的问题?
1、项目的工作流
2、在测试项目过程测试流程
3、在项目的测试过程用过哪些测试技术
7、用户的权限怎么测
1、用户和用户组
2、用户单个用户权限和用户组权限
3、管理员权限(最高权限)
8、执行测试怎么执行的、详细的步骤。
需求评审(产品经理) -> 测试计划(测试leader) -> 编写测试用例 ->
执行测试用例 -> 发现bug提交到禅道,追踪bug生命周期 -> 编写测试报告
9、怎么定位bug?
1、什么是bug
不满足需求的
(1、根据需求本身,2、比如我查询功能(抓包),3、只要到后台日志里面根据debug信息,提取出对应的sql)
存在程序错误 (2、到后台日志里面:存在 error 级别的错误信息)
不符合用户需求的 (1、竞品分析(去比对类似产品东西))
第二部:
数据库:
1、比如student
删除一张表里面是所有内容:
delete from student
左联:
以左表为基准,右表数据不存在的时候,则显示null
1、数据库,数据库数据是存在磁盘里面的
2、一个数据库实例,有多个数据库,一个数据库下一张到多张表
3、数据库有范式,因为是关系型数据库:
第一范式:一张没有任何约束,数据可以无限的重复。
第二范式:在第一范式的基础之上,多了一个主键约束。
第三范式:在第二范式的基础之上,增加了外键的约束。
....
4、sql语句:
对表数据的操作:
1、简单的sql查询:
查询(select):
select a.* from student (as) a where .... #*:表示当前表中的所有字段
select id,name from student (as) as where ...
更新(update):
update student set age=18 where ....
删除(delete):
delete from student where .....
因为数据是一行一行的删除,而 * 代表的是字段,所以 delete 后面千万不要加*。
增加(insert):
insert into student(字段) values(值)
2、对表的操作:
创建表和数据库:
创建/删除student表:create/drop table student
创建/删除数据库: create/drop database db1
对表结构的操作:(特殊的关键字,需要加table关键字)
alter table student add column api_pay_no varchar(32) not null unique comment
3、复杂sql:
1、嵌套:
course_name="数学"
选的课程为数学的学生:
select * from student where course_id in (
select id from course where course_name = "数学")
in :表示 条件数据 1个到多个
= : 相等于的数据只有一个
select * from class where id in (select class_id from student where
course_id in (select id from course where course_name="数学"))
2、左联、右联、内联
左联(left join):
以左表为基准,与右表比较值不相等,右表的数据显示为null
select s.* from student s
left join course c on s.course_id = c.id
where ....
右联:(right join)
以右表为基准,与左表比较值不相等,左表的数据显示为null
select s.* from student s
right join course c on s.course_id = c.id
where ....
内联:((inner) join )
以左表为基准,只显示匹配与右表相等数据
select s.* from student s
(inner) join course c on s.course_id = c.id
where ....
3、在sql的查询中,遵循一个原则: 一切皆表
select * from (
select * from (
select * from student where course_id in (
select id from course where course_name = "数学") ) as a
left join (
select s.* from student s
left join course c on s.course_id = c.id
where .... ) as b on a.id = b.id
where ... ) as a
.....
一个复杂的sql,有几大原则:
select 后面的永远是 字段
from 后面永远是 表
where 后面的永远是条件。
4、分组(group by)
select sum(course) from course group by name having name = "张三"
order by sum(course) desc(asc)
Linux
为什么要学Linux?Linux在我们工作当中什么时候使用?
Linux在我们公司里面 999.99999999...%应用都是部署在Linux。
Linux 上面服务都已经部署完毕,我们在排除程序上bug的时候,会去看日志。
1、搭建环境:
jdk、Tomcat、Apache、PHP、mysql、、小游戏
1、jdk : java程序运行环境
1、jdk /usr/local/java/
2、配置环境变量 /etc/profile
2、Tomcat:web应用服务器,可以部署在Linux(.sh),也可以部署在Windows(.bat),部署在mac
Tomcat目录:
- bin 存放执行脚本的文件夹 #启动方式: sh startup.sh 或者 ./startup.sh
- conf 存放程序配置文件 配置Tomcat端口等配置文件
- lib 存在程序所调用的jar包
- logs 存放日志信息
- temp 临时文件夹 存放临时文件
- webapps 存放我们的应用程序
- work 存放我们程序的进程号
3、apache(nginx):web服务器
目录结构:
....
2、常用命令:
1、创建文件:touch
2、删除文件:rm(remove)
3、查看文件:cat
4、复制: cp(copy)
5、创建文件夹: mkdir(make directory)
6、剪切或者重命名:mv(move)
7、压缩解压缩: tar
8、查看:ls list
9、编辑: vi/vim
10、查看当前路径:pwd(Print Working Directory)
11、切换用户:su switch user
12、创建用户:useradd
13、删除用户:userdel
14、创建用户组: groupadd
15、删除用户组:groupdel
16、查找: find
17、修改权限: chmod(change mode)
18、查看进程: ps process
19、杀进程: kill
20、查看日志:tail
1、Linux的每个命令,是一个或者多个英文单词的缩写。
2、拷贝: cp 文件1 文件2 cp -r 目录1 目录2
3、杀进程: kill -9 进程号
4、查看日志: tail -f catalina.out -n 300
查看命令的使用:
tail --help
man tail
接口测试:
postman
断言:在程序前加上一个断言,当程序没有出错,则正常往下执行,如果出错则,给出异常提示,程序终止。
http协议:
http协议属于应用层协议,分为请求和响应
请求:请求头(header)和请求体(body)
响应:响应头(header)和响应体(body)
请求,请求方法:
post、get...
1、post和get方法:
1、post 请求参数 放在 body 里面,get的请求的参数 放在 header里面的。
2、post请求参数的是没有字数限制,get请求参数是有字数限制的
3、post和get向服务器发送请求,没有本质安全可言,通过抓包都可以抓取到,只不过向服务器发送请求的时候
,一个放在手上,一个放在口袋。
2、http请求的状态码:
1xx: 消息
2XX: 成功 200
3XX: 重定向 302
4XX: 客户端错误 404 403
5XX: 服务端错误 500
第三部:
项目理论
一、项目的职责与分工
产品经理: 设计产品的prd,整理需求的人员
项目经理:管理整个项目的生命周期的,项目立项,项目在开发测试过程中协调,进度追踪,流程优化...
开发人员:
后台开发:Java开发(开发接口)
前台开发:安卓开发、ios、前端开发...(开发界面)
测试人员:
web测试(前端页面)
移动端测试(ios,安卓)
配置管理人员:
负责搭建测试环境、负责管理jar版本、负责管理线下配置文件、负责测试环境工具的稳定,负责管理代码
运维人员:
负责把测试环境的程序包放到生产上,负责生产环境的稳定。
DBA(数据库管理员):
负责测试环境和生产环境数据库的稳定和优化
二、项目立项
是由项目经理主导,管理整个项目的生命
为什么要立这个项目,解决的什么问题。
此次立项参与的人员(产品经理、开发人员、测试人员)
三、测试流程
1、参加需求评审会
2、测试leader编写测试计划
3、测试用例的设计和编写测试用例
4、评审测试用例
5、执行测试用例 -> 发现bug 提交 到禅道上
6、测试leader整理测试报告
7、上线(校验你能校验的)
四、测试人员
测试人员在我们工作当中,需要干些什么?
1、熟悉业务:
1、产品prd
2、熟悉web应用服务器部署
3、熟悉数据,数据的访问地址,跟业务相关的数据库表结构。
2、制定测试计划
测试计划是由测试leader制定,参与编写测试计划
3、设计测试用例
借助 xmind 工具,设计测试用例
(等价类、边界值、场景法等)
4、编写测试用例
5、发现bug,提交到禅道
bug的定义:
1、不符合需求
2、程序本身报错
3、不符合用户的使用习惯。
6、编写测试报告
测试leader写的,测试人员,参与编写测试报告
1、简要
2、测试结果的分析
1、测试用例覆盖率
2、测试用例通过率,失败率
3、发现bug严重程度的统计,每个模块的bug数的统计。
4、当前遗留的bug数
5、负责此项目的测试人员的测试用例
6、上线标准
3、本次测试过程中遇到风险
4、总结
7、编写发布单
8、上线发布的流程
这个问题来源于这段时间负责的电商项目,关于页面跳转逻辑日常的和开发进行了一番撕逼,开发坚持认为应该原路返回,坚决站在用户的角度 ,允许用户返回修改订单信息,balabala。
乍一看,这个问题不过是表现层方面的问题,仔细思考后,这个问题并不简单,隐藏着一些列不为人知的情节,且听我一一梳理
做产品这段时间以来,总结出一个有趣现象。每每和其他同事争论到刀光剑影的时刻,对方很容易就会把「用户」这尊大佛搬出来,顿时突然想笑。
做产品以用户为中心,没错,但是不管你是开发还是设计你仅仅代表的是你自己,不要轻易去代表用户,这样的讨论其实是苍白的,没有实际价值。
相比于「用户」这尊大佛,用数据才是最高级的做法,要学会用数据替你说话。不会撕逼的产品不是好产品,既然撕逼一定要撕得漂亮一点。
一、场景描述
想必大家都曾遇到过这个问题,在电商购物的过程中,已经走到了最后一步:去支付。这个时候突然意识到商品数量不对,或者收货信息选错。
除此之外,用户还存在之下返回的原因:
误点击,也就是说用户还是想买的;
犹豫中点了返回,想买的欲望不是十分坚决;
坚决不买了。
二、可选方案
(1)目前几乎所有主流电商平台,在支付页面点击返回跳转到订单的待支付页面。
(2)有一部分微商城,依然原路径返回,不过依然生成了待支付订单。
(3)原路返回,也不生成待支付订单,不过作者目前并没有找到此类型的案例。
三、为什么要有「待付款」状态
1. 库存计算
在电商系统中,前端页面显示的库存与仓库的实体库存是不同步的,因而在商品出仓前要求前端的库存进行「锁定」即前端的减库存。
关于库存的锁定,电商领域存在有两种方案:
一种是拍下减库存即生成订单(待付款)减库存,故此方案绕不过「待支付」;
一种是支付成功减库存。
拍下减库存存在的问题:
用户可能拍下不买,不乏存在有用户把拍下当收藏夹用,以致占用库存,影响平台的交易量。甚至存在更为极端的「恶拍」漏洞,竞争对手会把商品所有库存全都拍掉,也不付钱,平台的商品就全部被下架了。
支付成功减库存存在的问题:
支付成功减库存会碰到最严重的问题,是「超卖」。因为系统在付款成功之前,都不减库存,所以总是会发生“短时间很多人都拍下,甚至都付钱了,但是系统却发现库存不够了”。
买家拍下商品后,从提交付款到付款成功的之间是有时间差的,因为付款的动作是在几个不同的系统之间传信息。因此最后一件商品可能被多人拍下,这几个人都可能付款成功。
淘宝的做法是把何时减库存的决定权交给卖家,然后告知卖家两个方案各自适应的场景。
2. 提高转化
电商是通过交易驱动的产品类型,因此订单的每一步都要考虑转化率,提高转化率是电商的基础要求。
用户在电商下单,大多都是会进行一番思考的,毕竟支付宝里的钱也不是河水流过来的。用户在支付前总会有种种原因搁置付款,一般待支付订单的有效时间为24小时以内,在这段有效时间内平台就像一名促销员一样,告知你有未付款的订单。
四、确定解决方案
结果是几乎所有的电商都采用了从支付页面返回跳转至待支付的方案。
从用户角度来考量:退回去修改信息(收货信息、商品信息)一定是用户真实存在的诉求。
在商家的角度:提高订单的成交率,是第一要务。这个时候最好的办法就是利用数据工具,做埋点和统计,根据各种情况出现的概率做出相应的决策。
数据是最客观公证的,当场景遇到矛盾的时候,用数据来做决策。
在做产品时,很多业务场景中的实际问题是两难的,甚至更多。我们作为产品的设计者,需要在不同的角色利益之间做出权衡,因此我们需要设计出一个算法,赋予每个相关变量最恰当的权重,以此来求得一个最优解。
购物车
1. 商品信息- 数量、单价、名称
2. 用户信息- 帐号、密码、余额
3. 用户可充值
4. 购物历史信息
5. 允许用户多次购买,每次可购买多件
6. 余额不足时进行提醒
7. 用户退出时 ,输出当次购物信息
8. 用户下次登陆时可查看购物历史
9. 商品列表分级显示
使用的购物菜单数据,采用字典结构进行创建。
该项目思路:
1、创建一个文件用来保存用户名和密码信息,每次用户注册和登录操作时从中读取和写入数据
2、创建另一个文件用来记录每个用户的购物信息以及个人账户信息,采用字典键值对的形式实现每个用户和信息的对应关系
3、利用函数实现每一个模块的功能,最后进行整合
4、商品分级显示可以参照三级菜单的实现原则+try...except...
1 import time 2 #用户注册 3 #user_shopping_info = {name:[account,history_info=[商品名,时间]],} 4 def register(): 5 r_flag = True 6 while True: 7 new_user_name = input("请输入新账户名【返回R】:\n<<<").strip() 8 if not new_user_name: 9 print('用户名不能为空!') 10 continue 11 if new_user_name.lower() == 'r': 12 break 13 new_user_pwd = input("请输入用户密码且不少于六位【返回R】:\n<<<").strip() 14 if new_user_pwd.lower() == 'r': 15 break 16 if len(new_user_pwd) < 6: 17 print('密码长度小于6位!') 18 continue 19 with open("user_info",mode="r+",encoding="utf8") as f1: 20 with open('user_shopping_info','r+',encoding='utf8') as f2: 21 f1.seek(0) 22 for line in f1: 23 if line.startswith("name:") and new_user_name in line: #判断是否已经有该用户 24 print("该用户已经被注册!") 25 r_flag = False 26 continue 27 if r_flag: 28 f1.write("\nname:"+new_user_name+'\t|password:'+new_user_pwd) 29 info_str = f2.read() 30 try: 31 user_shopping_info = eval(info_str) 32 except Exception: 33 user_shopping_info = {} 34 user_shopping_info[new_user_name] = [10000,[]] #默认每个账户初始金额为10000 35 f2.seek(0) 36 f2.write(str(user_shopping_info)) 37 print("新用户注册成功!") 38 break 39 40 #用户登录 41 def login(): 42 l_flag = False 43 while True: 44 user_name = input("请输入账户名【返回R】:\n<<<").strip() 45 if not user_name: 46 print('用户名不能为空!') 47 continue 48 if user_name.lower() == 'r': 49 break 50 user_pwd = input("请输入用户密码【返回R】:\n<<<").strip() 51 if user_pwd == 'r': 52 break 53 with open("user_info",mode="r",encoding="utf8") as f_read: 54 f_read.seek(0) 55 for line in f_read: 56 #关键调试步骤 57 if line.startswith("name:") and user_name in line and line.strip().split(':')[-1] == user_pwd: 58 l_flag = True 59 break 60 if l_flag: 61 print('用户登录成功!') 62 print('欢迎进入购物车界面'.center(19,'*')) 63 switch(user_name) 64 break 65 else: 66 print('用户名或者密码错误!') 67 68 #购物历史信息查询 69 def history_info(user_name): 70 with open('user_shopping_info','r',encoding='utf8') as f: 71 info_str = f.read() 72 user_shopping_info = eval(info_str) 73 count = 1 74 print('您的历史购物记录如下:') 75 for line in user_shopping_info[user_name][1]: 76 if count % 2 == 1: 77 print(line,'日期:',end='') 78 count += 1 79 else: 80 print(line) 81 count += 1 82 83 #账户余额 84 def search_balance(user_name): 85 with open('user_shopping_info','r',encoding='utf8') as f: 86 info_str = f.read() 87 user_shopping_info = eval(info_str) 88 balance = user_shopping_info[user_name][0] 89 print('您的可用余额为:%s元'%balance) 90 return balance 91 #进行购物 92 #user_shopping_info = {name:[account,history_info=[商品名,时间]],} 93 def shopping(user_name): 94 while True: 95 balance = search_balance(user_name) 96 with open("shopping menu", mode="r",encoding='utf8') as f: 97 product_str = f.read() 98 product_list = eval(product_str) 99 current_layer = product_list 100 last_layer = [] 101 while True: 102 for line in current_layer: 103 print('\33[33m'+ line +'\33[37m') 104 choice = input('请输入要购买的物品【退回上一层B】【退出Q】:\n>>>>').strip() 105 if not choice: continue 106 if choice == 'q': #退出循环 107 print("退出购物!") 108 return 109 elif choice.lower() == 'b': # 退回到上一层目录 110 if last_layer: 111 current_layer = last_layer[-1] 112 last_layer.pop() 113 continue 114 else: 115 print('当前处在起始层!') 116 elif choice in current_layer: 117 try: 118 last_layer.append(current_layer) # 采用列表里面嵌套字典的结果! 119 current_layer = current_layer[choice] # 进到下一层 120 except Exception: 121 ins_buy = input('是否要购买该商品【是Y否N】').strip() 122 if ins_buy.lower() == 'y': 123 goods_name = choice.split(' ')[0] 124 print(goods_name) 125 print(type(goods_name)) 126 goods_price = choice.split(' ')[1] 127 tim = str(time.strftime('%Y-%m-%d %X')) 128 break 129 else: 130 continue 131 else: 132 print('您输入的信息有误,请重新选择!') 133 if int(balance) >= int(goods_price): 134 bal = int(balance) - int(goods_price) 135 with open('user_shopping_info','r+',encoding='utf8') as f: 136 info_str = f.read() 137 user_shopping_info = eval(info_str) 138 user_shopping_info[user_name][0] = bal 139 user_shopping_info[user_name][1].append(goods_name) 140 user_shopping_info[user_name][1].append(tim) 141 f.seek(0) 142 f.write(str(user_shopping_info)) 143 print('商品已经购买!') 144 else: 145 print('您的账户余额不足,请先进行充值!') 146 147 #账户充值 148 def recharge(user_name): 149 with open('user_shopping_info','r+',encoding='utf8') as f: 150 info_str = f.read() 151 user_shopping_info = eval(info_str) 152 balance = user_shopping_info[user_name][0] 153 print('您的可用余额为:%s元'%balance) 154 while True: 155 recharge = input('请选择要充值的金额:' 156 '\n\t【1】500' 157 '\n\t【2】1000' 158 '\n\t【3】2000' 159 '\n\t【4】5000' 160 '\n\t【5】自选金额' 161 '\n\t【Q】退出' 162 '\n>>>').strip() 163 if recharge.lower() == 'q': 164 break 165 if recharge == '1': 166 rec = '500' 167 elif recharge == '2': 168 rec = '1000' 169 elif recharge == '3': 170 rec = '2000' 171 elif recharge == '4': 172 rec = '5000' 173 elif recharge == '5': 174 rec = input('请输入金额:').strip() 175 if not rec.isdigit(): 176 print('您输入的金额有误,请重新开始操作!') 177 continue 178 else: 179 print('您选择的操作有误,请重新选择!') 180 continue 181 balance = int(balance) + int(rec) 182 print('充值成功,您可以继续充值或退出!') 183 user_shopping_info[user_name][0] = balance 184 f.seek(0) 185 f.write(str(user_shopping_info)) 186 187 #操作选择界面 188 def switch(user_name): 189 while True: 190 case = input('请选择您要进行的操作:' 191 '\n\t1.购物历史信息查询' 192 '\n\t2.账户余额' 193 '\n\t3.开始购物' 194 '\n\t4.账户充值' 195 '\n\tQ.退出' 196 '\n>>>').strip() 197 if case.lower() == 'q': 198 history_info(user_name) 199 print('成功退出购物车!') 200 break 201 elif case == '1': 202 history_info(user_name) 203 elif case == '2': 204 search_balance(user_name) 205 elif case == '3': 206 shopping(user_name) 207 elif case == '4': 208 recharge(user_name) 209 else: 210 print('您输入的指令有误,请重新输入!') 211 continue 212 213 214 if __name__ == '__main__': 215 while True: 216 choice = input("登陆L 注册R 退出Q:\n<<<<").strip() 217 if choice.lower() == 'q': 218 print("欢迎下次光临!") 219 exit() 220 elif choice.lower() == 'l': 221 login() 222 elif choice.lower() =='r': 223 register() 224 else: 225 print('请输入正确的操作指令!') 226 continue |
通过该项目掌握了对文件的操作,包括写入和读取数据的注意事项,字符编码很重要!!!
在写程序流时什么时候该用到循环,什么时候进行判断都很重要。在函数的调用时,函数内部存在循环时,continue 进行下一次循环;break 跳出循环;return 直接退出该函数并且可以具有返回值。
再一次体会到字典在保存和调用数据时非常给力,程序中函数相互调用时需要传入关键性参数,本项目的关键参数是每一个登录后的用户名。