作为一个经历过2020年秋招的人看到“小红书2020校招测试开发后端笔试题卷一”这个标题说实话还挺感慨的。当年这套卷子在牛客和知乎上被讨论过好几轮核心原因不是它有多难而是它把测试开发和后端放到同一张卷子里考很多人拿到卷子的第一反应是懵的。直到今天还经常有学弟学妹问我这套题怎么准备所以我干脆把当年的复盘笔记整理出来把每道题的出题意图、解答思路、工作场景下的延伸全部展开讲一遍。这套卷子适合谁看两类人。一类是准备校招测试开发或者后端岗位的应届生另一类是工作了两三年想跳槽但把基础知识忘得差不多的老开发。如果你是前者看完这篇文章你会知道笔试环节到底在筛选什么如果你是后者你会发现自己平时写业务代码时忽略的那些底层原理恰恰是这类大厂笔试题最喜欢考的地方。1. 卷面整体回顾这套卷子到底在考什么1.1 试卷结构概览小红书2020校招这套卷子从名字就能看出来它不是一个纯粹的“后端卷”或者“测试卷”而是把两个方向的核心知识点揉在了一起。我当时拿到的版本大概是这样的结构模块题型题量考察方向计算机基础单选多选15题数据结构、操作系统、网络、数据库算法与编程编程题2题手写算法、代码实现能力测试开发方向简答设计2题测试用例设计、自动化测试思路后端方向简答设计3题MySQL、Redis、并发编程、系统设计附加题开放题1题业务理解、技术选型能力从结构上就能看出一个信号这份卷子不是单纯考你“会不会写代码”而是考你“有没有完整的工程思维”。测试开发方向要你会设计测试用例、懂自动化框架后端方向要你会处理高并发、懂存储选型。而两者共用一套计算机基础题说明无论你报哪个岗位计算机基本功都是底线。1.2 出题思路解读小红书想要什么样的人结合2020年的业务背景来看当时小红书正处于内容社区快速增长期用户量持续上涨内容推荐、搜索、笔记发布这些核心链路对稳定性和性能要求都很高。所以出题人非常清楚自己想招什么样的人不是只会刷题的书呆子而是能理解业务、能定位问题、能落地解决方案的工程师。举个例子卷子里有一个典型的场景题是“如何测试一个笔记发布接口”。如果你只回答“输入合法参数、验证返回结果”那基本就是送命答案。出题人真正想看的是你会不会考虑接口的幂等性会不会想到网络超时导致客户端重试的情况会不会关注脏词过滤和图片鉴黄这条链路的测试这些都是在真实业务里每天都会遇到的问题。再说后端方向那道“如何设计一个短链服务”表面上是系统设计题实际上考察的是你对哈希算法的理解发号器 vs 随机字符串、你对存储选型的判断Redis vs MySQL、你对缓存和数据库一致性的把握。这些能力不是一个刚毕业的学生天然具备的得靠平时有意识地积累项目经验才能答得出来。所以我的建议是刷这套卷子不要只盯着标准答案而是要站在出题人的角度去想这个知识点对应的是业务里的哪个场景如果我在小红书做开发这个问题会在哪条链路上出现想通了这一点你收获的远不止一份参考答案。2. 核心题型拆解两个方向的考点分析2.1 测试开发方向用例设计、自动化与质量保障思维测试开发这块笔试里最容易拉开差距的是“测试用例设计题”。它不像算法题有唯一解但恰恰因为没有唯一解更能看出一个人有没有系统的测试思维。这里我以当年卷子里的一道经典题为例“请设计测试用例覆盖APP登录功能。”如果只是顺着页面功能写你会写出“输入正确用户名密码登录成功输入错误密码提示密码错误”这种毫无区分度的用例。但真正有经验的测试开发会这样拆解功能维度正常登录正确的手机号验证码、正确的手机号密码异常登录错误的手机号格式、不存在的用户、密码错误、验证码错误边界值手机号位数刚好11位、少1位、多1位、包含非数字字符状态切换前后台切换、网络中断恢复后继续登录、登录中杀进程再次进入安全维度密码是否明文传输抓包查看验证码是否有时效性过期后是否还能提交同一手机号短时间内频繁请求是否触发风控兼容维度不同系统版本Android/iOS、不同屏幕尺寸弱网环境2G/3G/wifi切换下的登录表现接口层面重复提交双击登录按钮是否会产生多条请求参数被篡改如修改手机号字段时后端是否能识别并发登录同一账号在不同设备上会话如何处理看到区别了吗前者是把测试当成“点按钮”后者是把测试当成“找漏洞”。我在实际工作里带新人时最常强调的一句话就是一个好的测试用例不是用来证明系统能用的而是用来证明系统在什么情况下会挂的。这个思路适用于任何功能不只是登录。除了用例设计卷子里还考察了自动化测试框架的理解。比如“如何设计一套基于Selenium的UI自动化框架”这种题。重点不在于你会不会写Selenium脚本而在于你有没有想过怎么管理测试数据怎么做用例的依赖处理失败用例怎么自动重跑和定位截图报告怎么输出这考察的是一个测试开发“开发”二字的含量。2.2 后端方向数据结构、存储、并发与系统设计后端方向的题量更大考察点也更密集。我把核心考点归纳成四个维度第一个维度是数据结构与算法。笔试里通常会出现1-2道经典算法题比如“手写LRU缓存”。这道题在LeetCode上是146题很多人在本地IDE里写过很多遍但笔试时手写就各种翻车。问题主要出在一是对双向链表的操作不熟容易把指针指丢二是没有想清楚为什么LRU要用“哈希表双向链表”而不是“哈希表数组”。如果你能说出“数组的插入删除是O(n)而双向链表可以在O(1)时间内完成节点删除和头尾移动”这道题就已经赢了一半。第二个维度是MySQL。这几乎是必考项。高频考点有三类索引失效的场景、事务隔离级别、SQL调优。其中“索引失效”是很多人容易混淆的点。比如“对索引列使用函数”会导致索引失效但很多人不知道“隐式类型转换”也会导致索引失效。举个例子如果表里有一个varchar类型的phone字段查询时写WHERE phone 13800138000MySQL会把数字转成字符串再比较一旦发生隐式类型转换索引就废了。这种坑在真实开发里非常常见笔试考到相当合理。第三个维度是并发编程。常见的是“手写一个单例模式”、“用多线程实现生产者消费者模型”这类题。考察的不仅是你会不会写还有你对线程安全的理解。以单例模式为例线程不安全的懒汉式、加synchronized的懒汉式、双重检查锁、静态内部类、枚举这一层层递进每一层改动背后的原因是什么都是面试官追问的对象。第四个维度是系统设计。这是区分度最大的一类题。比如“小红书笔记feed流怎么设计缓存和存储”这种题没有标准答案但考察的是你有没有自己的技术判断力。我当时回答的思路是先明确需求是推模式还是拉模式实时性要求多高再做技术选型Redis存热数据、MySQL存全量数据、消息队列做异步推送最后给出每一层的容灾和降级方案。这种层层递进的逻辑比直接抛出一个“用Redis就好了”的回答要高级得多。3. 实操过程我的完整复盘与解题记录3.1 测试开发题的完整复现与演进我选择把这道题完整复盘一下因为它在当年卷子里最有代表性。题目原文大概是“小红书是一个内容社区用户可以对笔记进行点赞、收藏、评论。请设计测试用例覆盖评论功能。”先看正常链路用户在笔记详情页输入评论内容点击发布评论展示在评论区评论数1。看起来很简单对吧但如果我们按照“找漏洞”的思路拆解会设计出下面这些用例。普通评论场景用例编号测试场景前置条件操作步骤预期结果优先级TC01正常发布评论用户已登录输入合法评论内容点击发布评论展示成功评论数更新P0TC02空评论用户已登录不输入内容直接点击发布按钮置灰无法提交P0TC03评论内容为纯空格用户已登录输入空格点击发布提示“评论内容不能为空”P1TC04超长评论用户已登录输入超过2000字的内容输入框截断或提交失败并提示P1TC05评论含敏感词用户已登录输入敏感词内容发布失败提示包含违规内容P0特殊场景用户未登录时点击评论是否跳转登录页登录后是否回跳到原笔记笔记作者删除笔记后评论入口是否关闭已发布评论是否同步删除评论后立刻删除评论计数是否正确扣回同一用户对同一笔记在短时间内大量评论是否触发频控网络请求中断时点击发布客户端是否有重试机制是否会导致评论重复接口层面直接构造POST请求绕过客户端限制后端是否能识别非法参数评论内容携带emoji、URL链接、HTML标签时后端是否做了防XSS处理并发请求同一账号同一时刻发送两条评论是否都成功ID是否冲突说实话如果只按页面功能设计这场笔试大概能得30%的分但如果按上面的维度展开并且每一类都给出几个具体的用例阅卷人一眼就能看出你是干过活的。这也是我给所有准备测试开发岗的同学一个非常务实的建议平时做项目时别只写功能代码多想想这个功能怎么测、在什么情况下会出问题养成这种思维模式之后笔试的用例设计题基本就是送分题。3.2 后端题的完整复现与演进后端方向我挑两道题详细复盘。第一道是上面提到的“手写LRU缓存”。我用Java实现先展示代码再讲思路。import java.util.HashMap; import java.util.Map; public class LRUCache { private final int capacity; private final MapInteger, Node map; private final Node head; private final Node tail; private class Node { int key; int val; Node prev; Node next; Node(int key, int val) { this.key key; this.val val; } } public LRUCache(int capacity) { this.capacity capacity; this.map new HashMap(); this.head new Node(-1, -1); this.tail new Node(-1, -1); head.next tail; tail.prev head; } public int get(int key) { if (!map.containsKey(key)) { return -1; } Node node map.get(key); moveToHead(node); return node.val; } public void put(int key, int value) { if (map.containsKey(key)) { Node node map.get(key); node.val value; moveToHead(node); } else { Node node new Node(key, value); map.put(key, node); addToHead(node); if (map.size() capacity) { Node removed removeTail(); map.remove(removed.key); } } } private void moveToHead(Node node) { removeNode(node); addToHead(node); } private void removeNode(Node node) { node.prev.next node.next; node.next.prev node.prev; } private void addToHead(Node node) { node.next head.next; node.next.prev node; head.next node; node.prev head; } private Node removeTail() { Node node tail.prev; removeNode(node); return node; } }这段代码的复杂度get和put都是O(1)。核心逻辑是读数据时把节点移动到链表头部写数据时如果超出容量就淘汰尾部节点。为什么必须用双向链表因为在淘汰尾部节点时我们需要同时访问它的前一个节点如果用的是单链表从头部遍历到尾部是O(n)整个缓存的复杂度就退化了。写完代码之后考场上还需要口头回答一个问题“如果并发访问这个LRU Cache有哪些线程安全问题”答案有三个第一HashMap本身不是线程安全的多线程put可能导致扩容时死循环JDK7的经典bug第二链表的节点指针操作不是原子的两个线程同时对同一个节点做moveToHead可能把链表结构改乱第三size的检查不是原子的多线程同时put时可能超过capacity。解决方法可以是加synchronized锁也可以使用ConcurrentHashMap加锁分段或者直接用LinkedHashMap包一层Collections.synchronizedMap但后者的锁粒度太粗生产环境一般会自己实现或者用Caffeine这类现成组件。这道题能答到这一层才算真正掌握了。第二道题是MySQL索引相关“表user(id, name, age, phone, create_time)有联合索引(age, create_time)请判断以下查询是否走索引并说明原因。”查询一SELECT * FROM user WHERE age 20 ORDER BY create_time DESC;走索引。联合索引的第二个字段可以用在ORDER BY上因为索引本身就是按(age, create_time)排序的age等值匹配后create_time已经天然有序。查询二SELECT * FROM user WHERE create_time 2020-01-01 ORDER BY age;不走索引或索引低效。因为联合索引的最左前缀原则查询条件跳过了第一个字段age直接用第二个字段create_time做过滤无法使用该联合索引。查询三SELECT * FROM user WHERE age 20 AND create_time 2020-01-01 ORDER BY create_time DESC;走索引但还需要看是否需要回表。如果查询的所有字段都在索引里覆盖索引效率最高如果还需要回表查询其他字段在大数据量下可能触发优化器放弃索引改走全表扫描。我在复盘这道题时反复跟身边人强调一个点判断一个查询是否走索引不能只背“最左前缀原则”这几个字而是要把B树索引的底层结构画出来把数据是怎么存储和排序的想清楚。联合索引(age, create_time)在B树里的排序规则是先按age排age相同再按create_time排。所以age等值条件天然能定位到一个有序子集create_time的范围排序也在这个子集内有序这种查询命中索引是符合逻辑的。反过来如果你一上来就查create_time索引里age字段的顺序就帮不上忙了。3.3 一道隐藏加分题如何测试一个推荐系统这套卷子里还有一道开放性很强的题我记得是“针对小红书的笔记推荐功能设计测试方案”。这道题在当年刷屏了因为很多人完全不知道推荐系统该怎么测。它没有明确的输入输出不存在“对和错”的标准答案很多人的第一反应是无从下手。我当时是这么拆解的。第一层数据质量测试。推荐系统的输入是用户行为日志和笔记特征数据。我会设计用例验证用户点击日志是否准确上报重复曝光是否被去重笔记审核状态是草稿时是否会被误推荐这些数据问题虽然看起来不涉及算法本身但恰恰是影响推荐效果最直接的因素。第二层接口功能测试。推荐接口本身要验证参数缺省时是否有默认值用户画像为空新用户时是否能返回兜底内容冷启动场景下是否有热门内容池填充接口的耗时是否在可接受范围内超时是否有降级策略第三层推荐效果测试。这一层是推荐系统测试的核心差异点。需要关注推荐结果的多样性连续10条是否全是同一类目去重逻辑同一篇笔记是否反复出现真实性用户已经看过的笔记是否被过滤刷新的随机性同一用户连续刷新结果是否完全不变第四层极端场景测试。用户长时间不登录后首次访问推荐内容是否有变化新注册用户没有任何行为数据推荐池是什么内容推荐服务挂了客户端是否有兜底页面还是直接白屏这道题能答出这四个维度至少能说明你不是一个只会写用例的执行者而是具备测试方案设计能力的人。当时我在卷面上还加了一句“推荐系统的效果评估不应只看线上点击率还需要结合用户停留时长、关注转化率、笔记举报率等多个指标综合判断。”后来我入职后才发现这句话在真实业务里比那些用例设计值钱得多因为推荐系统测试的核心难点从来不在于“测不测”而在于“用什么指标来衡量它好不好”。4. 常见问题与排查技巧实录4.1 笔试中容易踩的五个坑结合我这几年带人、看简历、也偶尔帮HR筛笔试的经验我总结出五个非常典型的失分点。第一个坑只写结论不写推导过程。比如上面那道索引题很多人直接写“走索引”或“不走索引”一个字的理由都不给。笔试阅卷很多时候不是看你对不对而是看你的思考过程能不能验证。尤其在后端方向推导比结果重要。正确的做法是“该查询走了索引因为联合索引最左前缀匹配到agecreate_time在索引内有序可以利用索引避免filesort。但需要注意是否回表若查询列超出索引范围且数据量大优化器可能选择全表扫描。”第二个坑算法题代码没有考虑边界条件。手写LRU的时候很多人处理了缓存未命中和容量淘汰但漏了当重复put同一个key时应该先更新值再移动节点而不是先删除再插入。这种边界处理就是笔试里“算法能跑通”和“代码无bug”的直接分水岭。第三个坑测试用例只测正常路径。这是测试开发岗最致命的问题。一份卷子如果从头到尾都是“输入正确数据返回正确结果”阅卷人可以直接判定这个人没有测试思维。一定要保证你的用例里至少有30%是在测异常路径、边界值、极端场景。第四个坑系统设计题没有需求澄清环节。一上来就甩方案是后端岗位的大忌。面试官问“怎么设计一个短链服务”你开口就是“用Redis存”这是典型的方案先行。真正的思路应该是先问“预估QPS是多少链接有效期是多长需不需要自定义短链”再谈“基于这些约束条件我选择用发号器生成IDRedis做缓存MySQL做持久化存储”。没有需求约束的技术方案都是空中楼阁。第五个坑忽略负向测试与安全测试。这个问题在校招笔试卷里特别常见。比如测试登录功能几乎没有人会想到去验证“如果请求参数被篡改会怎么样”。大厂非常看重安全意识和边界意识如果一个测试开发在笔试时就能主动补充SQL注入、越权访问这类用例分数会明显上一个档次。4.2 复盘这套卷子的实操建议很多人刷完一套题就把它丢到一边其实这是最大的浪费。我复盘这套卷子时有一个自己的方法每道错题不只看正确答案而是往前追问三步。第一步这道题考的是哪个知识点比如Redis持久化是RDB还是AOF区别是什么。 第二步这个知识点在我做过的项目里哪里可以用到比如我做的社区系统用户关注关系如果放Redis用Set存持久化策略应该选哪种 第三步如果我是面试官我还能顺着这道题追问什么比如问完RDB和AOF的区别肯定会追问“Redis宕机最多丢多少数据”、“AOF重写期间服务还能正常写吗”、“如果AOF文件损坏了怎么办”。用这个方式过一遍卷子等于把一道题变成了至少三道题。我当时把这套卷子翻了整整三遍每一遍都能挖出更深一层的问题。第一遍是“我会不会做”第二遍是“我能不能讲明白”第三遍是“我能不能出新的变体题考别人”。如果你能把一套卷子刷到能自己给自己出题的程度校招笔试基本就稳了。还有一个特别实用的建议给自己限定时间模拟真实笔试环境。这套卷子的题量不小如果按部就班慢慢做很容易做不完。我建议算法题控制在25分钟内简答题每题15分钟系统设计题30分钟。一旦超时不管答没答完都要停止然后看自己超时在哪一部分。大多数人会超时在系统设计题因为总是控制不住想把方案写得很完善这是典型的学生思维。在工作里方案永远是先给一个MVP最小可行版本再迭代完善笔试也一样。5. 写在最后通过这套题我看懂的几件事这套卷子对当年的我来说最大的收获不是拿到了Offer而是让我第一次真正想明白了“测试开发”和“后端开发”不是两个割裂的岗位。在这套卷子里你很难画出一条清晰的线说“这题是测试要考的那题是后端要考的”。测试要懂后端接口的幂等逻辑才能设计出防重复提交的用例后端要懂测试的边界思维才能写出不容易出bug的代码。技术岗位的底层能力是相通的区别只是你关注的视角和处于工程链路的位置不同。如果你现在正准备这类笔试最后给你两个建议。第一不要只刷题一定要动手写。这套卷子里的LRU、SQL分析、测试用例设计每一样都可以在自己电脑上实操验证一遍写一遍比背十遍都管用。第二不要只看标准答案一定要看推导过程。大厂的笔试从来不是只看你会不会做而是看你会不会想。把每个推导过程刻在脑子里比记住一堆零散结论重要得多。我个人复盘完这套卷子之后还有一个后遗症直到今天我写任何一个接口都会下意识地问自己一句“如果我是测试我会怎么攻击这个接口”。这种换位思考的习惯算是这套卷子给我留下的最实在的礼物。