💻 杂七杂八面试题

接口和压测工具都有哪些,如何选择?

接口测试工具:

  • Postman:适合简单接口测试,不适合复杂场景
  • SoapUI:主要用于测试Web Service接口(SOAP协议和RESTful接口)【SOAP现在用的很少】
  • JMeter:支持较多协议,但更多作为压测工具使用
  • Python/Pytest+requests:适合复杂场景,但需要一定的编程能力
  • Java/Rest-Assured:适合Java项目,但需要一定的编程能力

压测工具:(压力测试Load Testing和性能测试Performance Testing)

  • JMeter:支持较多协议,可以通过图形化界面或命令行方式执行,使用简单
  • LoadRunner:商业级性能测试工具,功能非常全面,支持广泛的协议和技术,能够模拟大规模用户并发,提供详细的性能分析报告。通常用于大型企业级应用的性能测试。
  • Locust:基于Python的开源压力测试工具,使用Python代码定义用户行为,易于编写复杂的测试场景,支持分布式压测,并提供实时的 Web UI 监控

选择因素

  • 成本/预算
  • 团队规模
  • 易用性
  • 可扩展性(集成其他工具,如监控系统、告警系统)
  • 可维护性
  • 接口类型
  • 项目需求和复杂度、系统技术栈
  • 团队技能和熟悉程度
  • 是否需要支持多环境
  • 是否需要支持多种协议
  • 是否需要支持团队协作
  • 问题定位
  • 测试报告展示

接口测试用例的编写要点有哪些?

理解需求和接口文档

覆盖各种测试场景

  • 正常场景:合法入参
  • 异常场景:非法入参、类型、长度、范围、必填
  • 边界场景:最大/最小值、临界值
  • 安全性测试:权限控制、敏感数据加密、SQL注入、XSS攻击【跨站脚本攻击,通常是注入js脚本】
  • 性能测试:大批量访问是否有性能瓶颈

关注业务逻辑

错误处理是否合理

可读性、可维护性

测试相关(参数化、数据驱动)

黑盒测试和白盒测试有哪些方法?

黑盒测试(不关注内部结构,只关注输入和输出)

  • 等价类划分
  • 边界值分析
  • 因果图
  • 决策表
  • 正交试验
  • 场景法
  • 错误推测

白盒测试(关注内部结构,关注代码逻辑)

  • 语句覆盖
  • 分支覆盖
  • 条件覆盖
  • 判定覆盖
  • 条件组合覆盖
  • 路径覆盖
  • 单元测试

Token和Session区别?

状态管理、存储位置

  • Token:无状态,服务端不保存会话状态,所有状态都保存在客户端
  • Session:有状态,服务端保存会话状态,客户端保存会话ID

可扩展性:

  • Token: 本身包含了足够的信息(例如用户身份、权限),服务器在验证时不需要查询额外的存储,因此更易于在分布式环境下进行扩展。每个独立的服务器都可以使用相同的密钥来验证Token。
  • Session:可扩展性好,可以用于分布式系统(需要使用共享储存,如Redis,Memcached)

安全性:

  • Token:主要的安全风险是Token被窃取,攻击者可以在Token过期之前冒充用户。为了提高安全性,通常会使用HTTPS、设置较短的过期时间、使用Refresh Token等机制。对于JWT,签名算法的安全性也至关重要。
  • Session:主要的安全风险是Session Hijacking和Session Fixation。为了减轻这些风险,通常会使用 HTTPS、HTTPOnly和Secure Cookie等措施。

服务器负载:

  • Token:服务器不需要存储用户的会话信息,因此可以减少服务器的负载,特别是对于大量用户的应用。
  • Session:每个用户都需要一个独立的Session,如果用户数量很多,服务器需要为每个用户维护一个Session,这将导致服务器负载增加。

过期时间:

  • Token:通常也会设置过期时间。为了在Token过期后保持用户的登录状态,通常会配合使用Refresh Token。当 Access Token 过期时,客户端可以使用Refresh Token向服务器请求新的Access Token。
  • Session:可以设置Session的过期时间,当用户长时间不活动时,Session会失效。

软件测试分为哪几个阶段?

  • 需求分析阶段
  • 测试计划阶段(编写测试计划)
  • 测试设计阶段(编写用例)
  • 测试环境准备阶段
  • 测试执行阶段
    • 单元测试
    • 集成测试
    • 系统测试
    • 验收测试(UAT)
    • 回归测试(可选)
  • 测试报告阶段

JMeter中跟踪重定向和自动重定向有什么区别?

特性 跟踪重定向 (Follow Redirects) 自动重定向 (Auto Redirects)
记录重定向请求 会记录每个重定向请求为一个独立的 SampleResult。 不会记录原始的重定向请求,只记录最终的请求。
测试结果条目 结果中会包含原始的 3xx 响应和后续的重定向请求。 结果中只包含最终的请求。
性能指标 可以查看每个重定向步骤的性能指标。 只能看到最终请求的性能指标,原始重定向的耗时包含在其中。
适用场景 需要详细分析重定向过程、验证重定向行为、查看所有相关请求。 只关心最终结果、希望测试结果简洁、将重定向视为正常行为。

http和https的区别?

核心区别:安全性【https是http的安全增强版】

  • http信息是明文传输,https信息是密文传输
  • 标准端口号:http是80,https是443
  • 证书:http不需要证书,https需要证书
  • 性能:https比http性能低
  • 成本:https比http成本高

get请求和post请求的区别?

特性 GET 请求 POST 请求
主要用途 获取资源 提交数据,创建或修改资源
数据传输位置 URL 查询参数 (Query Parameters) 请求体 (Request Body)
数据可见性 可见,安全性较低 相对不可见,安全性较高
缓存性 可以被缓存 通常不会被缓存
幂等性 幂等 通常非幂等
长度限制 受 URL 长度限制,不适合传输大量数据 限制较少,适合传输大量数据
添加到历史记录 会添加到浏览器历史记录 通常不会直接添加到浏览器历史记录
请求体 通常不包含请求体 必须包含请求体

测试用例的八大(N大)元素

  • 测试用例编号
  • 所属模块
  • 测试用例标题
  • 前置条件
  • 测试步骤
  • 预期结果
  • 实际结果
  • 优先级
  • 用例类型
  • 测试环境
  • 执行人员
  • 备注

算法题:找最小连续的子数组

算法题:一个数组,找出其中最小连续的子数组,使得这个子数组排序后,整体数组是有序的
例:数组[1,3,2,7,5,8],找到子数组[3,2,7,5],使得其排序后[2,3,5,7],整体[1,2,3,5,7,8]有序)

咋一看好像不难,写出了下面的代码:

def find_unsorted_subarray(nums):
    length = len(nums)

    if length <= 1:
        return []

    # 寻找右界idx
    right = length-1
    for i in range(length - 1, 0, -1):
        if nums[i - 1] > nums[i]:
            right = i - 1
            break
    right += 1
    print(f'right: {right}')

    # 寻找左界idx
    left = 0
    for i in range(0, length - 1):
        if nums[i] < nums[i + 1]:
            left += 1
            break
    print(f'left: {left}')

    return nums[left: right + 1]


if __name__ == "__main__":
    nums = [1,3,2,7,5,8]
    print(find_unsorted_subarray(nums))

如上,有问题:
如果数组本身就是有序的,返回有误(应该返回空数组)
只单纯的找到了起始和结束边界,但未进行扩展:考虑这样一种场景,如果子数组中的最小值比左边元素小,最大值比右边元素大,那么就不符合了,因此需要进行扩展边界

参考答案给出的代码如下:【这道题在搜索的时候,gemini和deepssek的回答多次有误,使用grok回答对了..囧😶】

def find_unsorted_subarray(nums):
    length = len(nums)
    print(f'length: {length}')

    if length <= 1:
        return []

    # 寻找左界
    left = 0
    while left < length - 1 and nums[left] <= nums[left + 1]:
        left += 1
    print(f'left: {left}, left num: {nums[left]}')

    if left == length - 1:
        return []

    # 寻找右界
    right = length - 1
    while right > 0 and nums[right] >= nums[right - 1]:
        right -= 1
    print(f'right: {right}, right num: {nums[right]}')

    # 找到子数组的最小值和最大值
    subarray_min = min(nums[left: right + 1])
    subarray_max = max(nums[left: right + 1])
    print(f'min: {subarray_min}, max: {subarray_max}')

    print(nums[left: right + 1])

    # 进行扩展:
    while left > 0 and nums[left - 1] > subarray_min:
        left -= 1
    while right < length - 1 and nums[right + 1] < subarray_max:
        right += 1

    return nums[left: right + 1]

Python的内存管理机制是什么?

1. 引用计数 (Reference Counting)

这是Python最主要的内存管理方式。
Python中的每个对象都会维护一个引用计数器,用于记录当前有多少个变量或对象引用着它。
增加引用计数的情况:

  • 创建一个新的对象并将其赋值给一个变量时,该对象的引用计数会增加。
  • 将一个对象作为参数传递给函数时,该对象在函数内部的引用计数会增加。
  • 将一个对象存储到容器(如列表、字典、元组)中时,该对象在容器中的引用计数会增加。

减少引用计数的情况:

  • 当一个变量被赋予新的值时,之前引用的对象的引用计数会减少。
  • 当一个包含对象的容器被销毁时,该容器中所有对象的引用计数都会减少。
  • 当一个函数执行完毕,函数内部使用的局部变量引用的对象的引用计数会减少。
  • 使用 del 语句显式删除一个变量时,该变量引用的对象的引用计数会减少。

优点: 简单高效,能够及时回收不再使用的内存。

缺点: 无法解决循环引用的问题。例如,两个对象互相引用,即使它们不再被其他对象引用,它们的引用计数也不会变为零,导致内存泄漏。

2. 垃圾回收 (Garbage Collection)

为了解决引用计数无法处理的循环引用问题,Python引入了垃圾回收机制。
垃圾回收器会定期扫描内存中的对象,检测是否存在循环引用,并回收这些不再使用的对象。
Python的垃圾回收机制主要基于标记-清除 (Mark and Sweep) 算法。

  • 标记阶段: 从根对象(例如全局变量、活动函数的局部变量等)开始,遍历所有可达的对象,并将它们标记为“存活”。
  • 清除阶段: 遍历整个堆内存,将没有被标记为“存活”的对象清除掉,释放其占用的内存。

为了提高垃圾回收的效率,Python还引入了分代回收 (Generational Garbage Collection) 的策略。

  • Python将内存中的对象分为三个“代”(generation):0代、1代和2代。
  • 新创建的对象属于第0代。
  • 如果在一次垃圾回收中对象仍然存活,它会被移动到下一代。
  • 垃圾回收器会更频繁地回收第0代的对象,因为新创建的对象更容易变成垃圾。而对于存活时间较长的对象(高代),回收频率会降低。
  • 这样做的目的是减少垃圾回收的开销,提高程序的整体性能。

3. 内存池机制 (Memory Pools / Object Allocator)

对于频繁使用的小块内存,Python引入了内存池机制进行优化。
Python的内存管理机制在底层会维护多个内存池,用于不同大小的对象分配。
当需要分配小于一定大小(通常是512字节)的对象时,Python会尝试从对应的内存池中获取空闲的内存块,而不是直接向操作系统申请内存。
优点:减少了频繁向操作系统申请和释放内存的开销,提高了内存分配和释放的效率。
Python的内存池机制是分层的:

  • Level 1: 用于分配小的对象,如整数、短字符串等。
  • Level 2: 用于分配中等大小的对象。
  • Level 3: 直接使用操作系统的内存分配。

总结:

  • 引用计数 是主要机制,负责大部分对象的及时回收。
  • 垃圾回收 作为补充,处理引用计数无法解决的循环引用问题,并采用分代回收优化性能。
  • 内存池机制 则针对小对象进行优化,提高内存分配效率。

TCP、UDP区别

特性 TCP UDP
连接性 连接导向 (Connection-Oriented) 无连接 (Connectionless)
可靠性 可靠传输 (Reliable Transmission) 不可靠传输 (Unreliable Transmission)
有序性 有序传输 (Ordered Delivery) 无序传输 (Unordered Delivery)
流量控制 有 (Yes) 无 (No)
拥塞控制 有 (Yes) 无 (No)
头部大小 较大 (Higher) 较小 (Lower)
传输速度 相对较慢 (Relatively Slower) 较快 (Faster)
适用场景 需要可靠数据传输的应用 对实时性要求高的应用,可容忍少量数据丢失

TCP的三次握手:

  • 第一次握手:客户端发送SYN包到服务器,等待服务器确认
  • 第二次握手:服务器收到SYN包,确认客户端的SYN,并发送SYN+ACK包到客户端
  • 第三次握手:客户端收到SYN+ACK包,确认服务器的SYN,并发送ACK包到服务器

2次不够,无法保证服务器知道客户端已接收到,也无法保证客户端知道服务器已接收到 4次不行,浪费资源(增加延迟和开销)

浏览器输入一个网址,接下来会发生什么?

  • URL解析
  • DNS查询获取目标服务器IP地址
  • 建立TCP连接(如果是HTTPS还会进行TLS/SSL握手)
  • 浏览器发送HTTP请求
  • 服务器处理请求
  • 服务器发送HTTP响应
  • 浏览器解析和渲染响应内容
  • (可能)关闭TCP连接

HTTPS数字证书的认证过程:

  • 服务器发送证书(证书信息:公钥、证书颁发机构、有效期、域名)
  • 浏览器验证证书的合法性
    • 检查证书是否由受信任的证书颁发机构(CA)签发
    • 检查证书是否过期
    • 检查证书是否与请求的域名匹配
  • 如果证书合法,浏览器生成一个随机数,并使用证书中的公钥加密该随机数
  • 浏览器将加密后的随机数发送给服务器
  • 服务器使用私钥解密得到随机数
  • 浏览器和浏览器使用该随机数进行对称加密通信

OSI7层网络模型

① 物理层:
负责在物理介质上传输原始的比特流(0和1)。它定义了物理连接的特性,如电缆类型、接口规范、信号传输速率等。

  • 以太网、WiFi、蓝牙、USB..

② 数据链路层:
负责在直接相连的两个节点之间提供可靠的数据传输。它将物理层提供的比特流组织成帧(frame),并处理MAC地址寻址、错误检测和纠正等。

  • 以太网中的MAC子层协议、点对点PPP、虚拟局域网VLAN、地址解析一些ARP

③ 网络层:
负责在源主机和目标主机之间提供逻辑寻址和路由选择。它定义了IP地址,并负责将数据包从源网络路由到目标网络。

  • 互联网协议IP、互联网控制报文协议ICMP、路由协议

④ 传输层:
负责在应用程序之间提供端到端的可靠或不可靠的数据传输服务。它处理数据的分段、重组、流量控制和拥塞控制。

  • 传输控制协议TCP、用户数据报协议UDP

⑤ 会话层:
负责建立、管理和终止应用程序之间的会话(连接)。它提供会话控制和同步等服务。

  • 点对点隧道协议PPTP、

⑥ 表示层:
负责数据的格式化、加密和压缩,以确保应用程序可以理解接收到的数据。它处理数据在不同系统之间的转换。

  • 安全套接字层/传输层安全协议SSL/TLS、图像相关JPEG/MPEG

⑦ 应用层:
为最终用户提供网络应用程序接口。这是用户直接与之交互的层次。

  • 超文本传输协议HTTP、安全超文本传输协议HTTPS、文件传输协议FTP、简单邮件传输协议FTP、邮局协议版本3 POP3、互联网消息访问协议IMAP、域名系统DNS、安全外壳协议SSH、远程终端协议Telnet、

TCP/IP模型:将OSI的会话层和表示层合并为应用层,将数据链路层和网络层合并为网络接口层。 即:网络接口层、互联网层、传输层、应用层

栈STACK和堆HEAP的区别

特征 栈 (Stack) 堆 (Heap)
用途 主要用于存储函数调用时的局部变量、参数、返回地址等 主要用于动态分配内存,存储程序运行时创建的对象和数据
数据结构 线性数据结构,后进先出 (LIFO - Last-In, First-Out) 无序的树状结构,不连续分配,可以随机分配和释放内存块
管理方式 由编译器自动管理,分配和释放由系统自动完成 由程序员手动管理(分配和释放),或者由垃圾回收机制负责
分配方式 连续分配,后进先出 (LIFO - Last-In, First-Out) 不连续分配,可以随机分配和释放内存块
内存大小 通常较小,大小在编译时确定或由操作系统限制 通常较大,理论上受限于系统可用内存
分配效率 非常快,只需要移动栈指针 相对较慢,需要查找合适的空闲内存块
碎片问题 不容易产生内存碎片 容易产生内存碎片,可能导致内存利用率不高
生命周期 变量的生命周期通常与其所在的函数或代码块相同 对象的生命周期由程序员或垃圾回收机制控制
访问速度 访问速度快 访问速度相对较慢

进程、线程的区别

  1. 定义:
    • 进程(Process): 是操作系统中资源分配的基本单位。一个进程可以包含一个或多个线程。每个进程都拥有独立的内存空间(地址空间)、数据段、代码段以及其他系统资源(如打开的文件、网络连接等)。可以将其理解为一个正在运行的应用程序的实例。
    • 线程(Thread): 是操作系统中CPU 调度的基本单位。一个线程存在于一个进程之内,是进程中实际执行的指令流。一个进程中的多个线程共享该进程的内存空间、数据段和代码段等资源,但拥有自己独立的栈空间和程序计数器。可以将其理解为进程中不同的执行路径。
  2. 资源拥有:
    • 进程: 拥有独立的资源,包括内存空间、文件句柄、I/O 设备等。创建和销毁进程的开销较大,因为需要分配和回收这些资源。
    • 线程: 不拥有独立的系统资源,而是共享其所属进程的资源。创建和销毁线程的开销相对较小。
  3. 上下文切换:
    • 进程切换: 当操作系统从一个进程切换到另一个进程时,需要保存当前进程的 CPU 状态(如寄存器值、程序计数器等)和内存状态,然后加载下一个进程的状态。这个过程开销较大。
    • 线程切换: 同一个进程内的线程切换只需要保存和恢复线程的 CPU 状态(如寄存器值、程序计数器等),由于它们共享内存空间,不需要切换内存状态,因此开销较小。
  4. 通信:
    • 进程间通信(IPC - Inter-Process Communication): 由于进程拥有独立的内存空间,进程之间的通信需要使用特定的机制,如管道、消息队列、共享内存、信号量、套接字等,这些机制通常涉及内核的介入,开销相对较大。
    • 线程间通信: 同一个进程内的线程可以直接共享进程的内存空间,因此线程之间的通信非常方便和高效。但也需要注意同步机制(如互斥锁、信号量、条件变量等)来避免竞态条件。
  5. 开销:
    • 进程: 创建、销毁和切换进程的开销都比线程大,因为涉及到独立的资源分配和管理。
    • 线程: 创建、销毁和切换线程的开销相对较小,因为它们共享进程的资源。
  6. 独立性:
    • 进程: 一个进程的崩溃通常不会影响其他进程。
    • 线程: 如果一个进程中的某个线程崩溃,可能会导致整个进程的崩溃,因为它们共享相同的内存空间。
  7. 地址空间:
    • 进程: 每个进程拥有独立的地址空间。
    • 线程: 同一个进程内的所有线程共享相同的地址空间。
特性 进程 (Process) 线程 (Thread)
基本单位 资源分配 CPU 调度
拥有资源 拥有独立的内存空间和系统资源 共享所属进程的内存空间和大部分资源,拥有独立的栈和程序计数器
上下文切换 开销较大,需要切换内存空间 开销较小,只需切换CPU状态
通信方式 需要使用IPC机制(管道、消息队列、共享内存等) 直接共享内存,通信高效,但需注意同步
创建/销毁开销 较大 较小
独立性 崩溃通常不影响其他进程 某个线程崩溃可能导致整个进程崩溃
地址空间 独立 共享