ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

搜狗测试工程师秋招笔试复盘:编程题与测试思维全解析

搜狗测试工程师秋招笔试复盘:编程题与测试思维全解析 2019年搜狗秋招测试工程师的第二场笔试我印象最深的一点是它不像开发岗那样只考算法题而是把“编程”和“测试思维”揉在一起考。网上对这场的原题记录比较零散我尽量还原了当时考到的几个核心题型把解题思路、代码实现、边界条件和笔试踩坑点都整理出来。无论你现在是在准备测试开发岗位还是想补一补算法基础这份复盘应该都能帮你在笔试前建立一个清晰的复习框架。1. 第二场笔试到底在筛什么人1.1 为什么测试工程师也要刷算法题很多第一次参加测试岗笔试的同学会问我以后又不写核心业务代码为什么笔试要考编程题这个想法在面试官眼里其实是个减分项。搜狗这类公司的测试工程师日常要处理接口测试、性能测试、自动化脚本、日志解析、数据比对等工作写代码不是可选能力而是基本工具。尤其是“测试开发”方向的岗位代码能力直接决定了你能否写出可维护的自动化框架能否快速定位问题根因。第二场笔试的编程题表面考的是数据结构、字符串处理、边界条件实际上考的是三件事第一你有没有扎实的编码基本功第二你能不能把一个模糊的题目转换成明确的需求第三你写完代码后有没有意识和能力给自己补测试用例。这三点恰好就是测试工程师日常工作的核心能力。所以别再把笔试编程题当成“随便应付”的环节它往往是筛人的第一道坎。1.2 第二场题型的整体分布与分值特点从当时考完的反馈来看第二场编程题大致有4到5道分值不低整体难度比开发岗稍微低一些但坑很多。题型集中在几类字符串处理去重、比较、过滤、格式化这类题最常出现。数组与指针基础涉及下标、越界、原地修改。链表操作快慢指针、环、反转属于经典必考题。场景模拟题给一段描述要求实现函数并补充测试用例。时间一般是一个半小时左右如果只埋头写代码不检查边界很容易“样例过了、提交全错”。所以我在复盘时一直强调每题写完必须留时间自己跑边界。这个习惯比多刷十道题还有用。2. 四道高频编程题复盘与代码实现2.1 字符串去重并保持原始顺序这道题不算难但非常典型很多笔试都会考类似变体。题目大意是给定一个只包含字母和数字的字符串要求去掉重复字符并保持每个字符第一次出现时的相对顺序。比如输入cbacdcbc输出cbad。如果题目再加一句“不区分大小写”那输出结果又会不一样。所以第一步不是写代码而是确认需求。一个简单又稳定的写法是用集合记录是否出现过再用列表保留结果顺序Python代码如下def deduplicate(s: str) - str: seen set() result [] for ch in s: if ch not in seen: seen.add(ch) result.append(ch) return .join(result)思路很直接集合负责去重列表负责保序。如果你用dict去重其实也行Python 3.7以后字典天然保持插入顺序代码可以更短def deduplicate(s: str) - str: return .join(dict.fromkeys(s))但这里要提醒一句笔试中尽量不要用太“魔法”的写法。dict.fromkeys在面试官眼里可能不好解释而且如果题目要求“忽略大小写”或“保留最后一次出现的顺序”这种写法就没法直接套用了。我建议用最基础、最好讲清楚的版本然后在注释里标注复杂度时间O(n)空间O(n)。这道题的隐藏考点不是去重而是你对“顺序”的理解。如果题目改成“重复字符保留最后一次出现的位置”你就要从后往前遍历如果改成“按重复次数排序”就变成了统计计数题。所以刷题时要多问自己一句题目变了半句话我的解法还成立吗2.2 版本号比较问题这道题我印象很深因为它特别贴近测试工程师的实际工作。版本号比较在客户端发版、接口兼容、Bug定位时经常用到。题目大致是这样给定两个字符串形式的版本号v1和v2格式为以点号分隔的数字比如1.10.2、2.0要求判断哪个版本号更大。规则是从左到右依次比较每个数字如果某个版本号没有更多段则缺失段按0处理。函数要求返回1表示v1大-1表示v2大0表示相等。def compare_version(version1: str, version2: str) - int: parts1 version1.split(.) parts2 version2.split(.) max_len max(len(parts1), len(parts2)) for i in range(max_len): num1 int(parts1[i]) if i len(parts1) else 0 num2 int(parts2[i]) if i len(parts2) else 0 if num1 num2: return 1 elif num1 num2: return -1 return 0这里有几个坑必须注意。第一int()转换已经帮我们处理了前导零问题01会变成1。如果你的语言没有这种现成转换比如C用stoi也需要注意前导零和空串问题。第二缺失段按0处理而不是直接判断长度。1.0和1是相等的这是最容易写错的地方。第三如果版本号特别长比如有几十位数字int可能会溢出。笔试一般不会这么极端但如果你用C建议用long long或者干脆用字符串去掉前导零后做字典序比较。我当时写这道题时第一遍就漏了1.0和1相等这个case样例一过就交结果被扣了分。后来学乖了只要是字符串比较类的题先把“末尾缺失补零”这个规则写进代码再开始写逻辑。2.3 链表中环的入口节点链表题在测试岗笔试里出现频率很高因为链表涉及指针操作最容易暴露编码习惯。这道题是经典题给定一个链表如果链表中存在环返回环的入口节点如果没有环返回null。看到环自然想到快慢指针。一个很常见但不够严谨的解法是快慢指针相遇就返回相遇点这样其实返回的是环中任意一个点不是入口。正确做法是分成两步第一步用快慢指针判断是否存在环。第二步相遇后让其中一个指针回到头节点两个指针每次都走一步再次相遇的位置就是环的入口。实现代码class ListNode: def __init__(self, x): self.val x self.next None def detectCycle(head: ListNode) - ListNode: if not head or not head.next: return None slow, fast head, head while fast and fast.next: slow slow.next fast fast.next.next if slow fast: p head while p ! slow: p p.next slow slow.next return p return None为什么要再走一遍这是很多初学者没想透的地方。假设链表头到环入口的距离是a入口到相遇点的距离是b相遇点再走到入口的距离是c。快指针走的距离是慢指针的两倍推导之后会发现从相遇点继续走到入口的距离刚好等于从头节点走到入口的距离a。所以让一个指针从头开始一个指针从相遇点开始同时同速前进它们必然在入口相遇。这个推导不要求你背下来但至少要能讲明白因为面试官可能会追问。这道题的易错点有几个第一循环条件必须同时判断fast和fast.next否则会空指针第二如果链表只有一个节点且自环fast.next是自身要区分next为None和指向自身的情况第三返回的是节点而不是节点值题目没看清就会错。2.4 敏感词过滤与模糊需求处理这道题是我觉得整套题里最有“测试工程师味道”的题。题目描述很简单输入一段文本和一个敏感词列表要求把所有敏感词替换成等长的*号。但如果你真按字面需求去写后面会发现自己陷入一堆“没说清楚”的问题。比如敏感词是[ab, bc]文本是abc替换后应该是什么如果按顺序逐词替换先用ab替换得到**c然后再处理bc此时bc已经被破坏可能匹配不到。如果先把所有命中位置标记出来再统一替换结果可能是***也可能是**c完全取决于题目怎么定义“覆盖重叠”。我当时写的是标记法先遍历所有敏感词在文本上标记需要遮蔽的位置最后统一替换。这样即使有重叠也能保证所有敏感词覆盖过的位置都被打码更符合安全过滤的直觉。def mask_sensitive(text: str, words: list) - str: if not text or not words: return text n len(text) mark [False] * n for word in words: if not word: continue start 0 pos text.find(word, start) while pos ! -1: for i in range(pos, pos len(word)): mark[i] True start pos 1 pos text.find(word, start) chars list(text) for i in range(n): if mark[i]: chars[i] * return .join(chars)这段代码的复杂度是O(m * n * len)m是敏感词数量n是文本长度严格说不够高效。笔试时如果时间充裕可以用更优雅的AC自动机或者KMP优化但对你来说能写出正确、可解释的暴力解已经赢了大多数人。面试官更关心你能不能主动提出“重叠覆盖”这个需求歧义而不是一上来就背AC自动机模板。这题给我们的启发是测试工程师拿到一个需求第一反应不是“怎么实现”而是“需求有哪些不明确的地方”。哪怕笔试不要求你写测试用例你也要在代码注释里标出假设比如“我按重叠也遮蔽处理”、“空敏感词直接忽略”。这样面试官会认为你有测试意识而不是一个纯粹的码农。3. 测试用例设计编程题之外隐藏的拉分点3.1 等价类与边界值在编程题中的应用很多同学笔试时写完代码就交觉得“样例过了就稳了”。但搜狗这类公司的评分系统里除了跑测试数据还有面试官人工看代码和注释。如果你在代码旁边额外写出你想到的测试用例会非常加分。测试用例设计有两个最基础的方法等价类划分和边界值分析。以版本号比较为例输入可以分成这几类正常情况1.2.3和1.2.4预期小版本号大的更新。长度不等1.0和1预期相等。前导零01和1预期相等。空字符串按题意可能需要返回特殊值或者直接判定异常。大数字999999999999和1000000000000测试是否会溢出。把这些整理成表格放在代码注释旁边是很专业的做法。我当时笔试时会在每道题最后留一小段比如测试用例1: (1.2.3, 1.2.4) - -1 测试用例2: (1.0, 1) - 0 测试用例3: (2.0, 1.999) - 1 测试用例4: (, 1) - 异常这不会占用你太多时间但能让面试官一眼看出你具备测试思维。3.2 为你的代码写出可执行的单元测试如果你用的是Python笔试时还可以直接在本地用assert跑一遍。比如版本号比较函数可以写一段简单的测试assert compare_version(1.2.3, 1.2.4) -1 assert compare_version(1.0, 1) 0 assert compare_version(2.0, 1.999) 1 assert compare_version(0.1, 0.0.1) 1注意最后一个用例0.1和0.0.1如果你在代码里执行int转换再比较会得到1因为第二个版本号的第二位是0。如果把这个用例跑完基本能确认你的补零逻辑是对的。对于字符串去重可以这样测assert deduplicate(cbacdcbc) cbad assert deduplicate() assert deduplicate(aabbcc) abc assert deduplicate(AbA) AbA这里有个隐藏问题如果题目要求忽略大小写那AbA应该输出Ab还是aB不同题目要求不同所以测试用例也帮你反过来验证了你的假设。我个人的习惯是核心算法写完之后先写至少三组用例一组正常一组空值一组边界。这个习惯帮我避免了很多“自我感觉良好”的错误。4. 笔试环境与典型Bug排查实录4.1 输入输出格式本地能跑OJ报错怎么办笔试平台和本地IDE有一个非常大的区别在线评测系统OJ对输入输出格式要求极其严格多一个空格、少一个换行都可能导致答案错误。很多人第一道题就栽在读取输入上。搜狗这类公司的笔试题通常不是LeetCode那种“只写函数”的格式而是需要你自己处理标准输入。比如题目要求第一行是测试用例数T后面T行是字符串那你需要写成import sys def main(): data sys.stdin.read().strip().split() if not data: return t int(data[0]) index 1 for i in range(t): s1 data[index] s2 data[index 1] index 2 print(compare_version(s1, s2)) if __name__ __main__: main()有人会问为什么不用input()其实也可以用但要注意每行后面可能有多余空格以及可能读到空行。用sys.stdin.read()一次性读进来再按空白拆是最稳妥的写法能避开大部分输入格式问题。有一个典型坑如果题目说“多组输入以EOF结束”你需要这样写while True: try: line input() except EOFError: break # 处理line这个写法在本地测试时如果你没有输入任何内容就直接回车程序会一直阻塞等待看起来像卡死实际上是在等EOF。这时候要按CtrlDLinux/macOS或CtrlZWindows模拟EOF。4.2 常见运行错误与调试经验笔试时最常见的报错是答案错误、超时、数组越界、空指针、死循环。我逐个说下排查思路。答案错误大概率不是思路问题而是边界条件遗漏。比如版本号比较没处理长度不等字符串去重没考虑大小写。遇到答案错误第一反应是回看题目里有没有“空字符串”“缺失段按0处理”这类细节而不是怀疑算法本身。超时如果题目数据范围很大你的暴力解可能过不了。比如敏感词过滤如果文本很长、敏感词很多逐个find会超时。这时候可以先检查是否有不必要的循环比如在while里重复切片、重复join都可能导致性能问题。测试岗笔试虽然对性能要求不如开发岗高但也不能完全不想复杂度。数组越界和空指针这两个问题几乎都出在边界判断。链表题里fast.next没有判空就继续走数组题里i1在最后一个元素时越界。我的经验是所有涉及next、i1、len-1的地方都要问自己一句“如果当前是空、是0、是最后一个会不会崩”。死循环快慢指针题目最常见。如果你在循环里没有让两个指针都前进或者循环条件写成了while fast.next而不是while fast and fast.next就可能陷入死循环。遇到这类问题先在纸上模拟一个只有两个节点的链表人脑跑一遍循环。还有一个调试技巧在本地调试时不要只跑样例要主动打印中间变量。比如链表题可以打印每次遍历时slow.val和fast.val字符串题可以打印seen集合里的内容。很多问题其实一眼就能看出来只是你一直盯着代码没输出。5. 针对测试岗的备考优先级与个人心得5.1 刷题优先级与时间分配如果你现在时间紧张只有两三周准备搜狗这类公司的测试岗笔试我的建议是不要贪多而是把高频考点吃透。按优先级排一下第一优先级字符串处理、数组、链表。这是测试岗笔试出现频率最高的三类。重点是去重、排序、反转、环、合并、拆分。第二优先级栈、队列、哈希表。常用于括号匹配、表达式求值、窗口计数。第三优先级二叉树、二分、动态规划。出现的概率相对低但不能完全不碰至少二叉树遍历要会。第四优先级复杂的图论、贪心、字符串匹配算法。如果没有余力可以先放一放性价比不高。除了刷题一定要练习“白板讲解”。笔试之后通常还有面试面试官会拿着你笔试的代码问“为什么这里用快慢指针”“如果输入是空怎么办”。你不仅要会写还要会讲。5.2 一个人人都能用的笔试习惯最后分享一个我每次笔试都会用的习惯拿到题面后先用30秒在草稿纸上列出三个东西——输入范围、输出格式、异常情况。这30秒看起来浪费实际能帮你节省十分钟的返工时间。举个例子如果题目没有指定输入字符串长度你就要默认可能很大不能用简单的多层循环如果题目说“版本号可能以0开头”你就要想到前导零如果题目说“敏感词可能有重叠”你就要在代码里做标记而不是逐词替换。这些判断全部来自开始写代码前的那30秒思考。我当年第二场笔试最大的教训是太急着写代码导致版本号比较题漏了补零逻辑链表的环入口题差点返回成相遇点。后来我把“先确认边界再写代码最后补用例”这个流程固定下来之后的各家笔试再也没有因为粗心丢过分。希望你也能把这套思路用在下一场笔试里少踩一些我已经替你踩过的坑。
返回列表