软件测试/测试开发10年经验技能树
2021-04-14 14:45:21 6 举报
AI智能生成
测试 测试开发 Android自动化 前端
作者其他创作
大纲/内容
后端 中间件测试
接口测试
restful api
REST,表示性状态转移(representation state transfer)。简单来说,就是用URI表示资源,用HTTP方法(GET, POST, PUT, DELETE)表征对这些资源的操作。
Resource: 资源,即数据,存在互联网上的可被访问的实体
Representation: 数据的某种表现形式,如HTML, JSON。
State Transfer:状态变化,HTTP方法实现
Resource: 资源,即数据,存在互联网上的可被访问的实体
Representation: 数据的某种表现形式,如HTML, JSON。
State Transfer:状态变化,HTTP方法实现
状态码
状态码 响应类别 原因短语
1XX 信息性状态码(Informational) 服务器正在处理请求
2XX 成功状态码(Success) 请求已正常处理完毕 200 OK
3XX 重定向状态码(Redirection) 需要进行额外操作以完成请求
4XX 客户端错误状态码(Client Error) 客户端原因导致服务器无法处理请求 400错误请求 401 Unauthorized未经授权 403 Forbidden 404not found
5XX 服务器错误状态码(Server Error) 服务器原因导致处理请求出错 501 Internal Server Error 503 Service Unavailable
状态码 响应类别 原因短语
1XX 信息性状态码(Informational) 服务器正在处理请求
2XX 成功状态码(Success) 请求已正常处理完毕 200 OK
3XX 重定向状态码(Redirection) 需要进行额外操作以完成请求
4XX 客户端错误状态码(Client Error) 客户端原因导致服务器无法处理请求 400错误请求 401 Unauthorized未经授权 403 Forbidden 404not found
5XX 服务器错误状态码(Server Error) 服务器原因导致处理请求出错 501 Internal Server Error 503 Service Unavailable
webservice soap
soap请求 (SimpleObject Access Protocol,简单对象访问协议) 是HTTP POST的一个专用版本,遵循一种特殊的xml消息格式Content-type设置为: text/xml任何数据都可以xml化。基于http和xml WSDL相当于接口文档
RPC dubbo
a) 业务功能覆盖是否完整
b) 业务规则覆盖是否完整
c) 参数验证是否达到要求(边界、业务规则)
d) 接口异常场景覆盖是否完整
e) 接口覆盖率是否达到要求
f) 代码覆盖率是否达到要求
g) 性能指标是否满足要求
h) 安全指标是否满足要求
b) 业务规则覆盖是否完整
c) 参数验证是否达到要求(边界、业务规则)
d) 接口异常场景覆盖是否完整
e) 接口覆盖率是否达到要求
f) 代码覆盖率是否达到要求
g) 性能指标是否满足要求
h) 安全指标是否满足要求
性能测试
负载测试
压力测试
编程语言
python
基础知识
算法
设计模式
MVC
MVVM
MVP
单例
kotlin
变量
val不可变的变量 对应java final
var声明可变的变量
类型推导机制
显示声明 val a: Int=10
kotlin抛弃java基本数据类型全部使用对象数据类型
函数
fun main(){}
接口
interface xx
引用接口 用逗号 ,
引用接口 用逗号 ,
OOP
继承 子类:父类() {}
单例 object 类名{} 类似静态方法调用
类名.方法 不用getinstance()
类名.方法 不用getinstance()
JAVA
测试前移辅助
代码静态扫描/code review
代码覆盖率
jacoco
持续集成
Jenkins
azure
代码分支策略
dashboard sonarqube
mock技术
Mock 不是 Stub,两者是有区别的:
前者被称为 mockist TDD,而后者一般称为 classic TDD ;
前者是基于行为的验证(behavior verification),后者是基于状态的验证 (state verification);
前者使用的是模拟的对象,而后者使用的是真实的对象。
Mock 测试就是在测试过程中,对于某些 不容易构造(如 HttpServletRequest 必须在 Servlet 容器中才能构造出来)或者不容易获取 比较复杂 的对象(如 JDBC 中的 ResultSet 对象),用一个 虚拟 的对象(Mock 对象)来创建,以便测试方法。
优点:
可以很简单的虚拟出一个复杂对象(比如虚拟出一个接口的实现类);
可以配置 mock 对象的行为;
可以使测试用例只注重测试流程与结果;
减少外部类、系统和依赖给单元测试带来的耦合。
缺点:
不能 mock 静态方法;
不能 mock 构造器;
不能 mock equals() 和 hashCode() 方法。
Mock 不是 Stub,两者是有区别的:
前者被称为 mockist TDD,而后者一般称为 classic TDD ;
前者是基于行为的验证(behavior verification),后者是基于状态的验证 (state verification);
前者使用的是模拟的对象,而后者使用的是真实的对象。
Mock 测试就是在测试过程中,对于某些 不容易构造(如 HttpServletRequest 必须在 Servlet 容器中才能构造出来)或者不容易获取 比较复杂 的对象(如 JDBC 中的 ResultSet 对象),用一个 虚拟 的对象(Mock 对象)来创建,以便测试方法。
优点:
可以很简单的虚拟出一个复杂对象(比如虚拟出一个接口的实现类);
可以配置 mock 对象的行为;
可以使测试用例只注重测试流程与结果;
减少外部类、系统和依赖给单元测试带来的耦合。
缺点:
不能 mock 静态方法;
不能 mock 构造器;
不能 mock equals() 和 hashCode() 方法。
mockito 以 行为驱动开发 的格式使用 //given //when //then 注释为测试用法基石编写测试用例,这正是 Mockito 官方编写测试用例方法,强烈建议使用这种方式测试编写
robotmatric
网络协议
TCPIP HTTP
CRUD分别对应到请求
C post
R get
U put
D delete
C post
R get
U put
D delete
HTTP基本性质
TCP
UDP
工具
bug管理系统 CQ QC 禅道
fiddler
postman
IDE
airtest
wireshark
tcpdump
DOS常用命令
git
分布式版本管理
尽量使用UTF-8编码
尽量使用UTF-8编码
git init foldername初始化一个文件夹作为git仓库
git add filename 添加到仓库
git commit提交到仓库-m参数是提交说明
git status命令可以让我们时刻掌握仓库当前的状态,上面的命令输出告诉我们,readme.txt被修改过了,但还没有准备提交的修改。
git diff顾名思义就是查看difference,显示的格式正是Unix通用的diff格式
git log命令显示从最近到最远的提交日志
HEAD当前版本 HEAD^上一个 HEAD^^上上一个
git reset --hard HEAD^ HEAD~100 git reset --hard 1094a
git reset --hard HEAD^ HEAD~100 git reset --hard 1094a
撤销修改
场景1:当你改乱了工作区某个文件的内容,想直接丢弃工作区的修改时,用命令git checkout -- file。
场景2:当你不但改乱了工作区某个文件的内容,还添加到了暂存区时,想丢弃修改,分两步,第一步用命令git reset HEAD <file>,就回到了场景1,第二步按场景1操作。
场景3:已经提交了不合适的修改到版本库时,想要撤销本次提交,参考版本回退一节,不过前提是没有推送到远程库。
场景1:当你改乱了工作区某个文件的内容,想直接丢弃工作区的修改时,用命令git checkout -- file。
场景2:当你不但改乱了工作区某个文件的内容,还添加到了暂存区时,想丢弃修改,分两步,第一步用命令git reset HEAD <file>,就回到了场景1,第二步按场景1操作。
场景3:已经提交了不合适的修改到版本库时,想要撤销本次提交,参考版本回退一节,不过前提是没有推送到远程库。
删除文件 一是确实要从版本库中删除该文件,那就用命令git rm删掉,并且git commit
另一种情况是删错了,因为版本库里还有呢,所以可以很轻松地把误删的文件恢复到最新版本:
$ git checkout -- test.txt
git checkout其实是用版本库里的版本替换工作区的版本,无论工作区是修改还是删除,都可以“一键还原”。
另一种情况是删错了,因为版本库里还有呢,所以可以很轻松地把误删的文件恢复到最新版本:
$ git checkout -- test.txt
git checkout其实是用版本库里的版本替换工作区的版本,无论工作区是修改还是删除,都可以“一键还原”。
git remote add origin git@github.com:michaelliao/learngit.git
把本地库的内容推送到远程,用git push命令,实际上是把当前分支master推送到远程。
由于远程库是空的,我们第一次推送master分支时,加上了-u参数,Git不但会把本地的master分支内容推送的远程新的master分支,还会把本地的master分支和远程的master分支关联起来,在以后的推送或者拉取时就可以简化命令。git push origin master
把本地master分支的最新修改推送至GitHub,现在,你就拥有了真正的分布式版本库!
要关联一个远程库,使用命令git remote add origin git@server-name:path/repo-name.git;
关联后,使用命令git push -u origin master第一次推送master分支的所有内容;
此后,每次本地提交后,只要有必要,就可以使用命令git push origin master推送最新修改;
分布式版本系统的最大好处之一是在本地工作完全不需要考虑远程库的存在,也就是有没有联网都可以正常工作,而SVN在没有联网的时候是拒绝干活的!当有网络的时候,再把本地提交推送一下就完成了同步,真是太方便了!
把本地库的内容推送到远程,用git push命令,实际上是把当前分支master推送到远程。
由于远程库是空的,我们第一次推送master分支时,加上了-u参数,Git不但会把本地的master分支内容推送的远程新的master分支,还会把本地的master分支和远程的master分支关联起来,在以后的推送或者拉取时就可以简化命令。git push origin master
把本地master分支的最新修改推送至GitHub,现在,你就拥有了真正的分布式版本库!
要关联一个远程库,使用命令git remote add origin git@server-name:path/repo-name.git;
关联后,使用命令git push -u origin master第一次推送master分支的所有内容;
此后,每次本地提交后,只要有必要,就可以使用命令git push origin master推送最新修改;
分布式版本系统的最大好处之一是在本地工作完全不需要考虑远程库的存在,也就是有没有联网都可以正常工作,而SVN在没有联网的时候是拒绝干活的!当有网络的时候,再把本地提交推送一下就完成了同步,真是太方便了!
要克隆一个仓库,首先必须知道仓库的地址,然后使用git clone命令克隆。
Git支持多种协议,包括https,但ssh协议速度最快。
Git支持多种协议,包括https,但ssh协议速度最快。
我们创建dev分支,然后切换到dev分支:
$ git checkout -b dev
Switched to a new branch 'dev'
git checkout命令加上-b参数表示创建并切换,相当于以下两条命令:
$ git branch dev
$ git checkout dev
Switched to branch 'dev'
$ git checkout -b dev
Switched to a new branch 'dev'
git checkout命令加上-b参数表示创建并切换,相当于以下两条命令:
$ git branch dev
$ git checkout dev
Switched to branch 'dev'
git merge命令用于合并指定分支到当前分支
$ git branch -d dev
Deleted branch dev (was b17d20e).
Deleted branch dev (was b17d20e).
Git鼓励大量使用分支:
查看分支:git branch
创建分支:git branch <name>
切换分支:git checkout <name>或者git switch <name>
创建+切换分支:git checkout -b <name>或者git switch -c <name>
合并某分支到当前分支:git merge <name>
删除分支:git branch -d <name>
用git log --graph命令可以看到分支合并图
查看分支:git branch
创建分支:git branch <name>
切换分支:git checkout <name>或者git switch <name>
创建+切换分支:git checkout -b <name>或者git switch -c <name>
合并某分支到当前分支:git merge <name>
删除分支:git branch -d <name>
用git log --graph命令可以看到分支合并图
git merge --no-ff -m "merge with no-ff" dev
no ff用的是普通合并而不是fastforward
ff 这种模式下,删除分支后,会丢掉分支信息
no ff用的是普通合并而不是fastforward
ff 这种模式下,删除分支后,会丢掉分支信息
git tag v0.9 f52c633
命令git tag <tagname>用于新建一个标签,默认为HEAD,也可以指定一个commit id;
命令git tag -a <tagname> -m "blablabla..."可以指定标签信息;
命令git tag可以查看所有标签。
git tag -d v0.1 删除tag
命令git tag -a <tagname> -m "blablabla..."可以指定标签信息;
命令git tag可以查看所有标签。
git tag -d v0.1 删除tag
命令git push origin <tagname>可以推送一个本地标签;
命令git push origin --tags可以推送全部未推送过的本地标签;
命令git tag -d <tagname>可以删除一个本地标签;
命令git push origin :refs/tags/<tagname>可以删除一个远程标签
命令git push origin --tags可以推送全部未推送过的本地标签;
命令git tag -d <tagname>可以删除一个本地标签;
命令git push origin :refs/tags/<tagname>可以删除一个远程标签
工作区与暂存区
Git和其他版本控制系统如SVN的一个不同之处就是有暂存区的概念
工作区(Working Directory)
就是你在电脑里能看到的目录,比如我的learngit文件夹就是一个工作区:
版本库(Repository)
工作区有一个隐藏目录.git,这个不算工作区,而是Git的版本库。
Git的版本库里存了很多东西,最重要的就是称为stage(或者叫index)的暂存区,还有Git为我们自动创建的第一个分支master,以及指向master的一个指针叫HEAD。需要提交的文件修改通通放到暂存区,然后,一次性提交暂存区的所有修改。Git跟踪并管理的是修改,而非文件
工作区(Working Directory)
就是你在电脑里能看到的目录,比如我的learngit文件夹就是一个工作区:
版本库(Repository)
工作区有一个隐藏目录.git,这个不算工作区,而是Git的版本库。
Git的版本库里存了很多东西,最重要的就是称为stage(或者叫index)的暂存区,还有Git为我们自动创建的第一个分支master,以及指向master的一个指针叫HEAD。需要提交的文件修改通通放到暂存区,然后,一次性提交暂存区的所有修改。Git跟踪并管理的是修改,而非文件
集中式版本控制系统,版本库是集中存放在中央服务器的,而干活的时候,用的都是自己的电脑,所以要先从中央服务器取得最新的版本,然后开始干活,干完活了,再把自己的活推送给中央服务器。中央服务器就好比是一个图书馆,你要改一本书,必须先从图书馆借出来,然后回到家自己改,改完了,再放回图书馆。
集中式和分布式的区别是:
你的本地是否有完整的版本库历史!
假设SVN服务器没了,那你丢掉了所有历史信息,因为你的本地只有当前版本以及部分历史信息。
假设GitHub服务器没了,你不会丢掉任何git历史信息,因为你的本地有完整的版本库信息。你可以把本地的git库重新上传到另外的git服务商。
集中式和分布式的区别是:
你的本地是否有完整的版本库历史!
假设SVN服务器没了,那你丢掉了所有历史信息,因为你的本地只有当前版本以及部分历史信息。
假设GitHub服务器没了,你不会丢掉任何git历史信息,因为你的本地有完整的版本库信息。你可以把本地的git库重新上传到另外的git服务商。
分支管理
首先,master分支应该是非常稳定的,也就是仅用来发布新版本,平时不能在上面干活;
那在哪干活呢?干活都在dev分支上,也就是说,dev分支是不稳定的,到某个时候,比如1.0版本发布时,再把dev分支合并到master上,在master分支发布1.0版本;
你和你的小伙伴们每个人都在dev分支上干活,每个人都有自己的分支,时不时地往dev分支上合并就可以了
那在哪干活呢?干活都在dev分支上,也就是说,dev分支是不稳定的,到某个时候,比如1.0版本发布时,再把dev分支合并到master上,在master分支发布1.0版本;
你和你的小伙伴们每个人都在dev分支上干活,每个人都有自己的分支,时不时地往dev分支上合并就可以了
面试常见问题
为什么要离职
客观
待遇
公司倒闭 业绩不好
家庭
主观
同上级 同事关系不佳
目前平台瓶颈
爱好 意向岗位
年限较长候选人:职业规划,更倾向于技术岗还是管理岗
技术岗
深度与广度的机会
管理岗
预期与坑位
逻辑思维
批判性思考 客观 质疑 数据说话
DISC
评测性格类型
向上管理 向下管理 平级管理
是否情绪化
高压状况下表现
软件测试基础理论
七个基本原则
测试证明存在错误 而不是为了证明不存在问题
测试应尽早开始,越晚发现修复成本越高
穷尽测试是不可能的
测试用例的杀虫剂效应
缺陷群集效应 2/8原则
不存在缺陷悖论
测试活动是上下文相关的,不同的测试背景侧重点不同采用不同的测试策略
测试用例的方法
基于需求/标准验证的方法
基于直觉和经验的方法
错误推测法
alac
ad hoc
基于输入域
场景法
决策表 因果图
边界值
划分等价类
组合测试法
场景法
正交实验法
白盒 基于代码
控制流覆盖
基本路径覆盖
逻辑覆盖
语句覆盖
条件覆盖
判定覆盖
组合覆盖
数据流覆盖
变量定义-引用 数据驱动
基于模型的方法MBT
质量六要素
功能性
易用性
可靠性
效率
可移植性
可维护性
定义:早期19世纪七八十年代 为发现错误而执行软件或者系统的过程
IEEE 使用人工和自动手段来运行或测试某个系统的过程,其目的在于检验它是否满足规定的需求或是弄清预期结果与实际结果之间的差别
IEEE 使用人工和自动手段来运行或测试某个系统的过程,其目的在于检验它是否满足规定的需求或是弄清预期结果与实际结果之间的差别
五个要素和两个目标 质量 人员 流程 资源 技术
目标 测试覆盖率 测试效率
目标 测试覆盖率 测试效率
内部质量 代码评审分析
外部质量 系统测试
使用质量 验收测试
外部质量 系统测试
使用质量 验收测试
测试流程
V模型
需求分析-概要设计-详细设计-代码 单元测试-集成测试-系统测试-验收测试
敏捷
CICD流水线,流水线上部署单元测试和接口测试 以及BVT 和少数UI测试,人工主要effort是最小可交付件的质量保证上
IPD模型
EVT DVT PVT MP STR1 STR2 STR3A STR3 STR4 STR5
可测试性
基本原则:可控制 可观察 可度量
ATDD/BDD需求可验证性
TDD代码可验证性
设计可验证性
接口定义清晰
功能 性能 压力/稳定性/负载 可靠性 安全 兼容 易用 合规
前端测试
web前端
web测试
H5
<!DOCTYPE html> 声明为 HTML5 文档
<html> 元素是 HTML 页面的根元素
<head> 元素包含了文档的元(meta)数据,如 <meta charset="utf-8"> 定义网页编码格式为 utf-8。
<title> 元素描述了文档的标题
<body> 元素包含了可见的页面内容
<h1> 元素定义一个大标题 标题h1-h6从大到小
<p> 元素定义一个段落
HTML 链接是通过标签 <a> 来定义的. <a href="https://www.runoob.com">这是一个链接</a>
HTML 图像是通过标签 <img> 来定义的. <img src="/images/logo.png" width="258" height="39" />
<br> 换行<hr>分割线
<!-- 这是一个注释 -->注释
<meta http-equiv="refresh" content="30"> 30s/刷
注:在浏览器的页面上使用键盘上的 F12 按键开启调试模式,就可以看到组成标签。
<html> 元素是 HTML 页面的根元素
<head> 元素包含了文档的元(meta)数据,如 <meta charset="utf-8"> 定义网页编码格式为 utf-8。
<title> 元素描述了文档的标题
<body> 元素包含了可见的页面内容
<h1> 元素定义一个大标题 标题h1-h6从大到小
<p> 元素定义一个段落
HTML 链接是通过标签 <a> 来定义的. <a href="https://www.runoob.com">这是一个链接</a>
HTML 图像是通过标签 <img> 来定义的. <img src="/images/logo.png" width="258" height="39" />
<br> 换行<hr>分割线
<!-- 这是一个注释 -->注释
<meta http-equiv="refresh" content="30"> 30s/刷
注:在浏览器的页面上使用键盘上的 F12 按键开启调试模式,就可以看到组成标签。
CSS
JS
VUE前端
app前端
Android测试
安卓四大组件以及常用组建
activity
broadcast receiver
service
contentprovider
intent
handler
adb 有三部分构成 PC端client PC端service 手机adbd进程
adb xxx是adb带的命令
adb shell xxx是安卓带的
adb xxx是adb带的命令
adb shell xxx是安卓带的
adb命令
adb devices获取设备列表和状态
adb kill-server , adb start-server杀 启服务
adb logcat抓Android log
adb bugreport , 打印dumpsys、dumpstate、logcat的输出,也是用于分析错误 比较长一般定向到一个文件里面
adb install安装-r覆盖安装
adb uninstall+包名 -k删除缓存文件数据
adb pull phonepath pcpath将手机文件pull到PC上
adb push pcpath phonepath 将电脑文件push到手机
adb root获取root权限 adb remount要有root权限挂载系统文件为可读写
adb reboot重启
adb forward , 将 宿主机上的某个端口重定向到设备的某个端口 adb forward tcp:1314 tcp :8888
执行该命令后所有发往宿主机 1314 端口的消息、数据都会转发到 Android 设备的 8888 端口上,因此可以通过远程的方式控制 Android 设备。
adb forward的功能是建立一个转发,adb forward tcp:11111 tcp:22222的意思是,将PC端的11111端口收到的数据,转发给到手机中22222端口
执行该命令后所有发往宿主机 1314 端口的消息、数据都会转发到 Android 设备的 8888 端口上,因此可以通过远程的方式控制 Android 设备。
adb forward的功能是建立一个转发,adb forward tcp:11111 tcp:22222的意思是,将PC端的11111端口收到的数据,转发给到手机中22222端口
adb connect 远程连接 Android 设备 adb connect 192.168.1.102缺点是可能会慢 不稳定
adb tcpip使用非USB方式来调试设备
adb shell命令
pm Package Manager , 可以用获取到一些安装在 Android 设备上得应用信息
adb shell pm
list package -s 系统应用
-3 三方
-f:列出应用包名及对应的apk名及存放位置
pm path 列出对应包名的 .apk 位置
pm dump , 后跟包名,列出指定应用的 dump 信息,里面有各种信息,自行查看
adb shell pm dump com.tencent.mobileqq
adb shell pm dump com.tencent.mobileqq
pm install手机上装
pm clear , 清除应用数据
am
am start , 启动一个 Activity,已启动系统相机应用为例
adb shell am start -n com.android.camera/.Camera
-S先停止再启动 -W等待应用启动完成 显示时间
-a 启动默认浏览器打开一个网页 adb shell am start -a android.intent.action.VIEW -d http://testerhome.com
-S先停止再启动 -W等待应用启动完成 显示时间
-a 启动默认浏览器打开一个网页 adb shell am start -a android.intent.action.VIEW -d http://testerhome.com
adb shell am start -a android.intent.action.CALL -d tel:10086
启动拨号面板打10086
启动拨号面板打10086
am instrument , 启动一个 instrumentation , 单元测试或者 Robotium 会用到
am monitor , 监控 crash 与 ANR
am force-stop , 后跟包名,结束应用
am startservice , 启动一个服务
am broadcast , 发送一个广播
input
adb shell input text test123456 发送文本
adb shell input keyevent KEYCODE_HOME
adb shell input tap 500 500 触摸事件
adb shell input swipe 900 500 100 500 滑动事件
adb shell input swipe 500 500 501 501 2000长按
screencap
adb shell screencap -p /sdcard/screen.png
screenrecord
adb shell screenrecord sdcard/record.mp4
uiautomator
UI自动化测试
adb shell wm size 分辨率
monkey
参数略 详见稳定性测试部分
dumpsys
adb shell dumpsys display
adb shell dumpsys battery
adb shell dumpsys cpuinfo
adb shell dumpsys meminfo
adb shell dumpsys activity
adb shell dumpsys package
adb shell dumpsys notification
adb shell dumpsys wifi
adb shell dumpsys power
adb shell dumpsys telephony.registry
adb shell dumpsys display
settings
#获取当前亮度值C:\Users\Administrator>adb shell settings get system screen_brightness
#更改休眠时间,10分钟
C:\Users\Administrator>adb shell settings put system screen_off_timeout 600000
C:\Users\Administrator>adb shell settings put system screen_off_timeout 600000
getprop
查看 Android 设备的参数信息,只运行 adb shell getprop,结果以 key : value 键值对的形式显示,如要获取某个 key 的值:
adb shell getprop ro.build.version.sdk
adb shell getprop ro.build.version.sdk
linux命令
常用的 cat、cd、chmod、cp、date、df、du、grep、kill、ln、ls、lsof、netstat、ping、ps、rm、rmdir、top、touch、重定向符号 ">" ">>"、管道 "|"
专项
流量
通过读取底层文件,很多管家类就这么干的
不过有局限性没有细分
不过有局限性没有细分
proc/uid_stat{UID}/tcp_snd
proc/uid_stat{UID}/tcp_rcv
通过系统api trafficstats类的getUidRxBytes()之类方法
app内部通过代码统计接口数据量
通用的流量测试方法
手机上抓包
windows下的wireshark
linux下tcpdump
./tcpdump -s 0 -w t3.pcap -v
-s 0抓数据包默认长度是68字节 加上该参数可以抓完整数据包
-w t3.pcap 将抓取的内容保存到t1.碰擦盘文件中
-v命令执行过程中显示当前抓到的包的数量
-s 0抓数据包默认长度是68字节 加上该参数可以抓完整数据包
-w t3.pcap 将抓取的内容保存到t1.碰擦盘文件中
-v命令执行过程中显示当前抓到的包的数量
基于wifi代理的方式获取流量数据
PC开启fildder代理工具 设置PC/手机代理服务器端口
mac可以用charles
mac可以用charles
常见的流量节省方法
数据的压缩
不同数据格式的采用 JSON作为接口数据返回格式比XML小
图片webp比jpg png同等尺寸画质的情况下小
图片webp比jpg png同等尺寸画质的情况下小
控制访问频次
只获取必要的数据
缓存
针对不同网络类型设计不同的访问策略wifi 2/3G手动下图
电流
基于硬件设备的方法常见的就是假电池+电量表+上位机
借助三方软件入GT Gsam battery monitor pro/
dumpsys batterystats
弱网络
IOS 开发者里面network link conditioner 进出带宽延迟 丢包 DNS延迟
基于代理的 network delay simulator
fiddler simulate moderm speed
charles
fiddler simulate moderm speed
charles
CPU
dumpsys cpuinfo
top
通过proc/stat
内存
dumpsys meminfo package total ram free ram PSS
每隔一个小时 am dumpheap package 进程内存
每隔一个小时 am dumpheap package 进程内存
进阶 接口方法埋点回调
稳定性
monkey
自带 java写的 在/System/framework/monkey.jar
shell启动 /system/bin/monkey 见自动化
shell启动 /system/bin/monkey 见自动化
MTBF
基本路径用例封装打包7X24
模糊思路的稳定性测试
sulley框架 python写的
fuzzy Android
遍历类爬虫
appclawler
IO
流畅度 fps/丢帧
dumpsys surfaceflinger或dumpsys gfxinfo 8.0以后获得最近128帧数据计算得出 fps=帧数/耗费时间
dumpsys surfaceflinger的方式还会计算丢帧janky值
丢帧:相邻两次绘制之间的丢帧数,丢帧数越多 说明问题越严重 一般默认丢帧超过10算是严重丢帧 3帧开始能感觉卡顿
流畅度影响卡顿。这个可以简单的理解为视觉惯性和电影帧这两个方面:
1、视觉惯性
视觉预期帧率,用户潜意识里认为下帧也应该是当前帧率刷新比如一直60帧,用户潜意识里认为下帧也应该是60帧率。刷新一直是25帧,用户潜意识里认为下帧也应该是25帧率。但是刷新如果是60帧一下跳变为25帧,扰乱用户视觉惯性。这个时候就会出现用户体验的卡顿感。
2、电影帧
电影帧率(18-24),一般是24帧。电影帧单帧耗时:1000ms/24=40ms。电影帧率是一个临界点。肉眼视觉暂留大概50ms左右。低于这个帧率,人眼基本能感觉画面不连续性,也就是感觉到了卡顿
1、视觉惯性
视觉预期帧率,用户潜意识里认为下帧也应该是当前帧率刷新比如一直60帧,用户潜意识里认为下帧也应该是60帧率。刷新一直是25帧,用户潜意识里认为下帧也应该是25帧率。但是刷新如果是60帧一下跳变为25帧,扰乱用户视觉惯性。这个时候就会出现用户体验的卡顿感。
2、电影帧
电影帧率(18-24),一般是24帧。电影帧单帧耗时:1000ms/24=40ms。电影帧率是一个临界点。肉眼视觉暂留大概50ms左右。低于这个帧率,人眼基本能感觉画面不连续性,也就是感觉到了卡顿
Google Jank 计算思路:考虑视觉惯性,以硬件vsync时间间隔,连续1次vsync没有新画面刷新,则认为是一次卡顿,也就是说下一次vsync时间点没有新画面刷新,则认为是一次Jank。
PerfDog Jank 计算思路:考虑视觉惯性,假设以前三帧的平均帧耗时为参考,作为vsync时间间隔,连续两次vsync没有新渲染画面刷新,则认为是一次潜在卡顿,也就是说下一帧耗时大于前三帧平均帧耗时2倍,则认为一次潜在卡顿。同时单帧耗时满足大于两倍电影帧耗时1000ms/24*2 (由于人眼低于24帧才能辨别画面不连续性),则认为是一次真正卡顿。同时若单帧耗时大于3倍电影帧耗时,则认为是一次严重卡顿。
注解:为什么是两次vsync?GPU一般是3重缓冲buffer,当前帧已占用一个buffer,即剩余2缓冲buffer,人眼一般可容忍2帧延迟。
为什么是两帧电影帧耗时?低于24帧画面,人眼就能感知到画面不连续性,电影一般都是24帧。即电影帧耗时1000ms/24=41.67ms,两帧电影帧耗时也就是41.67ms*2,三帧电影帧耗时是41.67ms*3。
注解:为什么是两次vsync?GPU一般是3重缓冲buffer,当前帧已占用一个buffer,即剩余2缓冲buffer,人眼一般可容忍2帧延迟。
为什么是两帧电影帧耗时?低于24帧画面,人眼就能感知到画面不连续性,电影一般都是24帧。即电影帧耗时1000ms/24=41.67ms,两帧电影帧耗时也就是41.67ms*2,三帧电影帧耗时是41.67ms*3。
PerfDog Stutter 定义:测试过程中,卡顿时长的占比。即Stutter(卡顿率)=卡顿时长/总时长
PerfDog Stutter计算思路:基于PerfDog Jank的基础上,一次Jank卡顿,会有一次卡顿时间Jank time。测试过程中可能有多次Jank卡顿,即有多次卡顿时间Jank time。测试总时长为Time。
Stutter(卡顿率) = ∑Jank time / Time
说明:Jank为卡顿次数,Stutter为卡顿率,Jank和Stutter趋势有一致性,但并非完全线性,因为每次Jank卡顿严重性是不一样的。同时也说明了,没有Jank卡顿出现,自然也就卡顿率是0了
PerfDog Stutter计算思路:基于PerfDog Jank的基础上,一次Jank卡顿,会有一次卡顿时间Jank time。测试过程中可能有多次Jank卡顿,即有多次卡顿时间Jank time。测试总时长为Time。
Stutter(卡顿率) = ∑Jank time / Time
说明:Jank为卡顿次数,Stutter为卡顿率,Jank和Stutter趋势有一致性,但并非完全线性,因为每次Jank卡顿严重性是不一样的。同时也说明了,没有Jank卡顿出现,自然也就卡顿率是0了
性能-启动时间 切换时间 响应时间
方法1 高速摄像机 优点准 缺点费人力
方法2 log埋点夹板activity fully drawn time和launch time 时间差省事快捷,缺点 和肉眼体验有差异 且log因为时序的关系可能丢失或者时间不准
方法3 DV录制 工具分帧 将video拆帧用算法判断起始来算
安全
安装包测试
是否能反编译 常用dex2jar工具介乎jd-gui工具查看源代码
安装包是否签名
完整性校验
权限设置检查
敏感信息测试
数据库是否存储敏感信息
日志敏感信息
配置文件是否存在敏感信息
软键盘挟持
账户安全
密码是否明问存储在后台数据库
密码传输是否加密
账户锁定策略
同时会话
注销机制
数据通信安全
关键数据是否散列或者加密
关键连接是否使用安全通信如https ,接口设计是否内容包含敏感信息
是否对数字证书合法性进行验证
是否校验数据合法性
组件安全测试
drozer各组件是否能被外部应用恶意调用从而带来安全问题
服务端接口测试
SQL注入
CSRF跨站请求伪造
越权访问
XSS跨站脚本攻击
需求和设计本身逻辑存在的安全隐患
SQL
db三种模型 层次性 网状型 关系型二维表
两种引擎 innodb myisam
关系数据库是建立在关系模型上的。而关系模型本质上就是若干个存储数据的二维表,可以把它们看作很多Excel表 在关系数据库中,关系是通过主键和外键来维护的
两种引擎 innodb myisam
关系数据库是建立在关系模型上的。而关系模型本质上就是若干个存储数据的二维表,可以把它们看作很多Excel表 在关系数据库中,关系是通过主键和外键来维护的
操作数据库语句
DDL:Data Definition Language
DML:Data Manipulation Language
DQL:Data Query Language
SQL语言关键字不区分大小写!!!但是,针对不同的数据库,对于表名和列名,有的数据库区分大小写,有的数据库不区分大小写。同一个数据库,有的在Linux上区分大小写,有的在Windows上不区分大小写。
CREATE DATABASE name创建数据库
drop database name删除数据库
创建表 CREATE TABLE table_name (column_name column_type);
DROP TABLE table_name ;
drop database name删除数据库
创建表 CREATE TABLE table_name (column_name column_type);
DROP TABLE table_name ;
键
主键 对主键的要求,最关键的一点是:记录一旦插入到表中,主键最好不要再修改,因为主键是用来唯一定位记录的,修改了主键,会造成一系列的影响。
选取主键的一个基本原则是:不使用任何业务相关的字段作为主键。INT 21亿多数据 BIGINT922亿
因此,身份证号、手机号、邮箱地址这些看上去可以唯一的字段,均不可用作主键
常用 1.自增整数类型 2.全局GUID 通常自增类型的主键就能满足需求。我们在students表中定义的主键也是BIGINT NOT NULL AUTO_INCREMENT类型
选取主键的一个基本原则是:不使用任何业务相关的字段作为主键。INT 21亿多数据 BIGINT922亿
因此,身份证号、手机号、邮箱地址这些看上去可以唯一的字段,均不可用作主键
常用 1.自增整数类型 2.全局GUID 通常自增类型的主键就能满足需求。我们在students表中定义的主键也是BIGINT NOT NULL AUTO_INCREMENT类型
联合主键 关系数据库实际上还允许通过多个字段唯一标识记录,即两个或更多的字段都设置为主键,这种主键被称为联合主键。
对于联合主键,允许一列有重复,只要不是所有主键列都重复即可:
对于联合主键,允许一列有重复,只要不是所有主键列都重复即可:
外键
在students表中,通过class_id的字段,可以把数据与另一张表关联起来,这种列称为外键。
外键并不是通过列名实现的,而是通过定义外键约束实现的:
ALTER TABLE students
ADD CONSTRAINT fk_class_id
FOREIGN KEY (class_id)
REFERENCES classes (id);
删除外键
ALTER TABLE students
DROP FOREIGN KEY fk_class_id;
多对多关系实际上是通过两个一对多关系实现的,即通过一个中间表,关联两个一对多关系,就形成了多对多关系
在students表中,通过class_id的字段,可以把数据与另一张表关联起来,这种列称为外键。
外键并不是通过列名实现的,而是通过定义外键约束实现的:
ALTER TABLE students
ADD CONSTRAINT fk_class_id
FOREIGN KEY (class_id)
REFERENCES classes (id);
删除外键
ALTER TABLE students
DROP FOREIGN KEY fk_class_id;
多对多关系实际上是通过两个一对多关系实现的,即通过一个中间表,关联两个一对多关系,就形成了多对多关系
索引
在关系数据库中,如果有上万甚至上亿条记录,在查找记录的时候,想要获得非常快的速度,就需要使用索引。
索引是关系数据库中对某一列或多个列的值进行预排序的数据结构。通过使用索引,可以让数据库系统不必扫描整个表,而是直接定位到符合条件的记录,这样就大大加快了查询速度。
ALTER TABLE students
ADD INDEX idx_score (score);
索引的优点是提高了查询效率,缺点是在插入、更新和删除记录时,需要同时修改索引,因此,索引越多,插入、更新和删除记录的速度就越慢。
对于主键,关系数据库会自动对其创建主键索引。使用主键索引的效率是最高的,因为主键会保证绝对唯一。
在关系数据库中,如果有上万甚至上亿条记录,在查找记录的时候,想要获得非常快的速度,就需要使用索引。
索引是关系数据库中对某一列或多个列的值进行预排序的数据结构。通过使用索引,可以让数据库系统不必扫描整个表,而是直接定位到符合条件的记录,这样就大大加快了查询速度。
ALTER TABLE students
ADD INDEX idx_score (score);
索引的优点是提高了查询效率,缺点是在插入、更新和删除记录时,需要同时修改索引,因此,索引越多,插入、更新和删除记录的速度就越慢。
对于主键,关系数据库会自动对其创建主键索引。使用主键索引的效率是最高的,因为主键会保证绝对唯一。
ALTER TABLE students
ADD UNIQUE INDEX uni_name (name);
唯一索引
ADD UNIQUE INDEX uni_name (name);
唯一索引
查询
select * from 表名 *可以指定列名,多个列的话用,分隔
SELECT语句可以通过WHERE条件来设定查询条件,查询结果是满足查询条件的记录。例如,要指定条件“分数在80分或以上的学生”,写成WHERE条件就是SELECT * FROM students WHERE score >= 80
where 多个条件之间逻辑关系 AND OR NOT(not xxx=2等价 xxx<>2) 可以用()来优先处理逻辑关系)
where name LIKE ‘%啊%’通配模糊查询
WHERE score BETWEEN 60 AND 90
adb shell dumpsys display
ORDER BY 从低到高
DESC倒序 从高到低
DESC倒序 从高到低
score列有相同的数据,要进一步排序,可以继续添加列名。例如,使用ORDER BY score DESC, gender表示先按score列倒序,如果有相同分数的,再按gender列排序:
分页 LIMIT 3 OFFSET 0表示,对结果集从0号记录开始,最多取3条
SELECT COUNT(*) / 3 FROM students;
SELECT FLOOR(COUNT(*) / 3) FROM students; 地板除
SELECT CEILING(COUNT(*) / 3) FROM students;获取页数,进位除
SELECT COUNT(*) / 3 FROM students;
SELECT FLOOR(COUNT(*) / 3) FROM students; 地板除
SELECT CEILING(COUNT(*) / 3) FROM students;获取页数,进位除
聚合查询 COUNT(*)表示查询所有列的行数,要注意聚合的计算结果虽然是一个数字,但查询的结果仍然是一个二维表,只是这个二维表只有一行一列,并且列名是COUNT(*)。
通常,使用聚合查询时,我们应该给列名设置一个别名,便于处理结果:SELECT COUNT(*) num FROM students;
函数 说明
SUM 计算某一列的合计值,该列必须为数值类型
AVG 计算某一列的平均值,该列必须为数值类型
MAX 计算某一列的最大值
MIN 计算某一列的最小值
通常,使用聚合查询时,我们应该给列名设置一个别名,便于处理结果:SELECT COUNT(*) num FROM students;
函数 说明
SUM 计算某一列的合计值,该列必须为数值类型
AVG 计算某一列的平均值,该列必须为数值类型
MAX 计算某一列的最大值
MIN 计算某一列的最小值
分组聚合 group by
SELECT class_id, COUNT(*) num FROM students GROUP BY class_id;
1 班 几个 2班 几个 3班 几个 注意聚合查询的列中,只能放入分组的列。
支持多列分组
SELECT class_id, gender, COUNT(*) num FROM students GROUP BY class_id, gender;
SELECT class_id, COUNT(*) num FROM students GROUP BY class_id;
1 班 几个 2班 几个 3班 几个 注意聚合查询的列中,只能放入分组的列。
支持多列分组
SELECT class_id, gender, COUNT(*) num FROM students GROUP BY class_id, gender;
多表查询SELECT * FROM students, classes;笛卡尔积,用表名.列名方式查询,有列名重复的也可以起别名比如sid cid
SELECT
students.id sid,
students.name,
students.gender,
students.score,
classes.id cid,
classes.name cname
FROM students, classes;
也可以给表起别名简化
SELECT
s.id sid,
s.name,
s.gender,
s.score,
c.id cid,
c.name cname
FROM students s, classes c;
多表查询支持WHERE
SELECT
students.id sid,
students.name,
students.gender,
students.score,
classes.id cid,
classes.name cname
FROM students, classes;
也可以给表起别名简化
SELECT
s.id sid,
s.name,
s.gender,
s.score,
c.id cid,
c.name cname
FROM students s, classes c;
多表查询支持WHERE
连接查询 JOIN
内连接——INNER JOIN
先确定主表,仍然使用FROM <表1>的语法;
再确定需要连接的表,使用INNER JOIN <表2>的语法;
然后确定连接条件,使用ON <条件...>,这里的条件是s.class_id = c.id,表示students表的class_id列与classes表的id列相同的行需要连接;
可选:加上WHERE子句、ORDER BY等子句。
先确定主表,仍然使用FROM <表1>的语法;
再确定需要连接的表,使用INNER JOIN <表2>的语法;
然后确定连接条件,使用ON <条件...>,这里的条件是s.class_id = c.id,表示students表的class_id列与classes表的id列相同的行需要连接;
可选:加上WHERE子句、ORDER BY等子句。
RIGHT OUTER JOIN,就有LEFT OUTER JOIN,以及FULL OUTER JOIN。它们的区别是:
INNER JOIN只返回同时存在于两张表的行数据,由于students表的class_id包含1,2,3,classes表的id包含1,2,3,4,所以,INNER JOIN根据条件s.class_id = c.id返回的结果集仅包含1,2,3。
RIGHT OUTER JOIN返回右表都存在的行。如果某一行仅在右表存在,那么结果集就会以NULL填充剩下的字段。
LEFT OUTER JOIN则返回左表都存在的行。如果我们给students表增加一行,并添加class_id=5,由于classes表并不存在id=5的行,所以,LEFT OUTER JOIN的结果会增加一行,对应的class_name是NULL:
INNER JOIN只返回同时存在于两张表的行数据,由于students表的class_id包含1,2,3,classes表的id包含1,2,3,4,所以,INNER JOIN根据条件s.class_id = c.id返回的结果集仅包含1,2,3。
RIGHT OUTER JOIN返回右表都存在的行。如果某一行仅在右表存在,那么结果集就会以NULL填充剩下的字段。
LEFT OUTER JOIN则返回左表都存在的行。如果我们给students表增加一行,并添加class_id=5,由于classes表并不存在id=5的行,所以,LEFT OUTER JOIN的结果会增加一行,对应的class_name是NULL:
修改数据
C
INSERT
INSERT INTO <表名> (字段1, 字段2, ...) VALUES (值1, 值2, ...);
INSERT INTO students (class_id, name, gender, score) VALUES (2, '大牛', 'M', 80);
还可以一次性添加多条记录,只需要在VALUES子句中指定多个记录值,每个记录是由(...)包含的一组值:
INSERT INTO students (class_id, name, gender, score) VALUES
(1, '大宝', 'M', 87),
(2, '二宝', 'M', 81);
P.S.注意;代表结束,代表数据间的隔离
INSERT INTO students (class_id, name, gender, score) VALUES (2, '大牛', 'M', 80);
还可以一次性添加多条记录,只需要在VALUES子句中指定多个记录值,每个记录是由(...)包含的一组值:
INSERT INTO students (class_id, name, gender, score) VALUES
(1, '大宝', 'M', 87),
(2, '二宝', 'M', 81);
P.S.注意;代表结束,代表数据间的隔离
R
见 查询
U
UPDATE
UPDATE <表名> SET 字段1=值1, 字段2=值2, ... WHERE ...;
UPDATE students SET name='大牛', score=66 WHERE id=1;
在UPDATE语句中,更新字段时可以使用表达式。例如,把所有80分以下的同学的成绩加10分:
UPDATE students SET score=score+10 WHERE score<80;
UPDATE students SET name='大牛', score=66 WHERE id=1;
在UPDATE语句中,更新字段时可以使用表达式。例如,把所有80分以下的同学的成绩加10分:
UPDATE students SET score=score+10 WHERE score<80;
注意
要特别小心的是,UPDATE语句可以没有WHERE条件
整个表的所有记录都会被更新。所以,在执行UPDATE语句时要非常小心,最好先用SELECT语句来测试WHERE条件是否筛选出了期望的记录集,然后再用UPDATE更新
要特别小心的是,UPDATE语句可以没有WHERE条件
整个表的所有记录都会被更新。所以,在执行UPDATE语句时要非常小心,最好先用SELECT语句来测试WHERE条件是否筛选出了期望的记录集,然后再用UPDATE更新
D
DELETE
DELETE FROM <表名> WHERE ...;
DELETE FROM students WHERE id=1;
DELETE FROM students WHERE id=1;
要特别小心的是,和UPDATE类似,不带WHERE条件的DELETE语句会删除整个表的数据:
在使用MySQL这类真正的关系数据库时,DELETE语句也会返回删除的行数以及WHERE条件匹配的行数。
在使用MySQL这类真正的关系数据库时,DELETE语句也会返回删除的行数以及WHERE条件匹配的行数。
误操作回滚 传统解法
用全量备份重搭实例,再利用增量binlog备份,恢复到误操作之前的状态。然后跳过误操作的SQL,再继续应用binlog。对于DML的回滚,此法费时费力,不值得再推荐。
利用binlog2sql快速闪回
首先,确认你的MySQL server开启了binlog,设置了以下参数:
[mysqld]
server-id = 1
log_bin = /var/log/mysql/mysql-bin.log
max_binlog_size = 100M
binlog-format = row
如果没有开启binlog,也没有预先生成回滚SQL,那真的无法快速回滚了。对存放重要业务数据的MySQL,强烈建议开启binlog。
随后,安装开源工具binlog2sql。binlog2sql是一款简单易用的binlog解析工具,其中一个功能就是生成回滚SQL。
shell> git clone https://github.com/danfengcao/binlog2sql.git
shell> pip install -r requirements.txt
然后,我们就可以生成回滚SQL了。
用全量备份重搭实例,再利用增量binlog备份,恢复到误操作之前的状态。然后跳过误操作的SQL,再继续应用binlog。对于DML的回滚,此法费时费力,不值得再推荐。
利用binlog2sql快速闪回
首先,确认你的MySQL server开启了binlog,设置了以下参数:
[mysqld]
server-id = 1
log_bin = /var/log/mysql/mysql-bin.log
max_binlog_size = 100M
binlog-format = row
如果没有开启binlog,也没有预先生成回滚SQL,那真的无法快速回滚了。对存放重要业务数据的MySQL,强烈建议开启binlog。
随后,安装开源工具binlog2sql。binlog2sql是一款简单易用的binlog解析工具,其中一个功能就是生成回滚SQL。
shell> git clone https://github.com/danfengcao/binlog2sql.git
shell> pip install -r requirements.txt
然后,我们就可以生成回滚SQL了。
GUI管理 mysql workbench
命令行 因为SSH的时候基本上只能用命令行所以还是要学
create database xxx;
创建
drop database xxx;
干掉
show databases;
显示有哪些db
USE databasename;
使用指定db
USE以后SHOW TABLES;
显示数据库里的表
表 DESC 表名;
查看表结构
SHOW CREATE TABLE students;
查询创建表的语句
create table xxx
建表 后面跟一堆列名约束数据类型
drop table xxx
ALTER table xxx
修改表
ALTER TABLE students DROP COLUMN birthday;删除一列
EXIT只是退出连接
实用sql语句
插入或替换
如果我们希望插入一条新记录(INSERT),但如果记录已经存在,就先删除原记录,再插入新记录。此时,可以使用REPLACE语句,这样就不必先查询,再决定是否先删除再插入:
REPLACE INTO students (id, class_id, name, gender, score) VALUES (1, 1, '小明', 'F', 99);
若id=1的记录不存在,REPLACE语句将插入新记录,否则,当前id=1的记录将被删除,然后再插入新记录。
如果我们希望插入一条新记录(INSERT),但如果记录已经存在,就先删除原记录,再插入新记录。此时,可以使用REPLACE语句,这样就不必先查询,再决定是否先删除再插入:
REPLACE INTO students (id, class_id, name, gender, score) VALUES (1, 1, '小明', 'F', 99);
若id=1的记录不存在,REPLACE语句将插入新记录,否则,当前id=1的记录将被删除,然后再插入新记录。
插入或更新
如果我们希望插入一条新记录(INSERT),但如果记录已经存在,就更新该记录,此时,可以使用INSERT INTO ... ON DUPLICATE KEY UPDATE ...语句:
INSERT INTO students (id, class_id, name, gender, score) VALUES (1, 1, '小明', 'F', 99) ON DUPLICATE KEY UPDATE name='小明', gender='F', score=99;
若id=1的记录不存在,INSERT语句将插入新记录,否则,当前id=1的记录将被更新,更新的字段由UPDATE指定。
如果我们希望插入一条新记录(INSERT),但如果记录已经存在,就更新该记录,此时,可以使用INSERT INTO ... ON DUPLICATE KEY UPDATE ...语句:
INSERT INTO students (id, class_id, name, gender, score) VALUES (1, 1, '小明', 'F', 99) ON DUPLICATE KEY UPDATE name='小明', gender='F', score=99;
若id=1的记录不存在,INSERT语句将插入新记录,否则,当前id=1的记录将被更新,更新的字段由UPDATE指定。
插入或忽略
如果我们希望插入一条新记录(INSERT),但如果记录已经存在,就啥事也不干直接忽略,此时,可以使用INSERT IGNORE INTO ...语句:
INSERT IGNORE INTO students (id, class_id, name, gender, score) VALUES (1, 1, '小明', 'F', 99);
若id=1的记录不存在,INSERT语句将插入新记录,否则,不执行任何操作。
如果我们希望插入一条新记录(INSERT),但如果记录已经存在,就啥事也不干直接忽略,此时,可以使用INSERT IGNORE INTO ...语句:
INSERT IGNORE INTO students (id, class_id, name, gender, score) VALUES (1, 1, '小明', 'F', 99);
若id=1的记录不存在,INSERT语句将插入新记录,否则,不执行任何操作。
快照
如果想要对一个表进行快照,即复制一份当前表的数据到一个新表,可以结合CREATE TABLE和SELECT:
-- 对class_id=1的记录进行快照,并存储为新表students_of_class1:
CREATE TABLE students_of_class1 SELECT * FROM students WHERE class_id=1;
新创建的表结构和SELECT使用的表结构完全一致。
如果想要对一个表进行快照,即复制一份当前表的数据到一个新表,可以结合CREATE TABLE和SELECT:
-- 对class_id=1的记录进行快照,并存储为新表students_of_class1:
CREATE TABLE students_of_class1 SELECT * FROM students WHERE class_id=1;
新创建的表结构和SELECT使用的表结构完全一致。
强制使用指定索引
在查询的时候,数据库系统会自动分析查询语句,并选择一个最合适的索引。但是很多时候,数据库系统的查询优化器并不一定总是能使用最优索引。如果我们知道如何选择索引,可以使用FORCE INDEX强制查询使用指定的索引。例如:
> SELECT * FROM students FORCE INDEX (idx_class_id) WHERE class_id = 1 ORDER BY id DESC;
指定索引的前提是索引idx_class_id必须存在。
在查询的时候,数据库系统会自动分析查询语句,并选择一个最合适的索引。但是很多时候,数据库系统的查询优化器并不一定总是能使用最优索引。如果我们知道如何选择索引,可以使用FORCE INDEX强制查询使用指定的索引。例如:
> SELECT * FROM students FORCE INDEX (idx_class_id) WHERE class_id = 1 ORDER BY id DESC;
指定索引的前提是索引idx_class_id必须存在。
写入查询结果集
如果查询结果集需要写入到表中,可以结合INSERT和SELECT,将SELECT语句的结果集直接插入到指定表中。
例如,创建一个统计成绩的表statistics,记录各班的平均成绩:
CREATE TABLE statistics (
id BIGINT NOT NULL AUTO_INCREMENT,
class_id BIGINT NOT NULL,
average DOUBLE NOT NULL,
PRIMARY KEY (id)
);
然后,我们就可以用一条语句写入各班的平均成绩:
INSERT INTO statistics (class_id, average) SELECT class_id, AVG(score) FROM students GROUP BY class_id;
确保INSERT语句的列和SELECT语句的列能一一对应,就可以在statistics表中直接保存查询的结果:
如果查询结果集需要写入到表中,可以结合INSERT和SELECT,将SELECT语句的结果集直接插入到指定表中。
例如,创建一个统计成绩的表statistics,记录各班的平均成绩:
CREATE TABLE statistics (
id BIGINT NOT NULL AUTO_INCREMENT,
class_id BIGINT NOT NULL,
average DOUBLE NOT NULL,
PRIMARY KEY (id)
);
然后,我们就可以用一条语句写入各班的平均成绩:
INSERT INTO statistics (class_id, average) SELECT class_id, AVG(score) FROM students GROUP BY class_id;
确保INSERT语句的列和SELECT语句的列能一一对应,就可以在statistics表中直接保存查询的结果:
事务
这种把多条语句作为一个整体进行操作的功能,被称为数据库事务。数据库事务可以确保该事务范围内的所有操作都可以全部成功或者全部失败。如果事务失败,那么效果就和没有执行这些SQL一样,不会对数据库数据有任何改动。
可见,数据库事务具有ACID这4个特性:
A:Atomic,原子性,将所有SQL作为原子工作单元执行,要么全部执行,要么全部不执行;
C:Consistent,一致性,事务完成后,所有数据的状态都是一致的,即A账户只要减去了100,B账户则必定加上了100;
I:Isolation,隔离性,如果有多个事务并发执行,每个事务作出的修改必须与其他事务隔离;
D:Duration,持久性,即事务完成后,对数据库数据的修改被持久化存储。
对于单条SQL语句,数据库系统自动将其作为一个事务执行,这种事务被称为隐式事务。
要手动把多条SQL语句作为一个事务执行,使用BEGIN开启一个事务,使用COMMIT提交一个事务,这种事务被称为显式事务
可见,数据库事务具有ACID这4个特性:
A:Atomic,原子性,将所有SQL作为原子工作单元执行,要么全部执行,要么全部不执行;
C:Consistent,一致性,事务完成后,所有数据的状态都是一致的,即A账户只要减去了100,B账户则必定加上了100;
I:Isolation,隔离性,如果有多个事务并发执行,每个事务作出的修改必须与其他事务隔离;
D:Duration,持久性,即事务完成后,对数据库数据的修改被持久化存储。
对于单条SQL语句,数据库系统自动将其作为一个事务执行,这种事务被称为隐式事务。
要手动把多条SQL语句作为一个事务执行,使用BEGIN开启一个事务,使用COMMIT提交一个事务,这种事务被称为显式事务
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT; 提交//ROLLBACK回滚
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT; 提交//ROLLBACK回滚
并发可能带来的数据不一致性
脏读
不可重复读
幻读
SQL标准定义了4种隔离级别,分别对应可能出现的数据不一致的情况:
Isolation Level 脏读(Dirty Read) 不可重复读(Non Repeatable Read) 幻读(Phantom Read)
Read Uncommitted Yes Yes Yes
Read Committed - Yes Yes
Repeatable Read - - Yes
Serializable - - -
Isolation Level 脏读(Dirty Read) 不可重复读(Non Repeatable Read) 幻读(Phantom Read)
Read Uncommitted Yes Yes Yes
Read Committed - Yes Yes
Repeatable Read - - Yes
Serializable - - -
Read Uncommitted是隔离级别最低的一种事务级别。在这种隔离级别下,一个事务会读到另一个事务更新后但未提交的数据,如果另一个事务回滚,那么当前事务读到的数据就是脏数据,这就是脏读(Dirty Read)。
在Read Uncommitted隔离级别下,一个事务可能读取到另一个事务更新但未提交的数据,这个数据有可能是脏数据
在Read Uncommitted隔离级别下,一个事务可能读取到另一个事务更新但未提交的数据,这个数据有可能是脏数据
Read Committed隔离级别下,一个事务可能会遇到不可重复读(Non Repeatable Read)的问题
不可重复读是指,在一个事务内,多次读同一数据,在这个事务还没有结束时,如果另一个事务恰好修改了这个数据,那么,在第一个事务中,两次读取的数据就可能不一致。
不可重复读是指,在一个事务内,多次读同一数据,在这个事务还没有结束时,如果另一个事务恰好修改了这个数据,那么,在第一个事务中,两次读取的数据就可能不一致。
在Repeatable Read隔离级别下,一个事务可能会遇到幻读(Phantom Read)的问题。
幻读是指,在一个事务中,第一次查询某条记录,发现没有,但是,当试图更新这条不存在的记录时,竟然能成功,并且,再次读取同一条记录,它就神奇地出现了。
幻读就是没有读到的记录,以为不存在,但其实是可以更新成功的,并且,更新成功后,再次读取,就出现了。
幻读是指,在一个事务中,第一次查询某条记录,发现没有,但是,当试图更新这条不存在的记录时,竟然能成功,并且,再次读取同一条记录,它就神奇地出现了。
幻读就是没有读到的记录,以为不存在,但其实是可以更新成功的,并且,更新成功后,再次读取,就出现了。
Serializable是最严格的隔离级别。在Serializable隔离级别下,所有事务按照次序依次执行,因此,脏读、不可重复读、幻读都不会出现。
虽然Serializable隔离级别下的事务具有最高的安全性,但是,由于事务是串行执行,所以效率会大大下降,应用程序的性能会急剧降低。如果没有特别重要的情景,一般都不会使用Serializable隔离级别。
虽然Serializable隔离级别下的事务具有最高的安全性,但是,由于事务是串行执行,所以效率会大大下降,应用程序的性能会急剧降低。如果没有特别重要的情景,一般都不会使用Serializable隔离级别。
如果没有指定隔离级别,数据库就会使用默认的隔离级别。在MySQL中,如果使用InnoDB,默认的隔离级别是Repeatable Read。
锁
乐观锁
悲观锁
安全注入
防止SQL注入,我们需要注意以下几个要点:
1.永远不要信任用户的输入。对用户的输入进行校验,可以通过正则表达式,或限制长度;对单引号和 双"-"进行转换等。
2.永远不要使用动态拼装sql,可以使用参数化的sql或者直接使用存储过程进行数据查询存取。
3.永远不要使用管理员权限的数据库连接,为每个应用使用单独的权限有限的数据库连接。
4.不要把机密信息直接存放,加密或者hash掉密码和敏感的信息。
5.应用的异常信息应该给出尽可能少的提示,最好使用自定义的错误信息对原始错误信息进行包装
6.sql注入的检测方法一般采取辅助软件或网站平台来检测,软件一般采用sql注入检测工具jsky,网站平台就有亿思网站安全平台检测工具。MDCSOFT SCAN等。采用MDCSOFT-IPS可以有效的防御SQL注入,XSS攻击等。
1.永远不要信任用户的输入。对用户的输入进行校验,可以通过正则表达式,或限制长度;对单引号和 双"-"进行转换等。
2.永远不要使用动态拼装sql,可以使用参数化的sql或者直接使用存储过程进行数据查询存取。
3.永远不要使用管理员权限的数据库连接,为每个应用使用单独的权限有限的数据库连接。
4.不要把机密信息直接存放,加密或者hash掉密码和敏感的信息。
5.应用的异常信息应该给出尽可能少的提示,最好使用自定义的错误信息对原始错误信息进行包装
6.sql注入的检测方法一般采取辅助软件或网站平台来检测,软件一般采用sql注入检测工具jsky,网站平台就有亿思网站安全平台检测工具。MDCSOFT SCAN等。采用MDCSOFT-IPS可以有效的防御SQL注入,XSS攻击等。
Linux
目录介绍
bin存放二进制可执行文件 sbin要root才能访问 s是super
etc存放系统配置文件
usr (unix shared resources)用于存放共享的系统资源
home 存放用户文件的根目录
root 超级用户目录
dev (devices)用于存放设备文件
lib (library)存放跟文件系统中的程序运行所需要的共享库及内核模块
mnt (mount)系统管理员安装临时文件系统的安装点
boot 存放用于系统引导时使用的各种文件
tmp (temporary)用于存放各种临时文件
var (variable)用于存放运行时需要改变数据的文件
proc 系统内存的映射目录,提供内核与进程信息
常用文件命令
cd 切换目录
pwd显示当前目录
touch 1.txt创建新文件
mkdir 创建一个空的新目录 -p mkidr -p dir1/dir2/dir3/ 创建多级目录,父目录不存在情况下先生成父目录
cp复制文件或目录 cp -r递归处理,将指定目录下的文件与子目录一并拷贝
mv dir1 dir2 移动文件或目录、文件或目录改名
rm 1.txt 删除文件
rm -rf dir1
r同时删除该目录下的所有文件,
f强制删除文件或目录
r同时删除该目录下的所有文件,
f强制删除文件或目录
rmdir dir1 删除空目录
cat 1.txt 显示文本文件内容
more 1.txt
分页显示文本文件内容,可前后翻页,空格向后,b向前
分页显示文本文件内容,可前后翻页,空格向后,b向前
less 1.txt
分页显示文本文件内容,可前后翻页,空格向后,b向前,支持底行模式(后面介绍)
分页显示文本文件内容,可前后翻页,空格向后,b向前,支持底行模式(后面介绍)
head 1.txt 查看文本开头部分,默认十行 -20 show20行
tail 1.txt 查看文本结尾部分,默认十行 -20结尾20行
tail -f 1.txt 循环滚动读取文件并动态显示在屏幕上,根据文件属性追踪
tail -F 1.txt 循环滚动读取文件并动态显示在屏幕上,文件文件名追踪
tail -f 1.txt 循环滚动读取文件并动态显示在屏幕上,根据文件属性追踪
tail -F 1.txt 循环滚动读取文件并动态显示在屏幕上,文件文件名追踪
wc 1.txt
统计文本的行数-l、字数-w、字符数-m
统计文本的行数-l、字数-w、字符数-m
find / -name 1.txt 在文件系统中的指定目录下查找指定的文件
grep aaa 1.txt 在指定文件中查找包含指定内容的行,例:在1.txt中查找包含aaa的所有行
ln 1.txt 1_bak.txt 建立链接文件,硬链接目录可以
-s
ln -s 1.txt 1_bak.txt 对源文件建立符号连接,而非硬连接
-s
ln -s 1.txt 1_bak.txt 对源文件建立符号连接,而非硬连接
常用系统命令
top 显示当前系统中耗费资源最多的进程
date 显示系统当前时间
ps较少单独使用,配参数根据需求,ps -ef 或者ps-aux
-e /-A ps -e 显示所有进程,环境变量
ps -ef 全格式显示
ps -a 显示所有用户的所有进程(包括其它用户)
ps -au 按用户名和启动时间的顺序来显示进程
-x ps -aux 显示无控制终端的进程
-e /-A ps -e 显示所有进程,环境变量
ps -ef 全格式显示
ps -a 显示所有用户的所有进程(包括其它用户)
ps -au 按用户名和启动时间的顺序来显示进程
-x ps -aux 显示无控制终端的进程
kill -9 pid 强制杀死一个进程
df 显示文件系统磁盘空间的使用情况
-h 以人类可读的方式显示,Kb,Mb,GB等
-h 以人类可读的方式显示,Kb,Mb,GB等
du显示指定的目录及其子目录已使用的磁盘空间的总和
du -s * 进显示指定目录的总和,*当前目录下表示所有
du -sh * 以人类可读的方式显示,Kb,Mb,GB等
du -s * 进显示指定目录的总和,*当前目录下表示所有
du -sh * 以人类可读的方式显示,Kb,Mb,GB等
free 显示当前内存和交换空间的使用情况
ifconfig 网卡网络配置,常用于查看当前IP地址
ifconfig eth0 192.168.12.22 临时修改系统IP(重启后失效)
ifconfig eth0 192.168.12.22 临时修改系统IP(重启后失效)
netstat -a更详细 -nu udp -apu dup端口号使用状况 -i显示网卡列表 -s网络统计信息
apt-get install
敏捷
3355
3 角色 PO SM ST
3 工件 product backlog sprint backlog 潜在可交付的产品增量PSPI DOD
5 活动
Sprint Planning(IPM)计划会
Daily Scrum Meeting(Standup)每日站会
Sprint Review(Showcase):Sprint评审会议发生在Sprint将要结束的时候。团队和客户一起评审本次Sprint的产出是否达到预期。
Retrospective:回顾会议发生在Sprint的最后,由Scrum Master负责召集团队召开。会中大家回顾和小结这个Sprint做的好的地方以及有哪些不足。保证团队能够持续改进,不断提高。
Backlog Refinement:Product Backlog的梳理,可以发生在整个Scrum周期的任何时间。
Sprint Planning(IPM)计划会
Daily Scrum Meeting(Standup)每日站会
Sprint Review(Showcase):Sprint评审会议发生在Sprint将要结束的时候。团队和客户一起评审本次Sprint的产出是否达到预期。
Retrospective:回顾会议发生在Sprint的最后,由Scrum Master负责召集团队召开。会中大家回顾和小结这个Sprint做的好的地方以及有哪些不足。保证团队能够持续改进,不断提高。
Backlog Refinement:Product Backlog的梳理,可以发生在整个Scrum周期的任何时间。
5 价值观
勇气:有勇气去面对各种挑战。
专注:每个迭代只专注于该迭代要完成的事情。团队和个人的能力、精力是有限的,在有限的时间内专注于最有价值的事情,以取得好的结果。
承诺:作为一个自组织团队,在迭代开始的时候做出承诺,并在迭代中全力完成。
尊重:团队是能随时沟通,并且相互理解的。
公开:团队所有的进展、问题、阻碍都是对所有人可视化、透明的。这样的团队才能彼此尊重,同时也能随时暴露问题
勇气:有勇气去面对各种挑战。
专注:每个迭代只专注于该迭代要完成的事情。团队和个人的能力、精力是有限的,在有限的时间内专注于最有价值的事情,以取得好的结果。
承诺:作为一个自组织团队,在迭代开始的时候做出承诺,并在迭代中全力完成。
尊重:团队是能随时沟通,并且相互理解的。
公开:团队所有的进展、问题、阻碍都是对所有人可视化、透明的。这样的团队才能彼此尊重,同时也能随时暴露问题
敏捷的精髓
提出了Scrum的本质上有三个拆分和两个优化。
Scrum的三个拆分
拆分组织:把组织拆分成小规模、跨职能的自组织团队。
拆分产品:把工作拆分成一系列小而具体的交付物,按优先级排序,估算每项任务的相对工作量。
拆分时间:把时间拆分成固定大小的短迭代(通常为1~4周),在每个迭代结束时对可交付的产品增量进行演示。Scrum的两个优化优化商业价值:在每个迭代结束后跟客户一起检查发布目标,并据此优化发布计划,更新产品代办事项列表的优先级。优化流程:每个迭代结束后进行回顾,对团队的实践过程做优化。
Scrum的两个优化
优化商业价值:在每个迭代结束后跟客户一起检查发布目标,并据此优化发布计划,更新产品代办事项列表的优先级。
优化流程:每个迭代结束后进行回顾,对团队的实践过程做优化。
提出了Scrum的本质上有三个拆分和两个优化。
Scrum的三个拆分
拆分组织:把组织拆分成小规模、跨职能的自组织团队。
拆分产品:把工作拆分成一系列小而具体的交付物,按优先级排序,估算每项任务的相对工作量。
拆分时间:把时间拆分成固定大小的短迭代(通常为1~4周),在每个迭代结束时对可交付的产品增量进行演示。Scrum的两个优化优化商业价值:在每个迭代结束后跟客户一起检查发布目标,并据此优化发布计划,更新产品代办事项列表的优先级。优化流程:每个迭代结束后进行回顾,对团队的实践过程做优化。
Scrum的两个优化
优化商业价值:在每个迭代结束后跟客户一起检查发布目标,并据此优化发布计划,更新产品代办事项列表的优先级。
优化流程:每个迭代结束后进行回顾,对团队的实践过程做优化。
自动化框架
X驱动
TDD测试驱动开发 一般用在单元测试里 用于验证类和方法是否可以正确工作
BDD/ATDD基于验收的驱动开发BDD是ATDD的实例化 B一般是一个具体的原子性小需求
DDT 数据驱动测试 python可以用ddt装饰器的方式来实现,比如一个方法验证一次数据输入,DDT以后可以大批量数据输入
UI自动化框架
selenium web前端测试框架 webdriver
Android monkey 伪随机事件流 adb shell monkey -p -v-v-v 500
Android monkeyrunner 已经过时了 python 基于坐标点击
Android uiautomator1/2 uiobject uidevice uiwatcher基于控件操作断言
espresso
appium hybrid native+webview
robotframework
instrumentation
xUnit
testcase testfixture testrunner testsuite
testcase testfixture testrunner testsuite
python unittest pytest httprunner
junit testng
好的单元测试应该符合AIR原则
A:Automatic(自动化)
I:Independent(独立性)
R:Repeatable(可重复)
【强制】单元测试应该是全自动执行的,并且非交互式的。测试用例通常是被定期执行的,执行过程必须完全自动化才有意义。输出结果需要人工检查的测试不是一个好的单元测试。单元测试中不准使用System.out来进行人肉验证,必须使用assert来验证。
【强制】保持单元测试的独立性。为了保证单元测试稳定可靠且便于维护,单元测试用例之间决不能互相调用,也不能依赖执行的先后次序。 反例:method2需要依赖method1的执行,将执行结果作为method2的输入。
【强制】单元测试是可以重复执行的,不能受到外界环境的影响。 说明:单元测试通常会被放到持续集成中,每次有代码check in时单元测试都会被执行。如果单测对外部环境(网络、服务、中间件等)有依赖,容易导致持续集成机制的不可用。 正例:为了不受外界环境影响,要求设计代码时就把SUT的依赖改成注入,在测试时用spring 这样的DI框架注入一个本地(内存)实现或者Mock实现。
【强制】对于单元测试,要保证测试粒度足够小,有助于精确定位问题。单测粒度至多是类级别,一般是方法级别。 说明:只有测试粒度小才能在出错时尽快定位到出错位置。单测不负责检查跨类或者跨系统的交互逻辑,那是集成测试的领域。
【强制】核心业务、核心应用、核心模块的增量代码确保单元测试通过。 说明:新增代码及时补充单元测试,如果新增代码影响了原有单元测试,请及时修正。
【强制】单元测试代码必须写在如下工程目录:src/test/java,不允许写在业务代码目录下。 说明:源码编译时会跳过此目录,而单元测试框架默认是扫描此目录。
【推荐】单元测试的基本目标:语句覆盖率达到70%;核心模块的语句覆盖率和分支覆盖率都要达到100% 说明:在工程规约的应用分层中提到的DAO层,Manager层,可重用度高的Service,都应该进行单元测试。
【推荐】编写单元测试代码遵守BCDE原则,以保证被测试模块的交付质量。
⚫ B:Border,边界值测试,包括循环边界、特殊取值、特殊时间点、数据顺序等。
⚫ C:Correct,正确的输入,并得到预期的结果。
⚫ D:Design,与设计文档相结合,来编写单元测试。
⚫ E:Error,强制错误信息输入(如:非法数据、异常流程、业务允许外等),并得到预期的结果。
⚫ B:Border,边界值测试,包括循环边界、特殊取值、特殊时间点、数据顺序等。
⚫ C:Correct,正确的输入,并得到预期的结果。
⚫ D:Design,与设计文档相结合,来编写单元测试。
⚫ E:Error,强制错误信息输入(如:非法数据、异常流程、业务允许外等),并得到预期的结果。
【推荐】对于数据库相关的查询,更新,删除等操作,不能假设数据库里的数据是存在的,或者直接操作数据库把数据插入进去,请使用程序插入或者导入数据的方式来准备数据。 反例:删除某一行数据的单元测试,在数据库中,先直接手动增加一行作为删除目标,但是这一行新增数据并不符合业务插入规则,导致测试结果异常。
【推荐】和数据库相关的单元测试,可以设定自动回滚机制,不给数据库造成脏数据。或者对单元测试产生的数据有明确的前后缀标识。 正例:在阿里巴巴企业智能事业部的内部单元测试中,使用ENTERPRISE_INTELLIGENCE _UNIT_TEST_的前缀来标识单元测试相关代码。
【推荐】对于不可测的代码在适当的时机做必要的重构,使代码变得可测,避免为了达到测试要求而书写不规范测试代码。
【推荐】在设计评审阶段,开发人员需要和测试人员一起确定单元测试范围,单元测试最好覆盖所有测试用例(UC)。
【推荐】单元测试作为一种质量保障手段,在项目提测前完成单元测试,不建议项目发布后补充单元测试用例。
【参考】为了更方便地进行单元测试,业务代码应避免以下情况:
⚫ 构造方法中做的事情过多。
⚫ 存在过多的全局变量和静态方法。
⚫ 存在过多的外部依赖。
⚫ 存在过多的条件语句。
说明:多层条件语句建议使用卫语句、策略模式、状态模式等方式重构。
⚫ 构造方法中做的事情过多。
⚫ 存在过多的全局变量和静态方法。
⚫ 存在过多的外部依赖。
⚫ 存在过多的条件语句。
说明:多层条件语句建议使用卫语句、策略模式、状态模式等方式重构。
【参考】不要对单元测试存在如下误解:
⚫ 那是测试同学干的事情。本文是开发手册,凡是本文内容都是与开发同学强相关的。
⚫ 单元测试代码是多余的。系统的整体功能与各单元部件的测试正常与否是强相关的。
⚫ 单元测试代码不需要维护。一年半载后,那么单元测试几乎处于废弃状态。
⚫ 单元测试与线上故障没有辩证关系。好的单元测试能够最大限度地规避线上故障。
⚫ 那是测试同学干的事情。本文是开发手册,凡是本文内容都是与开发同学强相关的。
⚫ 单元测试代码是多余的。系统的整体功能与各单元部件的测试正常与否是强相关的。
⚫ 单元测试代码不需要维护。一年半载后,那么单元测试几乎处于废弃状态。
⚫ 单元测试与线上故障没有辩证关系。好的单元测试能够最大限度地规避线上故障。
接口自动化
postman/Charles半自动
jmeter半自动
python request自动化构造
Android CTS/VTS
基于图像对比的框架
sikuli airtest
收藏
0 条评论
下一页
为你推荐
查看更多