你好,我的名字叫Ben Anderson,我是Visual C++库团队的测试软件设计工程师,目前负责测试ATL、MFC、C运行时、ATL服务器、OpenMP和其他一些技术。 正如我目前被分配到QA上的一些我们的新的MFC Vista的特点,今天我想跟大家谈谈我们在QA中遇到的最大的挑战之一,比如VisualC++的产品,就是规模。
为了确保产品的质量,我们维护了越来越大的自动化测试用例套件,以验证我们提供的函数和类的行为是否符合预期。 对于我们产生的每一个新特性,我们都会添加大量的新测试用例,这意味着多年来,我们的测试套件已经发展到了巨大的规模。 为了给我们一个具体的例子,我们讨论的范围是,我们为维护VisualStudio 2005(PrtTf,Cut,STL等)在维护的Visual Studio(Coprf,Cut,STL等)中所维护的C-RunTimes和标准C++库套件,为CRT的单一配置产生了超过9000个单独的结果。 再乘以3个目标处理器架构(x86、x64和安腾)、两个交叉编译器(在x86机器上运行但生成x64或安腾代码的编译器)、6个不同的链接 选项(MT,MTD,MD,MDd,MD,静态链接的C++ LIB,MDD与静态C++),三个不同的运行时目标(原生,CLR和CLR:纯),预JIT(NGEDE)和非PREJIT二进制文件,以及五代OSES(Windows 98, 2000,XP,2003服务器和Vista),以及无数的语言目标(美国英语和日语是我们的主要检查点),所有这些都必须交叉检查,以确保每个选项或配置相互配合。 其中一些配置的运行时间超过24小时,并且有时会以visualstudio和windowsvista的日常版本的形式移动目标,仅仅保持实验室自动化的正常运行并能够推动测试运行本身就是一个重大的挑战。 此外,一旦我们有了结果,即使只有很小的一部分测试失败了,仍然需要大量的人力来追踪失败的原因,无论是必须修复的产品问题、测试用例本身的错误,还是暂时性的问题,例如设置不正确的机器,或防病毒软件与文件访问发生冲突,以防止文件被删除。 最后,开发能够在如此广泛的平台上产生有效结果的新测试用例是一项具有挑战性的任务——即使没有问题需要解决,在所有平台上运行一个测试用例也是一项耗时的任务。 这只适用于CRT和SCL。 ATL、MFC、ATL Server和OpenMP都有自己的一组独立关注点。
我们有很多方法试图使这样规模的测试易于管理,我们总是在寻找新的解决方案。 为了降低复杂性,只要有可能,我们就尽可能多地“交叉覆盖”。 这意味着我们可以尝试同时运行两个或多个变量的排列,这样我们就可以合理地确信我们已经找到了有趣的场景并且没有留下漏洞。 例如,如果我们需要两个操作系统(比如windowsxp和windows2003server)和三个运行时(native、CLR和CLR:Pure),为了得到一个完整的矩阵,我们就必须运行testcase2×3=6次。 但是,如果我们确信每次点击native、CLR和CLR:Pure是重要的,而且XP和2003 Server之间的差异对于CLR来说并不显著,那么我们可以选择只运行三次XP/native、XP/CLR和2003/CLR:Pure,以减少运行时和可能重复的分析工作。 这种覆盖率和运行时数量的权衡是有效的,但必须非常小心地进行,以避免遗漏问题和在覆盖率中留下漏洞。
我们使用的另一种方法是在签入新测试或对旧测试进行更改时使用“挑战”系统。 这意味着,每当有人想要签入对我们的测试源的更改时,他们都会将更改提交给一个服务器,该服务器在更改之前和之后在多台机器上运行测试,这些机器将在各种支持的配置下运行测试。 如果变更后的故障比变更前多,则拒绝变更,并将结果报告给提交的工程师进行修复。 这样的系统在保护签入测试的质量方面非常有用,但同时也会增加维护负担,使所有计算机保持正常运行,并在可用时添加新的受支持目标,或添加新的测试套件以覆盖新功能。 使用这样一个系统来保持我们的测试套件的健壮性是很重要的,因为即使测试套件的质量稍有下降也会导致运行时和分析成本的大幅增加(考虑一个挂起或破坏机器状态的tescase)。 还有许多有趣的方法可以用来扩展这样一个系统,使其变得更有用(比如检查testcases中的最佳实践,或者向可执行文件中注入故障条件,以确保利用将正确地报告故障),但是,添加特性会增加维护负担,这会让你陷入和当初一样的麻烦——处理天平。
任何长期项目都面临的一个规模问题是,何时削减或放弃对不再常用的特性的支持,转而将精力花在更划算的项目上。 这是一个问题,考试不必面对其他学科,但随着时间的推移,它变得越来越重要。 在一定程度上,继续运行为您的产品开发的每个测试用例更容易,当然也更安全,而且随着运行时间的延长,只需在执行时抛出更多的机器。 这对运行分析没有任何帮助,尽管机器比新员工便宜,但是它们需要额外的人员来监视和修复机器的问题,同时也需要花费金钱。 在某一点上,有必要削减开支。 虽然承认这一点很痛苦,但并不是每一个精心制作的测试用例都能为我们的产品提供独特的、甚至有用的覆盖范围。 一些测试用例甚至以测试用例bug的形式造成了比捕获产品bug更大的麻烦,因为给定测试用例的质量可能太差,以至于需要不断的维护,而且永远不会捕获一个产品问题! 显然,在这种情况下,最好关闭这些测试,而不是一遍又一遍地运行它们,从而导致不必要的工作。 问题是确定我们数以万计的测试用例中的哪一个导致了问题。 理想情况下,我们希望生成一个测试用例列表,这些测试用例在将来永远不会为我们发现产品问题。 由于我们还没有找到一种生成这样一个列表的方法(微软还没有开发出时间旅行),我们已经提出了几个其他的度量标准来衡量我们的测试用例的有效性。 我们最好的测试用例是这样的:a)提供独特的代码覆盖率(我们对代码进行测试,以确定在执行每个单独的测试用例时,产品中的哪些代码被命中),b)我们必须修复的测试用例代码中从未有过bug,c)过去发现过产品问题(最好是很多问题)。 然后,我们根据测试用例满足的类别对测试用例的优先级进行排序。 既然我们不知道一个从来没有发现问题的测试用例在将来找不到问题,我们不想完全放弃测试用例,但是能够选择一些我们知道在过去既有效又可靠的测试用例来做时间敏感的测试或者在测试过程开始时嗅出潜在的问题是很好的。 测试用例不提供唯一的覆盖范围,在他们的代码中有很多需要修复的bug,并且从来没有发现过bug,这些测试用例也可以在评审时删除。
最后,为了减少最痛苦的瓶颈——分析失败的结果——我们开发了一些工具来帮助我们的测试工程师处理失败的测试用例,并快速确定其根本原因。 我们使用的工具最有用的特性之一是能够将产品或测试用例bug与失败的结果相关联,并标记出已知这些bug会影响哪些测试用例的配置。 这意味着一旦分析了一个失败,就不必每次新运行testcase时都重新检查它,直到与它相关联的bug被标记为已解决,此时如果测试失败,bug或者没有被修复,或者testcase因为其他必须被识别的问题而失败。 我们的工具还允许测试工程师标记他们正在调查的测试用例失败,以避免重复工作,甚至通过解析常见消息的失败日志来进行一些基本的自动分析。 这些工具极大地帮助了我们的努力,当它们无法使用时,我们真的感到痛苦。
好的,我希望能在处理C++工具箱中开发一个开发人员工具箱的基础上,给出一个很好的总结。 我们一直在寻找处理这些问题的新方法,并希望在未来进一步改进我们的技术。 如果您有任何问题或建议,请随时与我联系 benjaman@microsoft.com .
本·安德森 VisualC++图书馆团队