水滴测试开发一面:Agent 项目深挖与并发八股
- 轮次
- 一面
- 时间
- 2026-09
- 来源
- 牛客网
《面试题目》
- 请做一下自我介绍。
- 介绍一下你的这个 Agent 项目。
- 你在搭建这个 Agent 项目的时候,是怎么想到要用这个架构去实现这个功能的?
- 聊一下实习的工作?
- 选一个你比较熟悉的需求,聊一下这个需求,你是怎么设计对它进行测试的?
- 在这个过程中你用了哪些工具?测试这些东西的时候写过脚本吗?
- 测试报告是怎么写的,例如复测率、测试方面的统计指标?
- 你认为最难定位的 bug 是哪一个,你是怎么定位到的?
- 使用 Claude Code 做了哪些提效?
- 你在测试的过程中有没有遇到过需要并发解决的一些问题?
- 怎么做的这个自动生成测试用例,以及怎么计算得到 70% 的效果?
- 在日常生活中平时写代码,AI 用到了什么程度?
- 线程和进程、协程的区别?
- 为什么线程切换比进程快呢?
- 什么场景适合用多进程,什么场景适合用多线程?
- 什么情况会遇到线程安全的问题?
- 自己初始化线程池的话,有哪几种方法?
- 用构造函数初始化线程池,它的构造参数里面有哪些配置?
- 任务队列有哪几种?
- 你这个 Agent 项目如果让你重新设计,你会如何去控制输入、模型的选择、包括上下文以及工具调用呢?
- 之前 GPT 做心理咨询,它出现过不符合伦理的回答,你是怎么控制你这个项目的输出结果的?
《参考解析》
进程、线程、协程的区别
从资源归属和调度主体两个维度看最清楚。进程是资源分配的最小单位,有独立的虚拟地址空间、文件描述符表和页表,进程间互不共享内存,通信要靠管道、共享内存、socket 这类 IPC;线程是 CPU 调度的最小单位,同一进程内的线程共享地址空间和堆,各自只有独立的栈、寄存器和线程局部存储,所以线程间通信几乎零成本,代价是共享数据要自己加同步。
协程是用户态的轻量执行体,由程序自己(或运行时)调度,内核完全不感知,一个线程里可以挂很多协程,切换只在用户态保存少量寄存器,没有陷入内核、也没有 TLB/页表切换的开销,所以能轻松支撑几万到几十万个并发单元。它的局限也很明确:一个协程做阻塞式系统调用会卡住整个线程,因此协程库通常要求配套的非阻塞 IO 和事件循环(Go 的 GMP 会在阻塞时把 P 交给别的 M 来缓解这一点)。面试里常被追问的一句话是:协程解决的是并发规模和切换开销,不是并行能力,真正的并行仍然取决于 CPU 核数。
为什么线程切换比进程快
切换本身的开销分两块:直接开销和间接开销。直接开销是保存/恢复上下文——进程切换要换页表基址寄存器(CR3)、刷新 TLB 或打上 PCID 标记、切换内核栈和硬件上下文;线程切换因为地址空间不变,CR3 不用动,TLB 和 cache 里的映射与数据大部分还能命中。间接开销更致命:切换后访问的内存、指令缓存几乎全冷,进程切换后要重新预热页表和 cache,线程切换的惩罚小得多。
所以回答时最好补一句:进程切换慢主要慢在「地址空间和缓存失效」,而不是慢在保存寄存器那几十条指令。再往下追问通常会到协程——协程切换省掉了内核态与用户态的往返,开销又降一个数量级。
线程池构造参数与任务队列怎么选
ThreadPoolExecutor 的核心参数:corePoolSize、maximumPoolSize、keepAliveTime + unit、workQueue、threadFactory、handler。关键在于把「任务提交后的决策顺序」讲清楚:先看当前线程数是否小于核心数,是就直接新建核心线程;否则尝试入队;队列满了才把线程扩到最大线程数;仍然满就触发拒绝策略。这也解释了为什么用无界队列(LinkedBlockingQueue 默认容量 Integer.MAX_VALUE)时 maximumPoolSize 形同虚设、且任务持续堆积会 OOM。
队列的取舍:ArrayBlockingQueue 有界数组、可控但吞吐受锁限制;LinkedBlockingQueue 链表、吞吐高但默认无界,生产上一定要指定容量;SynchronousQueue 不存储元素、来了必须马上有线程接手,配合大 maximumPoolSize 就是 CachedThreadPool 的行为;PriorityBlockingQueue 按优先级出队;DelayQueue 做定时/延迟任务。线程数经验值:CPU 密集型取核数 +1 左右,IO 密集型可以按「核数 × (1 + 平均等待时间 / 平均计算时间)」放大,但最终必须靠压测定;拒绝策略里 CallerRunsPolicy 能形成天然的背压,比直接丢弃更容易发现容量问题。
自动生成测试用例,70% 这个效果怎么算
先要定义清楚「70%」的分子分母,否则这个数字没有意义。可控的口径是:以人工评审通过的历史用例集为基线(golden set),让模型对同一批需求/接口文档生成用例,统计基线用例被覆盖到的比例(召回,衡量有没有漏测)和生成用例中被评审判定为有效的比例(准确率,衡量噪声多少),再补一个上游指标——需求点覆盖率(每个验收点是否至少有一条对应用例)。做自动化生成时,经验是接口层比 UI 层好做得多:把接口契约(OpenAPI)、字段约束、枚举值、历史缺陷单一起喂进去,让模型按等价类 + 边界值 + 异常组合的模板展开,输出的用例再走一层去重(按接口 + 参数组合指纹)和可执行性校验(能不能跑通、断言是否有意义)。
要注意的坑:拿「用例条数」当效果指标会被模型灌水,所以必须统计相对基线的增量覆盖,而不是绝对数量;另外生成集要与基线分开维护,否则自己的用例会污染评测集,指标会虚高。
怎么控制大模型项目的输出合规
思路是分层设防,而不是指望一句 system prompt。第一层是输入侧:对用户输入做意图与风险分类,命中自伤、医疗诊断、法律意见这类高风险域时走预设的安全回复或转人工,不进正常生成链路。第二层是提示词与角色约束:明确角色边界和能力边界,规定「不做什么」的同时要给出「遇到越界请求时该做什么」,只写禁止项模型容易在诱导下绕开。第三层是输出侧审核:用小模型或规则做一遍分类(涉政、涉黄、涉暴、医疗用药、心理危机干预),命中即拦截或改写,并对流式输出做分段检测,避免整段生成完才发现问题。第四层是兜底与可追溯:保留会话与拦截日志、给用户一键转人工/求助热线的出口,高危场景(心理咨询尤其)在首轮就声明能力边界并展示危机干预资源。
还有一点面试官爱追问:不要用「加个关键词黑名单」当答案——黑名单只能防低级绕过,真正的做法是风险分类器 + 场景化的可控回复模板,再配合灰度上线和人工抽检迭代分类器。