近期面试, 遇到的一些有意思的问题

问题一: 如果你测试的是B站的弹幕, 你发送的弹幕, 接口中看到有, 但是页面上没有显示,是什么原因呢?

我的回答: 首先, 是不是弹幕的屏蔽功能, 弹幕可以屏蔽关键字的.
第二, 有没有可能输入的是特殊的字符, 但是APP 端没有能够显示, 类似QT5 中的 把字符翻译的那个函数, 没有能把字符翻译出来.
第三, 看看是不是B站个人用户发送的弹幕因为不文明而被举报了,导致了输入的弹幕没有显示, 可能弹幕的会有对应的字段来让APP端判断这个json 是不是符合规定.
第四, 可能是输入的这个弹幕因为某些不文明的发言, 被举报了, 但是没有举报成功, 但是APP端对 数据的筛选条件 应该用 and, 反而用了一个 or , 导致了这个弹幕不可见.
第五, 数据的筛选和调度, 本身就应该是后台做的事情,但是后台把被用户举报了的, 应该筛选掉的数据放了出来, 因此APP 端拿到了数据,但是APP端做了对数据的二次校验, 从而导致的APP上, 该弹幕不可见.
当时面试的现场, 没有答得这么多, 回来后才想到这么多, 因此当时面试的那个公司没有过.

问题二: git 常用命令

现场是没有答出来, 只记得 add, comit , push 以及如果遇到版本冲突, 先 pull, 解决冲突后, 再 push , 被问到git 创建分支, 和查看当前分支的命令, 一时间没有答出来.
git remote 仓库别名 远端仓库地址 给仓库起别名
git branch 查看当前的分支情况
git branch master_name 建立新的分支
git checkout master_name 切换到其他分支
git merge master_name 合并分支的指令

问题三: 查看服务器端口和进程的命令

查看进程 ps -aux , 查看端口号 netstat -tnlp
我当时就只记得查看进程的, 查看端口的忘了

问题四:性能测试的时候, 使用什么工具进行监控?

windows server 上, 直接使用 性能监视器 即可, linux 上 使用 mpstat 即可, mpstat -P ALL 1 > cpu.log

问题五: 协程,线程, 进程之间的区别是什么?

协程是微线程, 实际上只是通过单线程的任务切换 模拟多线程.
线程 和 进程相比较协程, 会消耗服务器的资源.
怎么用通俗的话去理解进程和线程呢?
进程, 就是好比我电脑上, 开启了微信, Word , 和 pycharm , 这些一直在运行的就是进程;
而线程, 就是 我在一台电脑上部署了脚本, 另一台 电脑上也部署了一样的脚本, 多台电脑之间通过分布式工具去统一调度, 这就是多线程.
线程和进程之间, 不是一定线程多就性能更好, 或者进程更多, 性能更好, 而是之间有一个换算比例的.
比如我一台电脑要执行2000个线程, 那么这个电脑的配置可能会撑不住, 但如果我把这一台电脑要做的事情分给两个电脑, 每个电脑就是一个进程, 再由这两个电脑去各自跑一千线程, 这样单台电脑的压力就会小很多了.
当时在现场,我并没有答得深入.

问题六: web 测试和 APP 测试的区别是什么?

相同点

web 和 app 测试从流程上来说没有区别. 都需要经历 需求分析, 用例设计, 测试执行, 缺陷管理, 测试报告等相关流程.
web 和 app 测试的测试类型也基本相似, 都需要进行功能测试, 性能测试,安全测试, GUI 测试等等测试类型.
web是B端, app 是C端, 走的都是http 协议.

不同点

相对于 web 测试, app 测试除了要考虑基本的功能和性能外, 还要考虑手机本身固有的属性特性, 所以要在app 测试过程中还需要注意以下几个方面特性:
中断测试: 来回点, 接收短信等操作都会对 app 应用程序产生影响;
中断测试有人为的, 也有新任务中断以及意外中断等几种情况, 主要从以下几个方面来验证:a.来电中断(呼叫挂断, 被呼叫挂断, 通话挂断, 通话被挂断);
b.短信中断:接收短信, 查看短信; c. 其他中断: 蓝牙闹钟插拔数据线, 手机锁定, 手机断电, 手机问题(系统死机,重启)
app 测试还要测试新旧数据的兼容性, 避免出现空指针异常而导致的闪退现象, web 端 一般顶多出现404或者500 .
app 测试还需要测试手机端的插件或者第三方的插件, 以及各种机型的兼容性.
而web 端,更关注于样式和浏览器之间的兼容性等等.
手机用户对APP 的安装卸载操作也是需要重点测试的:a从上一个版本两个版本直接升级到最新版本;b全部安装新版本;c新版本覆盖旧版本安装;d卸载旧版本安装新版本;e卸载新版本,安装新版本

问题七: 接口测试要测试一些什么?

对必填项的校验, 必填项的数据类型;接口的安全性; 接口的加密; 缺参数,少参数; 出参和入参是否匹配; 后台是否捕获接口的异常并返回正确的提示,参数不对,不能直接报错500; 接口产生或者修改的数据在数据库里的数据是否匹配;

问题八:接口自动化测试, 怎么做?

使用场景法, 按照业务逻辑写出来, 多个业务接口组成一个业务场景.
如何确保业务场景的输出是准确的? 判断假设的预定条件, 最终产生的数据是否和这个预定条件相等, 以及对这个业务流程中每一个接口的断言,确保每一个步骤都是正确的前提下组合而出的业务场景用例.

问题九:如何保证软件的质量?

0.在需求提出来的时候, 就要尽早发现一些可能产生新旧业务逻辑的地方, 尽早提出问题, 越早发现解决的成本越低;
1.从用例的覆盖度, 尽可能齐全;
2.从自己对项目的熟悉度和掌控度来看, 在提测的时候就要重点测试模块和模块之间的耦合部分以及数据库里级联相关的数据;
3.在新功能测完之后, 要对原来的主体业务流程进行一轮回顾测试;
4.在上线之后, 快速对新增的模块和旧有的业务逻辑进行冒烟测试;

问题十: 软件的生命周期

软件生命周期是指一个计算机软件从功能确定,设计, 到开发成功投入使用,并在使用中不断地修改, 增补和完善,直到停止该软件的使用全过程.
它有以下几方面的内容:初始构思、需求分析、 功能设计、内部设计、文档计划、测试计划、文档准备、集成、测 试、维护、升级、再测 试、逐步淘汰 (phase-out)、 等等.

问题十一: 闭包

在函数嵌套的前提下, 内层函数引用了外层函数的变量(包括参数), 外层函数又把内层函数当作返回值进行返回;
这个内层函数+所引用的外层变量, 称之为”闭包”
闭包的简单示范

1
2
3
4
5
6
7
8
9
10
def test1():
num = 1
def test2():
print(num)

return test2

result = test1()
result()

闭包的应用场景

外层函数,根据不同的参数, 来生成不同作用功能的函数.
案例: 根据传递的参数不同, 生成不同的分割线函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def line_config(content, length):

def line():
print("-"*(length//2) + content + "-" *(length //2) )

return line

line1 = line_config("闭包",40)

line1()
line1()
line2 = line_config("#####",40)

line2()
line2()

闭包的注意事项(一)如果要对外层的变量进行修改, 就需要使用 nonlocal var 进行声明

如果要修改引用的外层变量, 需要使用 nonlocal var 声明, 否则会当成对闭包内新定义的变量进行修改

1
2
3
4
5
6
7
8
9
10
11
12
def test():
# 外层变量
num = 10
def test2():
# 内层变量
num = 666
print(num)

return test2

result = test()
result()

这是变量作用域的问题, 在内层函数里, 新定义的变量 num会认为是局限于test2函数里的一个新变量

所以如果要对外层的变量进行修改, 就需要使用 nonlocal var 进行声明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def test1():
a = 10
def test2():
nonlocal a
a = 11
print("修改外层变量")
print(a)

# 未修改之前
print("未修改之前" + str(a))
# 修改外层变量
test2()
# 修改之后
print("修改外层变量后" + str(a))
return test2

test1()

闭包的注意事项(二)当闭包内,引用了一个, 后期会发生变化的变量时,一定要注意

1
2
3
4
5
6
7
8
def test():
a = 1
def test2():
print(a)
a = 2
return test2
newFunc = test()
newFunc()

比如上面这里, 外层变量a 修改了数值, test2() 在 test()里面只是函数声明, 并没有调用, 所以此时的 print(a) 中的变量a 只是一个变量标识符, 在 newFunc() 调用的时候才会去找 变量a 此时存储的数值, 所以传递出去的就是最近改变的值

闭包的注意事项(三)闭包的深入用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def test():
funcs = []
for i in range(1, 4):
def test2(num):
num = 1 + i
def inner():
print(num)
return inner
funcs.append(test2(i))
return funcs
newFuncs = test()
print(newFuncs)
newFuncs[0]()
newFuncs[1]()
newFuncs[2]()

问题十二: 装饰器

装饰器的作用:
在函数名以及函数体不改变的前提下, 给一个函数附加一些额外代码, 就是给某个函数, 增加一个提前操作.
简单例子:

1
2
3
4
5
def fss():
print("发说说")

def ftp():
print("发图片")

此时, 想要在现有的功能上,新增一个登陆验证的功能, 要怎么做呢?
方法1. 在业务逻辑代码里修改? 增加一个登陆验证操作.
这样子做的坏处是: 因为业务逻辑代码非常多, 就造成了, 每一份,逻辑代码, 在调用具体的功能函数之前都需要做一个验证操作, 代码冗余度比较大, 复用性也会变差, 代码的可维护性比较差.
方法2:直接在功能函数里面去修改, 方便代码的重用
这样子虽然是解决了一些冗余的问题, 但是依然有不足, 就是违背了单一职责的思想, 所谓的单一原则就是每个方法只做单一的功能, 而不是把其他的功能也做了.
同时 也是违背了”开放封闭”思想, 这是面向对象的重要思想, 有两点:1.已经写好的代码,尽可能不要修改 2. 如果想要新增功能, 可以在原有代码基础上,单独进行扩展

此时就可以引出装饰器了, 需要借助闭包

1
2
3
4
5
6
7
8
9
10
def check_login(func):
def inner():
print("测试登陆")
func()
return inner

def say_word():
print("发说说")

say_word = check_login(say_word)

这就是装饰器的最原始使用, 没有使用语法糖的情况下. check_login 就是 say_word的装饰器, 这就是装饰器的设计原理,借助于闭包,在不修改原来的函数功能的情况下, 给它增加了一些功能.需要自己手动 say_word = check_login(say_word) 这样来使用装饰器.

在python中, 有语法糖来实现装饰器, 语法糖就是简单的快捷方法

1
2
3
4
5
6
7
8
9
def check_login(func):
def inner():
print("测试登陆")
func()
return inner
@check_login
def say_word():
print("发说说")

我们直接用 @func 就可以实现装饰器的调用

装饰器的注意事项之带参数的装饰器

如何给装饰器添加参数?就是在装饰器的外层再做一个闭包,因为不能直接在装饰器里有多个参数;

1
2
3
4
5
6
7
8
9
10
11
12
13
def get_zsq(char):
def zsq(func):
def inner():
print(char * 10)
func()
return inner
return zsq

@get_zsq("$") #语法糖, 相当于 fn_num = zsq(fn_num), 而且装饰器语法糖不支持多个传参, 会报错
def fn_num():
print("123")

fn_num()

装饰器的叠加, 就是使用了多个装饰器

从上到下装饰, 从下到上执行

装饰器的注意事项之被装饰的函数带参数怎么办?用不定参数解决

装饰器一般是用来装饰多个函数的, 如果不同的函数 , 它们的参数长度本身就不一定, 怎么办?
可以用不定参数解决, *args 就是把传递进来的实参打包成一个元祖来使用,**kwargs 打包关键字参数成dict给函数体调用.
在接收到参数后, 在闭包里, 要怎么拆包呢?元祖拆包 *args,而字典是直接 kwargs 就可以使用了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def zsq(func):
def inner(*args, **kwargs):
print("_" * 30)
print(args, kwargs)
func(*args, **kwargs)
return inner

@zsq
def pnum(num, nm2, num3):
print(num, num2, num3)

@zsq
def pnum2(num):
print(num)

被装饰的函数有返回值怎么办?

对有返回值的函数进行装饰: 无论什么场景, 保证函数返回值一致,就是在装饰器里, 也要有返回值
装饰器的通用写法, 假如被装饰的函数没有返回值, 那么也不受任何影响, 没有返回值就是None, 有返回值就返回

def jundge_login(func):
    def inner(*args):
        #返回值
        result = func(*args)
        return result
    return inner

@jundge_login
def fss(word):
    print(word)
    # 返回值
    return word

@jundge_login
def fsp(word, news):
    print(word, news)
    return "nothing is"     
result2 = fsp("哈哈","晚上")
print(result2)
print("====")
result1 = fss("你好")
print(result1)

问题十三: 你能不能自己自定义框架

网上有成熟的开源框架, 如 pytest, unitest , 能够结合开源框架针对自己的项目需求, 进行一个二次设计, 融合自己对 mysql, docker , redis , 异步的一些使用, 让数据流和业务流结合在一起.

问题十四: 你的测试框架是怎么搭建的?

通过 pytest + request + pymysql + appium + selenium + allure 以及我自己定义的一些基类组合成的一个能做接口, 也能做app, 也可以做 web 的自动化组合框架. 采用的驱动方式是 数据驱动.
然后使用jenkins 对其进行CI 持续集成.

问题十五: 你个人的优点和缺点是什么?

优点: 1. 测试理论扎实, 测试技能过硬, 能够快速定位问题;
2.沟通表达能力强, 能够和开发以及运维妥善处理关系;
3.有一定的代码基础, 对问题的理解和剖析能从开发的思维角度去看, 也能从用户的使用角度去看, 提出一个合理的折中解决方案;
缺点: 1. 有点强迫症, 对于项目的掌控方面, 很希望自己想要的尽量都去按照自己的想法去做, 对于一些问题, 也比较较真;

问题十六: 白盒测试的拓展点

问题: 假如让你测试一个图片上传功能, 你要怎么测试?
功能上: 冒烟测试, 能不能上传, 上传成功或者失败有没有提示; 图片的大小, 尺寸, 分辨率等有没有限制;
数据库: 上传之后, 数据库里传递的是图片的二进制, 如果以二进制的数据能不能复现一个一模一样的图片出来? 图片有没有分辨率被压缩?
接口: 上传成功或者失败的时候, 返回的接口信息,对出参,入参进行测试.
从白盒测试的角度:从函数的角度去看, 图片上传成功之后, 对应的url 是什么, 上传图片, 需要一个url, 一个 二进制数据, 可能还有图片的一些名字等信息;如果再次上传一个图片的时候, 传递同样的url , 图片是被覆盖了还是覆盖失败呢?

问题十七: 软件测试工程师要具备哪些条件?

我认为有三个条件, 业务能力, 测试技能, 思维的敏捷性. 我认为这是个三角形, 缺一不可.
业务不熟悉, 测试不出什么深层次的bug, 只能测出简单的bug;
测试技能, 测试技能越强, 就可以提高自己的测试效率, mysql, linux, 脚本能力这些就可以在熟悉业务的基础上迅速定位问题;
思维的敏捷性, 能够在出现一个问题的时候, 快速联想到与其相关联的模块,并分析出极有可能出现问题的地方;

问题十八: 软件测试工程师在项目中的角色?

一个测试工程师, 最起码是 质量的把关者, 能够完成对产品质量的把控, 这是最基本的;
但是 一个经验丰富的测试工程师, 应该是产品的掌控者. 毕竟测试人员才是 这个项目的最熟悉的人. 产品经理对产品的 了解很多是基于使用层面的, 开发只是关注于代码的实现, 只有测试是 了解产品的设计和使用, 代码的实现和数据库的级联关系以及各个模块之间的耦合关系.
只有对项目熟悉到这种程度, 你才能说你对这个项目有绝对的掌控力. 这样,在提出需求的时候, 就可以尽早规避BUG, 联调的时候迅速 发现bug, 产生问题的时候,立即知道bug 出现在哪里并且还会影响到哪里.