当你一边说“我又不是测试人员”,一边准备跳过本章时,请先停下来想一想。事实上,测试对于软件工程师来说是一项重要的工作,因此 ,在面试中可能会出现测试问题。当然,如果你正在申请测试的工作(或者测试开发工程师),那么你就更有理由注意这方面的问题。
测试问题通常可以分为四类:(1)测试真实世界的对象(比如一支笔);(2)测试一个软件; (3)编写一个功能的测试代码; (4)对存在的问题进行故障排除。下面我们将介绍这四类情况的解决方法。
请记住,这四种情况下你最好不要假设输入或者用户会正常表现。对滥用的情况有心里准备,并为此做好计划。
从表面上看,测试问题似乎只是在提出大量测试用例。在某种程度上,这是对的。你确实需要拿出一个合理的测试用例列表。
但除此之外,面试官还想测试以下几个方面:
-
全局理解:你是否了解软件的真正含义? 你能否正确确定测试用例的优先级? 例如,假设你被要求测试一个像 Amazon 这样的电商系统。确保在正确的位置显示产品图片非常重要,但更重要的是,付款应能可靠地工作,将产品添加到发货队列中,并且客户永远不会被重复收费。
-
了解各个部分如何组合在一起:你是否了解软件是如何工作的,以及如何将其融入更大的生态系统中? 假设要求你测试 Google Spreadsheets,对打开、保存和编辑文档进行测试很重要。但是,Google Spreadsheets 是更大的软件生态的一部分。你需要测试它与 Gmail、插件以及其他组件的集成。
-
组织:你是用一种结构化的方式来处理问题,还是对你想到的任何东西只是脱口而出?有些求职者,当被要求给出一个相机的测试用例时,只会想到什么就说什么。一个好的候选人会将这些部分细分成几类,比如拍照、图像管理、设置等等。这种结构化方法还将帮助你更全面地创建测试用例。
-
实用性:你真的能创建合理的测试计划吗?例如,如果用户报告说,当他们打开特定的图像时,软件崩溃了,而如果你只是告诉他们要重新安装软件,这通常是不太实际的。你的测试计划对于公司而言必须切实可行的。
展示这些方面将证明你有能力成为测试团队中有价值的一员。
有些应聘者被问到诸如如何测试一支笔之类的问题时会感到惊讶。毕竟,你应该测试软件,对吗?也许吧,但这些“现实世界”的问题仍然很常见。让我们通过一个例子来完成这个过程。
问题:你如何测试一个回形针?
你需要与面试官讨论谁正在使用该产品,以及出于什么目的。答案可能不是你想的那样。答案可能是“老师,为了把文件放在一起”,也可以是“艺术家,为了弯曲成动物的形状”,或者是两者兼有。这个问题的答案将决定你如何处理剩下的问题。
对你来说,列出用例列表是很有用的。在当前情况下,用例可能只是简单地以不损坏纸张的方式将纸张固定在一起。
对于其他问题,可能有多个用例。例如,产品可能需要能够发送和接收内容,或者编写和删除内容,等等。
使用范围可能意味着在一次使用中最多可持有 30 张纸而不会造成永久性损坏(例如,弯曲),以及 30 至 50 张纸只允许产生最小程度的永久性弯曲。
这个范围也可以扩展到到环境方面的因素。例如,回形针在非常温暖的温度(90~110 华氏度)下能够工作吗?那极度寒冷的情况下呢?
没有产品是防故障(fail-proof)的,所以分析故障条件需要成为测试的一部分。和你的面试官好好谈谈什么时候产品故障是可以接受的(甚至是必要的),以及故障的含义是什么。
例如,如果要测试一台洗衣机,则可能会决定该洗衣机至少应能处理 30 件衬衫或裤子。装载 30~45 件衣服可能会导致轻微故障,例如衣服没有被充分清洗。如果衣服超过 45 件,极端的故障可能是可以接受的。但是,这种情况下的极端故障可能只是意味着机器永远不会打开水工作,这当然不应该意味着洪水或火灾。
在某些情况下,讨论执行测试的细节可能也很重要。例如,如果你需要确保一把椅子可以正常使用五年,你可能不能把它放在家里等五年。相反,你需要定义什么是“正常”使用(每年有多少人“坐在”这个座位上?扶手呢?)然后,除了进行一些手动测试之外,你可能还需要一台机器来自动化执行某些使用。
测试软件实际上与测试现实世界对象非常相似。主要区别在于,软件测试通常更加注重执行测试的细节。
请注意,软件测试有两个核心方面:
-
手动测试与自动化测试:在理想的情况下,我们可能希望使所有内容实现自动化,但这几乎是不可行的。有些东西用手工测试会更好,因为有些特性太定性,计算机无法有效地检查(比如内容是否是代表色情)。此外,尽管计算机通常只能识别被告知要查找的问题,但人类的观察可能会发现尚未专门检查的新问题。人和计算机都是测试过程的重要组成部分。
-
黑盒测试与白盒测试:这一区别指的是我们对软件的访问程度。在黑盒测试中,我们只是按原样获得该软件,并需要对其进行测试。通过白盒测试,我们可以通过其他编程方式来测试各个功能。我们还可以自动化一些黑盒测试,尽管这肯定要困难得多。
让我们从头到尾逐步介绍一种解决方法。
虽然这个问题通常可以推迟到后面的步骤,但我喜欢尽早解决它。询问面试官你是在做黑盒测试还是白盒测试,或者两者都做。
软件通常具有一个或多个目标用户,并且在设计功能时考虑了这一点。例如,如果要求你在 Web 浏览器上测试用于家长控制的软件,则目标用户既包括父母(正在实施件屏蔽),也包括孩子(作为件屏蔽的接收者)。你可能还会有“客人”(既不应实施也不应该接受件屏蔽的人)。
在软件屏蔽场景中,针对父母的用例包括安装软件、更新控件、删除控件,当然还有他们自己的网络使用情况。对于儿童,用例包括访问合法内容和“非法”内容。
请记住,并不是由你来神奇地决定用例。这应该是你和面试官对话的结果。
现在我们已经定义了模糊的用例,我们需要弄清楚这到底意味着什么。一个网站被屏蔽意味着什么?应该只屏蔽“非法”页面还是整个网站?应用程序应该“学习”什么是不良内容,还是基于白名单或黑名单?如果它想知道什么是不合适的内容,什么程度的误报(false positives)或漏报(false negatives)是可以接受的?
当软件发生故障时(发生故障是不可避免的),故障应该是什么样的呢?显然,软件故障不应该使计算机崩溃。相反,软件应该只允许一个被屏蔽的站点,或者禁止一个被允许的站点。在后一种情况下,你可能需要讨论使用父母密码进行选择性覆盖的可能性。
这就是手动测试和自动测试、黑盒测试和白盒测试之间的区别真正发挥作用的地方。
step 3 和 4 应该大致定义了用例。在 step 6 中,我们进一步定义它们,并讨论如何执行测试。你测试的具体情况是什么?这些步骤中哪些可以自动化?哪些需要人工干预?
请记住,虽然自动化可以让你进行一些非常强大的测试,但它也有一些明显的缺点。手动测试通常应该是测试程序的一部分。
当你浏览这张清单时,不要一口气说出你能想到的每一个场景。它是杂乱无章的,你肯定会错过主要类别。相反,应该以结构化的方式处理这个问题。将你的测试分解为主要组件,然后从那里开始。这样你不仅可以给出一个更完整的测试用例列表,而且还会显示出你是一个结构化、有条理的人。
在许多方面,测试一个功能是最简单的测试类型。由于测试通常仅限于验证输入和输出,所以对话通常更简短,也不那么模糊。
但是,请不要忽略与面试官交谈的价值。您应该与面试官讨论任何假设,特别是关于如何处理特定情况。
假设你被要求编写代码来测试 sort(int[] array),该方法对整数数组进行排序。你可以按照以下步骤进行。
通常,你应该考虑以下类型的测试用例:
-
正常情况:它为典型输入生成正确的输出吗?记住,这里要考虑潜在的问题。例如,由于排序通常需要某种类型的划分,因此可以合理地认为该算法可能会在元素数量为奇数的数组上失败,因为它们无法均匀划分。你的测试用例应列出这两个例子。
-
极端情况:传入空数组时会发生什么?或者一个非常小的(一个元素)数组?如果你传入一个很大的呢?
-
Null 和“非法”输入:当给定非法输入时,代码应该如何表现是值得考虑的。例如,如果你测试一个可以生成第n个斐波那契数的功能,那么你的测试用例应该包括 n 为负的情况。
-
奇怪的输入:有时会出现第四种输入:奇怪的输入。当传入一个已经排序的数组时会发生什么?或者是一个倒序排列的数组?
生成这些测试确实需要你了解正在编写的功能。如果你不清楚这些限制,你需要先问面试官。
通常,预期的结果是显而易见的:正确的输出。然而,在某些情况下,你可能希望验证其他方面。例如,如果 sort 方法返回数组的新排序副本,则你可能应该验证原始数组是否没有被修改。
一旦定义了测试用例和结果,编写用于实现测试用例的代码就应该非常简单。你的代码可能是这样的:
1 void testAddThreeSorted() {
2 Mylist list = new Mylist();
3 list.addThreeSorted(3, 1, 2); // Adds 3 items in sorted order
4 assertEquals(list.getElement(0), 1);
5 assertEquals(list.getElement(1), 2);
6 assertEquals(list.getElement(2), 3);
7 }
最后一种问题是说明如何调试或解决现有问题。许多候选人都对这样的问题不屑一顾,给出了不切实际的答案,例如“重新安装软件”。实际上你可以像对待其他事情一样,用一种结构化的方式来处理这些问题。
让我们通过一个例子来解决这个问题:当你在 Google Chrome 团队中工作时,收到了一个bug报告:Chrome 在启动时崩溃。你会怎么做?
重新安装浏览器可能会解决该用户的问题,但对其他可能遇到相同问题的用户没有帮助。你的目标应该是了解实际发生了什么,以便开发人员可以对其进行修复。
你应该做的第一件事是问问题,尽可能多地了解情况。
-
用户遇到这个问题有多久了?
-
这是什么版本的浏览器?什么操作系统?
-
这个问题经常发生吗?或者多久发生一次?什么时候发生?
-
是否有启动错误报告?
既然你已经了解了该场景的详细信息,您就可以将问题分解为可测试的单元。在这种情况下,你可以想象情况的流程如下:
-
进入 Windows 开始菜单。
-
单击 Chrome 图标。
-
浏览器实例启动。
-
浏览器加载设置。
-
浏览器发出 HTTP 主页请求。
-
浏览器获得 HTTP 响应。
-
浏览器解析网页。
-
浏览器显示内容。
在此过程中的某个时刻,某些操作会失败,并导致浏览器崩溃。一个强大的测试人员将遍历可能导致此场景的各个因素以诊断问题。
以上每个组件都应具有切合实际的说明——你可以要求用户执行的操作或可以自己执行的操作(例如,在自己的计算机上复制步骤)。在现实世界中,你将与客户打交道,并且你不能给他们下达他们不能或不愿做的指示。。
-
11.1 错误(Mistake):找出下列代码中的错误:
unsigned int i; for (i = 100; i >= 0; --i) printf("%d\n", i);
提示:#257, #299, #362
-
11.2 随机崩溃(Random Crashes):给定一个在运行时崩溃的应用源代码。在调试器中运行十次之后,你会发现它不会在同一个地方崩溃。应用程序是单线程的,并且只使用 C 标准库。哪些编程错误可能导致这个崩溃?你将如何测试每一个?
提示:#325
-
11.3 象棋测试(ChessTest):我们在国际象棋游戏中使用以下方法:
boolean canMoveTo(int x, int y)
。此方法是 Piece 类的一部分,并返回棋子是否可以移动到位置(x, y)
。解释如何测试这个方法。提示:#329, #401
-
11.4 没有测试工具(No Test Tools):不使用任何测试工具,如何对网页进行负载测试?
提示:#313, #345
-
11.5 测试笔(Test a Pen):你将如何测试一支笔?
提示:#140, #164, #220
-
11.6 测试ATM(Test an ATM):你将如何测试分布式银行系统中的 ATM ?
提示:#210, #225, #268, #349, #393
提示从第 662 页开始。