测试基础理论
1.测试的理论知识,说一说测试用例、白盒测试、黑盒测试
答:黑盒测试:已知产品的功能设计规格,不看内部实现代码,可以进行测试证明每个功能是否符合要求。
白盒测试:已知产品的内部工作过程和代码,测试所有内部操作是否符合设计规格要求。
2.对测试的理解,测试是做什么的
答:通过人工或者自动化脚本的方式,验证系统或者软件是否能够满足规格要求。
3.系统的安全测试包括哪些方面?web安全测试包括哪些方面?
答: 系统——应用程序:区分系统中不同用户权限,是否会出现用户冲突,用户权限的改变是否会影响系统,使用时系统的端口是否被监听。数据库:系统数据是否机密,系统数据可管理性是否只有有权限的用户才能修改或者访问,系统数据可备份和恢复能力。
web——包括注册功能 登陆功能 文件上传 SQL注入攻击,XSS跨站脚本攻击
4.单元测试、集成测试、系统测试的侧重点是什么?
单元测试——是在软件开发过程中要进行的最低级别的测试活动,在单元测试活动中,软件的独立单元将在与程序的其他部分相隔离的情况下进行测试,测试重点是系统的模块,包括子程序的正确性验证等。
集成测试——也叫组装测试或联合测试。在单元测试的基础上,将所有模块按照设计要求,组装成为子系统或系统,进行集成测试。测试重点是模块间的衔接以及参数的传递等。
系统测试——也叫组装是将经过测试的子系统装配成一个完整系统来测试。它是检验系统是否确实能提供系统方案说明书中指定功能的有效方法。测试重点是整个系统的运行以及与其他软件的兼容性。
5.黑盒测试的具体方法有哪些?
答:等价类划分、边界值分析、因果图、判定表、正交表、场景法
5-1. 场景法的设计具体流程?
答:1.根据需求,描述基本流和备选流。2.根据基本流和备选流生成测试场景。3.对每个场景生成测试用例。4.确定测试用例的数据值。
5-2. 产品元素
答:结构:代码、接口、配置文件。功能、数据、接口、平台、操作、时间。
6.一条测试用例包括哪些内容?
答:用例编号、测试项描述、操作步骤、输入、预期结果、实际结果、测试结果、缺陷编号、回归测试结果、最终测试结果、测试人、测试时间、备注
7.性能测试时关注哪些参数?
答:**QPS(TPS):**每秒钟request/事务数量;并发数: 系统同时处理的request/事务数; 响应时间: 一般取平均响应时间。https://www.cnblogs.com/zuola/p/6295729.html
8.对手机的安卓系统有什么了解?
答:Android是一种基于Linux的自由及开放源代码的操作系统。主要使用于移动设备,如智能手机和平板电脑,由Google(谷歌)公司和开放手机联盟领导及开发。底层是用C++实现的,应用层用Java实现。
9.桩模块有什么作用?
答:比如对函数A做单元测试时,被测的函数单元下还包括了一个函数B,为了更好的定位错误,就要为函数B写桩,来模拟函数B的功能,保证其正确。
测试用例设计
-
双11,活动满1000-100如何测试?
1. 时间节点可以用等价类划分和边界值结合的方法测试,00:00 和24:00,开始和结束的时间 2. 商品的总价也可以用边界值测试,1000的左边界和右边界 3. 当商品总价超过2000时,是否可以满减200,满减功能是不是可以叠加 4. 从同一个商铺中选择1000的商品,和从不同的商家选择1000的商品,是否都能实现满减 5. 如果不同的店铺还有其他的打折活动,那么优先级是怎样的,是否可以共同存在,是否能给出最优方案
-
某商品在7天内如果价格产生了下调,就会给之前使用过的客户退款,如何测试?
-
时间节点,6.7.8三个不同的天数下调,是否会退款
-
如果连续两个7天都产生了下调,是否会连续退款
-
如果商品还存在其他活动不支持保价功能,是否会退款
-
如果从不同的渠道购买商品,是否都有保价功能
-
-
某电商的登录首页,怎么测试这个页面,(用户名,密码,登陆按钮)
答:功能测试。1.输入注册过的用户名和密码,点击登陆按钮,是否能够成功跳转。
2 输入正确的用户名和错误的密码,点击登陆按钮,是否能够成功跳转
3 输入未注册的用户名和密码,是否会有相应的提示未注册。
4 用户名和密码都输入为空时,是否会有提示信息,是否能成功跳转
5 是否有记住用户名的功能
6 用户名输入时带有特殊字符,如空格,会有什么处理是否有提示
7 输入密码时如果开启了大写,是否有提示
8 是否支持清空两个输入框
安全测试。1. 用户和密码是否以加密后的形式发送给服务器
2 用户名和密码的验证应该在服务器端完成
3 输入框是否屏蔽了SQL注入攻击,是否禁止输入脚本
4 密码错误的次数是否有限制
5 验证码是否随机,是否起作用
性能测试。1.打开登陆界面,需要时间是否满足需求
2 输入正确的用户名和密码后,跳转的时间是否满足需求。
3 高并发情况下多个用户同时登陆,是否能够满足
4 是否支持多用户在同一台设备登陆,是否支持一个用户多点登陆
兼容测试。1 主流的浏览器下能否显示正常已经功能正常
2 不同的平台是否能正常工作,比如Windows, Mac OS
3 移动设备上是否正常工作,比如IOS, Andriod
4 不同网络环境是否正常工作,比如WIFI 4G
-
对新闻下方的评论如何设计测试用例呢?
答:1. 评论内容是否有字数限制,当评论字数等于、超过、恰好小于字数限制时,是否能够成功发表评论
- 评论内容为空,或者特殊字符时,是否可以发表
- 评论内容中是否可以插入图片、超链接
- 评论内容中如果出现敏感词汇,是否会有提示
- 是否允许频繁发表评论
- 是否允许其他用户对评论进行转发
- 是否允许与其他用户对评论进行回复
-
如何测试出办公大楼里面人员使用andirol 系统还是 IOS 系统
-
如何测试一个纸杯
答:功能:1. 最重要的基础功能,能否装水 2. 是否可以装除了水以外的液体,比如酒精、饮料 3. 能装多少水
性能:1. 能够用来泡茶 2. 能否用来装冰块 3. 装满水过一段时间,是否会漏水,4.重量是多少
外观:1. 是否有刻度,可以让人知道装了多少水 2.图案是否美观,形状如何,是否是完整的,没有破损
安全:1. 杯子的材料是否安全 2.是不是能摔碎,对使用者产生伤害 3. 长期使用会不会产生细菌
-
一个自动贩卖机如何测试?
答: 这个可以用判定表,条件桩,动作桩分别填写,动作桩:出货、找零、不出货、退款
-
金额刚够,货区有货,是否能顺利出货
-
货区无货时,是否提示,并退钱
- 金额超出, 是否能够找零,出货
- 投入金额不足,是否进行提示,并且退钱
- 没有投入金额时,是否也能出货
- 投入金额后,想取消交易,是否可以退钱
- 如果投入的是假币,是否退钱,并且不出货
- 是否支持除了现金支付之外的支付方式
- 每次产生的付款码是不是都是随机的
-
编程语言(Python Java)
-
python 装饰器
答:当需要对一个函数再添加一些功能,又不想修改内部的代码时,可以用闭包写一个装饰器在函数前@装饰器,就会在执行函数功能之前先调用装饰器中的功能,装饰器可以复用。装饰器可以用来打印日志。
闭包:定义一个外函数,外函数的返回值是内函数的引用,外函数中的内函数使用到了外函数的参数。
def w1(func): def inner(): print("装饰器") func.() return inner @w1 def test_1(): print('---test1---') test_1.() # 装饰器 ---test1---
-
python pass函数 super函数
答:如果没有想好某个模块的具体代码,可以用pass跳过当前模块,但是后面的代码还是会执行。
super 是用来解决多重继承问题的,直接用类名调用父类方法在使用单继承的时候没问题,但是如果使用多继承,会涉及到查找顺序的问题。Python 3 可以使用直接使用 super().func 代替 super(Class, self).func
class Bird: def __init__(self): self.hungry = True def eat(self): if self.hungry: print 'Ahahahah' else: print 'No thanks!' class SongBird(Bird): def __init__(self): self.sound = 'Squawk' def sing(self): print self.song() sb = SongBird() sb.sing() # 能正常输出 sb.eat() # 报错,因为 songgird 中没有 hungry 特性 # 修改方法 class SongBird(Bird): def __init__(self): super(SongBird,self).__init__() // 查询所有的父类,直到找到所需要的属性 self.sound = 'Squawk' def sing(self): print self.song() # python2与python3之间的区别 # Python 3 可以使用直接使用 super().xxx 代替 super(Class, self).xxx
-
python代码太长换行操作
答:\
-
字符串中寻找某个子串(这个python写过啦)
s1 = "helloworld" s2 = 'low' for i in range(len(s1)-len(s2)): if s1[i:i+len(s2)] == s2: print(i)
-
Java中final关键字修饰不同内容时的含义
答:1.修饰的类不能被继承。2.修饰的方法不能被重写。3.修饰的成员变量时要赋初值,是基础类型时变成常量,是引用类型时引用的地址值不能改变,但地址所存储的内容可以变化。 -
比较两个版本号的大小(python写过啦)
a = s1.split('.') b = s2.split('.') m = len(a) n = len(b) c = list() if m-n > 0: for i in range(m-n): b.append('0') elif m-n < 0: for i in range(n-m): a.append('0') for i in range(m): c.append(int(a[i]) - int(b[i])) j = 0 while j < len(c): if c[j] == 0: j += 1 if c[j] < 0: print("a比b版本低") break else: print("a比b版本高") break
-
python的数据类型
数值型,列表,字符串,元组,集合,字典,字典的存储是无序的。
# 可以用字符串进行两个数的相加 a = 12345555 a = str(a) for i in a: print(i) # 1,2,3,4。。。。。 # 统计字符串中某个元素的出现次数 n = "111000" number = n.count("1")
-
列表去重
s = 'ajldjlajfdljfddd' # 方法1 s = sorted(set(s)) s = str(''.join(s)) # 连接符.join(列表) print(s) # 方法2 a = [] for i in s: if i not in a: a.append(i) a = sorted(a) print(''.join(a))
-
Python中的.py文件对应什么概念?可以定义类吗?
答:一个文件就是一个模块,一个模块里面可以包含某些函数和变量,也可以包含类。包含一些模块文件的目录叫做包。将数据和操作进行封装,以便于复用叫做类。
-
Python的面向对象中的静态方法,类方法和实例方法,类属性和实例属性
答:
属性:
类属性 就是给类对象中定义的属性
实例属性 对象具有的属性
私有属性 (属于对象,跟普通属性相似,只是不能通过对象直接访问)
方法:
私有方法(方法前面加两个下划线)
静态方法:不需要创建对象,可以通过类直接调用
类方法:方法中的self是类本身,调用方法时传的值也必须是类属性
实例方法:对象调用的方法
-
Python中a==b和a is b的区别?
答:对于Python对象而言,一般存在三个属性:type 类型, value 值 以及 地址id。a == b , 这是一个比较运算符,用于比较两个对象的value(值)是否相同,a is b 用于比较两个对象的物理id。
-
Python异常?try finally
# try 尝试捕捉异常,如果捕捉到异常则执行except,否则执行else, finally语句不管是否捕捉到异常都要执行 try: num = 100 print(num) except NameError as errorMsg: print('产生错误了:%s' % errorMsg) else: print('没有捕获到异常,则执行该语句') try: num = 100 print(num) except NameError as errorMsg: print('产生错误了:%s' % errorMsg) finally: print('不管是否捕获到异常,都执行') # 运行结果 # 100 # 没有捕获到异常,则执行该语句 # 100 # 不管是否捕获到异常,都执行
-
有一个rand7()函数,均匀返回1-7之间的整数,通过rand7()实现rand10()能均匀返回1-10之间的整数
#由rand7生成rand10 import random #产生随机数是1-7的均匀分布 def rand7(): return int(random.uniform(1,7)) #产生随机数1-10的均匀分布 def rand10(): x = 0 while True: #首先rand7()-1得到一个离散整数集合{0,1,2,3,4,5,6},其中每个整数的出现概率都是1/7。那么(rand7()-1)*7得到一个离散整数集合A={0,7,14,21,28,35,42},其中每个整数的出现概率也都是1/7。而rand7()得到的集合B={1,2,3,4,5,6,7}中每个整数出现的概率也是1/7。显然集合A和B中任何两个元素组合可以与1-49之间的一个整数一一对应,也就是说1-49之间的任何一个数,可以唯一确定A和B中两个元素的一种组合方式,反过来也成立。 x = (rand7()-1)*7 + rand7() # 1/7 * 1/ 7 = 1/49 x在0-49均匀分布,随机产生数 if x <= 40: # 如果已经产生了1-40的随机数,那么就停止产生 break return x % 10 + 1 # x在1-40上均匀分布,那么x%10+1就在0-10上均匀分布
-
查找一个数组中出现次数大于数组长度一半的元素,并且打印出来
from collections import Counter list_1 = [1, 1, 1, 2, 3] dict_1 = dict(Counter(list_1)) k = [k for k, v in dict_1.items() if v > (len(list_1)//2)] # 由值查键 print(k) # 方法二,不使用库 list1 = [1,1,1,1,1,3,5] a = list(set(list1)) # 去重, i = 0 while i < len(a): # 统计元素在list1中的个数,一旦大于一半的长度,就break if list1.count(a[i]) > len(list1)/2: print(a[i]) break else: i += 1
-
查找一个数组中和为9的两个元素,并且打印出
list_1 = [1, 5, 4, 3, 88, 99] list_1 = sorted(list_1) low = 0 high = len(list_1) - 1 while low < high: if list_1[low] + list_1[high] == 9: print(list_1[low], list_1[high]) break if list_1[low] + list_1[high] < 9: low += 1 else: high -= 1
-
来python中 n = n+1 和 n+= 1有什么区别
对于不可变类型来说,两者都是创建了新的对象来接受+1之后的值;对于可变类型来说n=n+1创建了一个新的对象,+= 是在原来的对象上进行+1.
a = 10 print(id(a)) # 1844735296 a = a + 1 print(id(a)) # 1844735328 b = [1, 2] print(id(b)) # 2517772483144 b += [1] print(id(b)) # 2517772483144 b = b + [1] print(id(b)) # 2517772393608
-
手写斐波那契序列
def feibo(n): a = 0 b = 1 while a<n: a,b = b,a+b print(a)
-
生成器版斐波那契
def feibo(n): a = 0 b = 1 while a<n: yield a a,b = b,a+b
-
迭代器版斐波那契
class Fibonacci(object): def __init__(self, all_num): self.all_num = all_num self.current_num = 0 # 实例属性,一个类所创建出来的实例之间并不共有这些属性 self.a = 0 self.b = 1 def __iter__(self): return self def __next__(self): if self.current_num < self.all_num: ret = self.a self.a, self.b = self.b, self.a+self.b self.current_num += 1 return ret else: raise StopIteration fibo = Fibonacci(10) for i in fibo: print(i)
-
正则化表达式,匹配出邮箱中的数字(abc_123@gmail.cn)
import re a = "abc_123@gmail.cn" b = re.findall('\d+', a)
-
多线程和多进程的编程
import threading import multiprocessing def print_a(): print(a) def print_b(): print(b) if __name__ == "__main__": thread_1 = threading.Tread(target=print_a) thread_2 = threading.Tread(target=print_b) thread_1.start() # 多线程真正被创建 thread_2.start() process_1 = multiprocessing.Process(target=print_a) process_2 = multiprocessing.Process(target=print_b) process_1.start() # 多进程真正被创建 process_2.start()
-
写随机生成一个1-100之间的数字,并写入文件
import random number = random.randint(0, 100) # 返回0-100之间的整数 number1 = random.random() # 默认产生一个0-1之间的小数 print( random.uniform(1.1,5.4) ) # 等概产生1.1 到5.4 之间的随机浮点数,区间可以不是整数 print( random.randrange(1,100,2) ) # 生成从1到100的间隔为2的随机整数 f = open("randomint.txt", "w") f.write(str(number))
-
java中equals和==的区别
答: equals比较的是变量指向的内存空间的值,=比较的是两个变量是否指向同一个内存空间。
网络通信
-
TCP三次握手,四次挥手
https://www.cnblogs.com/zmlctt/p/3690998.html
-
为什么建立协议时是三次握手,但挥手却要四次,为什么客户端在TIME_WAIT状态还需要等2MSL(2倍最大报文生存时间报文)后才能返回到CLOSED状态?
答:因为服务器收到SYN报文,它可以把应答报文ACK和自己的SYN放在一个报文里发送。但是在接收到FIN报文的时候,只能表示客户端不再发送数据了,但服务器的数据还可能没有发送完,所以ACK和FIN报文不能在一个报文里面发送,当服务器的消息也发送完毕,才可以发送FIN报文同意关闭链接。因为不能保证服务器是否接收到了ACK,如果接收到了,那么客户端就可以关闭,但是如果服务器没有收到ACK,他就会再发送一个FIN,所以需要等待时间。(2个的原因是 客户端发送一个ACK是一个,再加上如果服务器没有收到ACK还要再发一个FIN又是一个,所以是两倍的最大报文生存时间)
-
OSI的七层模型,每一层的主要任务和功能,对应的TCP四层模型,每一层对应有哪些协议
https://www.cnblogs.com/qishui/p/5428938.html
TCP四层模型以及每一层对应的协议举例:
应用层: FTP HTTP DNS SMTP
传输层 TCP UDP
网络层 IP ICMP IGMP
数据链路层 ARP RARP
IP: 在源地址和目的地址间传送数据包,不提供可靠的传输服务,不提供重发和流量控制,出错通过ICMP报告。
ICMP:网络控制报文协议,传输控制消息(网络是否不通、主机是否可达、路由是否可用等网络消息)
IGMP:网络组管理协议,用来在IP主机和与其直接相邻的组播路由器之间建立、维护组播成员关系
ARP:地址解析协议,根据IP地址获取物理地址的一个TCP/IP协议
-
TCP 的流量控制(滑动窗口协议)
cwnd:发送端窗口:拥塞窗口大小
rwnd:接收端窗口:与接受方的TCP数据报缓冲区大小相关
实际发送窗口的大小为上面两个中的最小值,也就是当发送端窗口更大时,接收方的接受能力限制发送方窗口的最大值,当接收端窗口更大时,则是网络的拥塞限制发送端窗口的最大值。
流量控制:抑制发送端的发送数据的速率,以便于接收端来得及接受(TCP使用了滑动窗口协议),利用滑动窗口机制,通过接收端返回的rwnd控制发送方的流量。TCP的窗口单位是字节,不是报文段,发送方的发送窗口不能超过接收方给出的接收窗口的数值。
-
在网页中输入一个URL,经历了怎样的过程
通过ARP广播得到DNS服务器的物理地址,先与DNS服务器链接, 进行域名解析,获取服务器的IP地址,然后TCP三次握手,发送HTTP请求给服务器,服务器将对应的资源响应给客户端,解析到网页上,TCP四次挥手断开连接。
-
HTTP的状态码
1xx:指示信息–表示请求已接收,继续处理
2xx:成功–表示请求已被成功接收、理解、接受
3xx:重定向–要完成请求必须进行更进一步的操作
4xx:客户端错误–请求有语法错误或请求无法实现
5xx:服务器端错误–服务器未能实现合法的请求
面试中经常问到的:
200 OK 请求成功。一般用于GET与POST请求 204 No Content 无内容。服务器成功处理,但未返回内容。 206 Partial Content 部分内容。服务器成功处理了部分GET请求 205 Reset Content 重置内容。清空某个表单中的所有文字 301 Moved Permanently 永久移动。 302 Found 临时移动。 303 See Other 临时重定向。但要用GET访问 304 Not Modified 未修改。客户端发送的附带条件的请求未满足 400 Bad Request 客户端请求的语法错误,服务器无法理解 401 Unauthorized 需要用户身份验证 404 Not Found 服务器无法根据客户端的请求找到资源 500 Internal Server Error 服务器内部错误,无法完成请求 502 Bad Gateway 网关、代理服务器执行请求时,接收到了一个无效的响应 504 Gateway Time-out 充当网关或代理的服务器,未及时从远端服务器获取请求 503 Service Unavailable 由于超载或系统维护 505 HTTP Version not supported 服务器不支持请求的HTTP协议的版本,无法完成处理 -
HTTP和HTTPS的区别,为什么要使用HTTPS
[外链图片转存失败(img-GpRJWq2u-1563852189940)(C:\Users\bigbrother\AppData\Roaming\Typora\typora-user-images\1556262039035.png)]
HTTP直接和TCP通信,但是HTTPS先和SSL通信,再由SSL和TCP进行通信。采用了SSL后,HTTPS就有了加密、证书和完整性保护这些功能。
HTTP协议在通信中使用未经加密的明文,另外,无法确认通信方,所以加入了的加密处理和认证机制之后的HTTP就叫做HTTPS。
-
TCP与UDP的区别
UDP(用户数据报协议) 发送方和接收方不需要先建立链接,没有客户端和服务器之分,只管发送,不管对方是否接收到,所以是不可靠的。TCP(传输控制协议)服务器和客户端必须要先建立链接,才能传输数据,并且采用应答机制,超时重发,以确保对方收到,因此更安全可靠。UDP的效率更高一些。UDP可以用于广播
-
进程与线程的区别,为什么引入线程?他们各自的特点
进程是资源分配的最小单位,线程是程序执行的最小单位。进程是一个运行的程序和这个程序占用的所有资源的总称 线程是程序的最小单位。 一个进程中可以包含多个线程,可以实现多任务。同一个进程中多个线程是共享资源的,可以节省内存,但是会有同步的问题需要解决,比如线程对同一个变量进行修改,可能存在问题。多进程中,每个进程的资源是复制的,消耗内存。
-
HTTP协议中GET和POST的区别。
GET明文发送,并且在URL中传输,POST在报文体中加密传输,GET有长度限制
-
多线程通信的方式,系统产生死锁的原因(四个必要条件)?
简单来说死锁就是,有两个线程,各自占有一定资源,但是又在同时等着对方释放了,自己才能释放资源。
1) 互斥条件:一个资源每次只能被一个进程使用。
2) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
3) 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
4) 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
-
ARP协议?
地址解析协议,根据IP地址得到物理地址的协议
-
websocket协议
属于服务端推送技术,本质上是一种应用层协议,可以实现持久连接的全双工双向通信。在建立连接之后,客户端和服务器都可以主动向对方发送或者接受数据,建立前提是需要借助HTTP协议,建立的链接会转换成websocket协议,使用标准的HTTP协议无法实现websocket,只有支持那些协议的专门浏览器才能正常工作。
-
HTTP协议的请求报文结构,请求头中有哪些首部字段,有什么作用
起始行,请求头,请求体。
host:服务器地址
user-agent:浏览器版本
accept:浏览器可以接受的文件类型
accept-encoding:优先的内容编码
accept-language:优先的自然语言
range:实体的字节范围请求
-
HTTP协议的响应体首部字段,作用
allow: 服务器支持哪些方法(get post put delete head。。。)
content-encoding:服务器对实体的主体部分选用的内容编码方式
content-type:实体主体内对象的媒体类型
content-range:告知客户端作为响应返回的实体的哪个部分符合范围请求
set-cookie:开始状态管理所使用的cookie信息
cache-control:缓存管理
server:服务器信息
-
cookie和session的区别
答:在网站中,http请求是无状态的。也就是说即使第一次和服务器连接后并且登录成功后,第二次请求服务器依然不能知道当前请求是哪个用户。cookie的出现就是为了解决这个问题,第一次登录后服务器返回一些数据(cookie)给浏览器,然后浏览器保存在本地,当该用户发送第二次请求的时候,就会自动的把上次请求存储的cookie数据自动的携带给服务器,服务器通过浏览器携带的数据就能判断当前用户是哪个了。session和cookie的作用有点类似,都是为了存储用户相关的信息。不同的是,cookie是存储在本地浏览器,而session存储在服务器。存储在服务器的数据会更加的安全,不容易被窃取。
当客户端第一次请求session对象时候,服务器会为客户端创建一 个session,并将通过特殊算法算出一个session的ID,用来标识该session对象,当浏览器下次(session继续有效时)请求别的资 源的时候,浏览器会偷偷地将sessionID放置到请求头中,服务器接收到请求后就得到该请求的sessionID,服务器找到该id的session 返还给请求者(Servlet)使用。一个会话只能有一个session对象,对session来说是只认id不认人。
点击记住密码,其实就是把用户名和密码写在cookie里面,发送cookie到服务器验证
LINUX常见命令
-
命令大全
命令 作用 ls 查看文件夹中包含的所有文件 -a 列出所有文件,包含隐藏文件 -l 显示文件详细信息 cd 切换目录 pwd 查看当前工作目录路径 mkdir 创建文件夹 rm 删除目录中的文件 -r 递归删除 rmdir 删除目录中一个或多个子目录 mv 移动文件或修改文件名 cp 复制 cat 显示整个文件,从键盘创建一个文件cat > filename, 合并 cat file1 file2 > file head 默认head命令打印文件的开头10行 which 查询可执行文件的位置,返回第一个搜索结果 locate 通过系统内建文档数据库快速找到档案 find 遍历硬盘查找文件 chomd 改变目录或文件的访问权限 tar 压缩和解压文件,tar只能用来打包,压缩和解压是调用其他的功能来完成 df 显示磁盘空间使用情况 ln 创建软链接(-sv),硬链接(-v,类似快捷方式) date 显示或设定系统的日期与时间 grep 全局正则表达式搜索,文本搜索命令 ps 用来查看当前运行的进程状态 -aux top 查看正在运行的进程的相关信息 kill 发送制定的信号到相应进程 -
Grep不区分大小写,加什么参数?
-A n --after-context显示匹配字符后n行
-B n --before-context显示匹配字符前n行
-C n --context 显示匹配字符前后n行
-c --count 计算符合样式的列数
-i 忽略大小写
-l 只列出文件内容符合指定的样式的文件名称
-f 从文件中读取关键词
-n 显示匹配内容的所在文件中行数
-R 递归查找文件夹
-
简单来说就是kill -9 可以顺利杀死进程,kill不一定能杀死进程,因为调用两个命令时,系统发送给程序的信号一个会被阻塞,一个不会被阻塞,立即执行。
kill命令执行时,系统会发送一个SIGTERM信号给对应的程序,程序收到该信号后,会有三种可能立即停止,释放一些资源再停止,继续运行,这是由于SIGTERM是可以被阻塞的,所以不一定能杀死进程。但是kill-9 系统给程序发送的信号是SIGKILL,不会被系统阻塞,因此直接杀死进程。
数据库
-
索引的优缺点,什么情况下不适合使用索引
优点:加快数据检索速度,提高对数据的访问效率,提高数据查询性能
缺点:建立索引就相当于将添加索引的字段单独再存成一张表,占用大量硬盘空间,当对数据进行增删改的时 候,更新速度也会因此变慢。
不适合使用索引:
- 频繁更新的字段不适合,表的更新速度会变慢
- 不常用的字段,增加索引只能浪费内存,提高不了查询速度
- 字段中的值如果重复性过高(比如性别),不能明显加快检索速度
-
学校中有一个年级表Grade,有两个核心字段,student_id和class_id,把年级中大于30人的班级查出来?
select class_id from Grade group by class_id where count(*) > 30;
-
MySQL的外键的作用
保持数据的一致性和完整性,学生表中有班级字段classid,班级表中也有班级字段classid,两张表通过这个字段建立外键约束,当为学生表中添加数据时,classid这个字段插入的数据就不能超过班级表中的班级字段范围。
# 创建表格时,添加外键 foreign(class_id) references class(id); # 外链到class表的id字段 # 表格已经创建好了,添加外键 alter table add foreign key(class_id) references class(id);
-
MySQL连接查询
内链接:inner join
外链接:left join(返回左边表的所有记录,如果右表不含对应的记录,则用null代替),right join
-
数据库 表student 有个字段 name字段 name=liuguoge 唯一标识id=3 修改name=guogeliu
update student set name = 'guogeliu' where id = 3
数据库基本操作
-
DDL
create table student; # 创建表student desc student; #验证创建表的结构信息 drop table student; # 删除表 alter table student rename student_2; # 表重命名 alter table student modify name varchar(20) # 修改表中name字段的类型,不能重命名 alter table student change name names varchar(20) #修改name的名字和类型 alter table student add age int(10) # 添加age字段 alter table student drop age # 删除age字段 create index name_index on student(name); # 为name字段添加索引 create index union_index on student(name,age); # 创建组合索引 create index name_index2 on student(name(10)); # 创建部分索引 drop index name_index # 删除索引
-
DML(增删改查)
insert into student(name,age)values(”xxx“,10)# 为name 和 age字段增加记录 insert into student values(”xxx“,10),(”yyy“,12) delete from student where name=“xxx“; # 删除name="xxx"的记录 update student set age = 15 where name=”xxx“; # 修改age
# 单表查询 假设表student中含有std_id,age,name,class_id select * from student order by age; # 查询所有记录,依据age排序,默认asc,desc降序 select name from student where age > 10; # 添加条件限制 select distinct name from student; # 查找不重复的姓名 select * from student where name like '张%' # 通配符 % 多个 _ 一个 select class_id,count(*) from student group by class_id # 根据班级分组,查询每个班的人数 select class_id,max(age) from student group by class_id # 查询每个班最大年龄,最小MIN select class_id,sum(age) from student group by class_id # 查询每个班级的年龄和 select class_id,avg(age) from student group by class_id # 查询每个班级的平均 select * from student limit 1 5; # 从第一条开始,显示五条记录 # union 两个查找结果的并集 union on 两个查找结果的并集去重 # 联接查询 再假设表class 中包含class_id,teacher_name字段 # 内连接,查找两个表中两个字段相等的所有记录 select * from student inner join class on student.class_id = class.class_id # 左外连接,返回左边表的所有记录,如果右边的表没有对应的记录,则用null select * from student left join class on student.class_id = class.class_id select * from student right join class on student.class_id = class.class_id
数据结构
-
排序算法的过程和复杂度,稳定性(如果a=b,a原本在b前面,排序之后如果a在b前面则稳定,否则不稳定)
冒泡 O(n^2) 稳定 选择 O(n^2) 不稳定 快速 O(n^2) 不稳定 插入排序 O(n^2) 稳定 归并排序 O(nlogn) 稳定 堆排序 O(nlogn) 不稳定 希尔排序 O(n^2) 不稳定 下面是算法的过程以及部分python实现
冒泡排序:
一次比较两个元素,如果顺序错误就交换两个元素的位置,如果顺序没有错,则继续往下遍历,从头遍历到尾,每次的排序都会将最大值或者最小值找出来。重复这样的步骤直到排序完成。下面是python的实现
def bubble_sort(list): n = len (list) for j in range (n-1): for i in range (n-j-1): if list[i] > list[i+1]: list[i],list[i+1] = list[i+1],list[i]
选择排序:
每次都找到未排序的元素中的最大值,与当前值进行交换。
def select_sort(list): for j in range(n-1): min_index = j: for i in range(j+1,n): if list[i]< list[min_index]: min_index = i list[j],list[min_index] = list[min_index],list[j]
插入排序
将第一个元素视为已经排序好,比较第二个元素和第一个元素的大小,如果比第一个小就交换位置,否则就保持不变,至此前两个元素排序完成。第三个元素依次遍历前两个元素,进行比较,如果比第二个小就交换,再和第一个比较,如果小就交换,否则保持原状。重复上面的过程,直到排序完成。
def insert_sort(list): n = len(list) for j in range(n): for i in range(j, 0, -1): if list[i] < list[i-1]: list[i], list[i-1] = list[i-1], list[i]
快速排序
以第一个元素为基准,比他小的放在他的左边,比他大的放在右边,再对左边和右边递归完成所有未排序的数字
def quick_sort(alist, first, last):
if first >= last: # 当列表中只剩下一个元素,停止递归
return
mid_value = alist[first] # 基准值被保存下来了,所以low位置上现在空了
low = first
high = last
while low < high:
# 当high对应的元素大于基准元素,让high的游标往左移动
while low<high and alist[high] >= mid_value:
high -= 1
# high 移动不满足的时候,low位置的元素等于high位置,此时high位置就空了
alist[low] = alist[high]
# 当low所对应的小于基准值,就让low右移,
while low<high and alist[low] < mid_value:
low += 1
# low移动条件不满足的时候,让high位置元素等于low元素,此时low又空了
alist[high] = alist[low]
# 从循环退出时,high=low,这个位置是空的,也就是mid_value的正确位置
alist[low] = mid_value
# 对low左边的列表快排
quick_sort(alist,first,low-1)
# 对low右边的列表快排
quick_sort(alist,low+1,last)
。。。剩下的有空再更新把。。。
-
怎么知道一个链表里有环存在
用 set 遍历链表,把节点放入set里,每次访问下个节点时,如果set长度不变,则跳出,说明有环。否则set长度+1,继续遍历。
def is_circle(list_1): list = [] cur = list_1.__head # 从头开始遍历 while cur: list.append(cur.val) if len(set(list)) < len(list): #set之后的长度如果更小,说明节点重复了,则有环 return True else: cur = cur.next # 如果set之后的长度没有变化,就往下一个遍历 return False # 如果上面的循环结束发现,长度每次都增加了,那么就返回false
-
如何确定两个排序链表是否有相同的节点
从头开始,比较两个链表的节点是不是相等,如果相等的话,就在列表中添加节点,哪一个大了,另一个就变成下一个节点
# 比较两个链表中相同位置上的元素,如果一个比另一个大,那么就把小的那个+1 def same_val(list_1,list_2): a = list() # 一个空数组用来存储相同节点 list1 = list_1.__head list2 = list_2.__head while (list1 and list2): # 遍历条件是两个节点都不指向空 if list1.val == list2.val: a.append(list1.val) list1 = list1.next list2 = list2.next if list1.val > list2.val: list2 = list2.next else: list1 = list1.next return a # 返回相同的节点
-
返回链表的倒数第k个节点
# 目前只写了笨办法,先遍历一遍,得到链表的长度,倒数第k个就是正数n-k+1个 def return_k(single_list,k): len = 0 cur = single_list.__head while cur: len = len + 1 cur = cur.next # 遍历得到链表长度 i = 0 cur = single_list.__head while i < len-k+1: cur = cur.next i += 1 # 遍历到正数第n-k+1个返回 return cur
-
单链表反转
# 1--》2 --》3 --》null # 3--》2 --》1 --》null def invert_list(single_list): head = single_list.__head if head is None: return head last = None while head: temp = head.next head.next = last last = head head = temp
-
二分查找
def binary_search(list, item): low = 0 high = len(list) - 1 while high >= low: mid = (low + high) // 2 # / 返回小数,//返回下底整数 if list[mid] == item: # list[mid]等于item的时候,就返回mid,否则就判断大小,修改low 和 high return mid if list[mid] < item: low = mid + 1 else: high = mid - 1 return None
-
栈的原理和应用:左右括号是否正确匹配的经典问题
# 是左括号的时候,就压入栈,判断栈是不是为空,不为空的时候就去比较栈顶元素和下一个元素是不是一对, # 是,就弹出栈顶元素 # 最后栈的长度如果为0,说明有效 def is_enable(s): a = list() if len(s) > 0: if len(s) % 2 != 0: return False if s[0] == '}' or s[0] == ')' or s[0] == ']': return False for i in range(len(s)-1): if s[i] == '('or s[i] == '[' or s[i] == '{': a.append(s[i]) if len(a) > 0: if a[-1] == '(' and s[i+1] == ')': a.pop() elif a[-1] == '[' and s[i+1] == ']': a.pop() elif a[-1] == '{' and s[i+1] == '}': a.pop() return (len(a) == 0)
-
合并两个有序链表
从头开始,比较两个位置上面的节点大小,让新的链表的指针指向更小的那个节点,如果有一个链表遍历到空了,那么直接让指针指向另一个链表的剩余节点。
# 1-》2-》3 # 1-》2-》4 # 1-1-2-2-3-4 def merge_lists(singlelist1,singlelist2): cur1 = singlelist1.__head cur2 = singlelsit2.__head newlist.__head = None # 创建一个新的单链表,指向空 while (cur1 and cur2): # 当两个指针都不指向空时,比较大小,相同依次链接,大小不同指针移动 if cur1.val == cur2.val: newlist.next = cur1 cur1 = cur1.next newlist.next.next = cur2 cur2 = cur2.next if cur1.val > cur2.val: newlist.next = cur2 newlist = newlist.next cur2 = cur2.next else: newlist.next = cur1 newlist = newlist.next cur1 = cur1.next if cur1 == None: # 如果链表1已经遍历完了,那就让新链表指向链表2的指针,然后把剩下的连上去 while cur2: newlist.next = cur2 cur2 = cur2.next newlist = newlist.next elif cur2 == None: # 同上 while cur1: newlist.next = cur1 cur1 = cur1.next newlist = newlist.next return newlist
-
手写冒泡
def bubble_sort(alist): '''冒泡排序''' n = len(alist) #从头走到尾n-1次 for j in range(n-1): count = 0 for i in range(0, n-1-j): # 如果i+1比i的值小,就交换,每次一把最大值冒到最后一个 # 从头走到尾,如果发现某次没有发生交换,直接结束 if alist[i] > alist[i+1]: alist[i], alist[i+1] = alist[i+1], alist[i] count += 1 if count == 0: return
-
选择排序
def select_sort(alist): '''选择排序''' n = len(alist) for j in range(0, n-1): # j 0:n-2,到了索引n-2的元素就可以不用再排了 min_index = j for i in range(j+1, n): if alist[min_index] > alist[i]: min_index = i # 找到最小值的位置,只要有元素比min_index位置上的小,就更新min_index的值,就能找到最小的索引 alist[j], alist[min_index] = alist[min_index], alist[j]
Selenium 基本操作
python的测试框架: unittest,unite,nose,pytest
unittest 单元测试框架: python中用来测试各种标准类库模块的,通过使用unittest可以创建测试用例、测试套件、测试夹具的能力。
Test Fixture:定一个在单个或者多个测试执行之前的准备工作和测试执行之后的清理工作
Test Case: 一个测试用例是在unittest中执行的最小单元。unittest中提供了Testcase的基础类,可以用来创建测试用例。通过unittest提供的assert方法来验证一组特定的操作和输入以后得到的具体响应。
assert方法: assertEqual用来校验预期结果,assertTrue用来验证条件,assertRaise用来验证预期的异常
Test Suit:多个测试用例的集合。一个测试套件内的测试用例将一起执行
Test Runner:测试执行器,负责测试执行调度并且生成测试结果给用户
Test Report: 测试报告用来展示所有执行用例的成功或者失败
在脚本中引入延时机制来使脚本的运行速度和程序的响应速度匹配。有以下两种
隐式等待时间:隐式等待对于解决由于网络延迟或利用Ajax动态加载元素所导致的程序响应时间不一致是非常有效的。driver.implicitly_wait(10),webdriver在找不到一个元素的时候,将会等待10秒,超过10秒,就会引发异常
显式等待时间:只作用于仅有同步需求的测试用例。为脚本设置一些预置条件或定制化的条件,等待条件满足后再进行下一步测试。webdriver提供了webdriverwait和expected_conditions类来实现显式等待。
element = WebDriverWait(driver,10).until(expected_conditions.\
visibility_of_element_located\ ((By.LINK_TEXT,"account")))
# 用visibility_of_element_located方法来判断目标元素是否可见,直至最大等待时间10s
-
元素定位方式(selenium webdriver)
from selenium import webdriver url = "xxxxxxxxxxxxxxxxxxxxxxxxxx" driver = webdriver.Firefox() driver.get(url) # id 定位元素 element = driver.find_element_by_id('xxx') # name 定位元素 element_1 = driver.find_element_by_name('xxx') # 类名定位元素 element_2 = driver.find_element_by_class_name('xxx') # 标签定位元素,返回符合条件的第一个 element_3 = driver.find_element_by_tag_name('input') # 超链接文本和部分文本定位 element_4 = driver.find_element_by_link_text('文本') element_5 = driver.find_element_by_partial_link_text('部分文本') # 通过Xpath定位元素 # 绝对路径,从/html开始 element_6 = driver.find_element_by_Xpath('/html/body/div/p[1]') # 相对路径 //,可以与属性相结合 element_7 = driver.find_element_by_Xpath("//input[@id='xx']") # 通过CSS选择器定位元素 element_8 = driver.find_element_by_css_selector('#id') element_9 = driver.find_element_by_css_selector('.class') element_10 = driver.find_element_by_css_selector('input/button') element_11 = driver.find_element_by_css_selector("name = 'a'") element_12 = driver.find_element_by_css_selector('p > input')
-
操作元素
上述定位到的元素具有的属性
element.size 元素的大小
element.tag_name 元素的HTML标签名称
element.text 元素的文本值
元素具有的方法
方法描述 实例 clear,清除文本框或者文本框或者文本域内容 element.clear( ) click,单击元素 element.click( ) get_attribute(name) 获取某个属性值 element.get_attribute(“value”) is_display( ) 检查元素对于用户是否可见 element.is_display( ) is_enabled( ) 检查元素是否可用 element.is_enabled( ) is_selected( ) 检查元素是否被选中,应用于复选框和单选按钮 element.is_selected( ) send_keys( *value),模拟输入文本 element.send_keys(“hello”) submit( ) 提交表单元素,如果对一个元素执行这个方法,会提交元素所属表单 element.submit( ) value_of_css_property(property_name) 获取CSS属性值 element.value_of_css_property(“backgroundcolor”) 操作下拉菜单
selenium webdriver提供了特定的Select类,实现与网页上的列表和下拉菜单的交互,具体的Select类中方法先不细写
select_element = Select(driver.find_element_by_id("select-language")) select_element.select_by_index(0) # 根据索引选择 select_element.select_by_visible_text("Chinese") # 根据目标选项的文本值 select_element.select_by_value("chinese") # 按照value属性选择
操作警告和弹出框
selenium webdriver 通过Alert类来操控JS警告。webdriver的switch_to_alert方法可以返回一个alert实例
功能/方法 示例 text 获取警告窗口的文本 alert.text accept() 接受警告,单击OK按钮 alert.accept( ) dismiss() 驳回警告 单击取消按钮 alert.dismiss() send__keys(*value) 模拟给元素信息 alert.send_keys(“foo”) alert = driver.switch_to_alert() alert_text = alert.text # 获取警告信息 alert.accept() # 接受这个警告 alert.dismiss() # 拒绝警告
-
鼠标和键盘事件
通过Action类模拟键盘或者鼠标事件
from selenium.webdriver.common.action_chains import ActionChains element = driver.find_element_by_link_text('文本') ActionChains(driver).move_to_element(element).perform() # 鼠标移动到element上
-
数据驱动测试
针对测试步骤相同而使用不同的输入值的场景,适合使用数据驱动模式。使用外部的CSV(逗号分隔文件)或者Excel表格中的数据,使得脚本参数化,这种方式可以将测试脚本与测试数据分离,使得脚本在不同数据集合下高度复用。主要使用了python ddt库。(@data, @data(get_data), @unpack)
@data('python') # 用来传入参数,相应的测试方法需要接收参数 def test_baidu_python(self, search_value): # 定位到搜索框,输入python,点击搜索 driver = self.driver driver.find_element_by_id("kw").clear() driver.find_element_by_id("kw").send_keys(search_value) driver.find_element_by_id("su").click() self.assertEqual([], self.verificationErrors) # assertEqual()校验预期结果
# 外部文件 # get_data方法调用外部CSV库 def get_data(file_name): # 创造一个空列表 rows = [] # 打开csv文件 data_file = open(file_name, 'r') # 创建一个csv阅读器 reader = csv.reader(data_file) # 跳过首行 next(reader, None) # 添加每一行的数据到rows里 for row in reader: rows.append(row) return rows @data(*get_data("data_ch8.csv")) # 使用data装饰器调用get_data方法,读取外部数据文件,逐行返回给@data @unpack def test_baidu_chrome(self, search_value, cnt): driver = self.driver driver.find_element_by_id("kw").clear() driver.find_element_by_id("kw").send_keys(search_value) driver.find_element_by_id("su").click() print(cnt) self.assertEqual([], self.verificationErrors)
-
unittest 怎么单个运行test_xx_方法
pycharm里面,鼠标移动到对应的方法上面,右键运行。如果想当前的脚本上所有的用例一起执行,只需把鼠标放到_name_ == main:这句话的后面或者下方就行了
人生理想
-
在之前的测试工作中,遇到过最棘手的bug是什么,你是怎么解决的?
-
如果开发不觉得你提出的bug需要修改,如何处理?怎么处理好和开发之间的关系?
-
如果一个产品马上就要上线了,但是还有很多bug没来得及修复,开发以及尽全力了,这个时候,你该怎么办。
- 首先向上级反映下时间和工作量,让上级了解当前进度,便于对人力和进度进行合理安排调配。
- 对软件进行分析,一般按照常用的功能,用户关注的功能,优先级高的Bug部分,与开发协调,优先修复。
- 设计测试用例更加科学,尽可能少的数量覆盖最大。
- 及时与开发人员和客户沟通,评估风险。
- 加班
-
为什么选择做测试?
软件测试是为软件产品的质量把关的,目前软件测试的工业化时代还没有来临,自动化软件测试工具还没有能统一起来的模式,大部分还是靠人工测试,所以软件测试有很大的发展空间和前景。自己本身对这个行业更有兴趣。
-
觉得自己的性格适合做测开吗?
-
以后的职业规划?
-
项目中遇到难题如何解决?
-
个人优缺点
优点:接受新知识比较快
缺点:如果有把握的事情,我可能会比较坚持自己的意见,但是如果没把握,很难自己拿决定
智商测试
-
一元钱买一瓶水,2个盖子换一瓶水。20块能买几瓶水?
答:20+10+5+2+1+1+1= 40瓶。剩下一个瓶子的时候可以借一个瓶子,再兑换一瓶水,喝完再把瓶子还掉。
-
5L和3L的水如何装出4升?
答:装满三升,倒入五升,再装满三升倒入五升的桶,装满时三升的桶里剩下一升,倒光五升的桶,把一升倒入五升的桶,再装满三升倒入五升,得到四升水。