你好,我是徐昊,今天我们来继续学习 AI 时代的软件工程。
上节课,我们展示了按照测试驱动开发(Test Driven Development,TDD)的节奏,与大语言模型(Large Language Model,LLM)结对编程(Pairing Programming)的过程。还展示了我们如何使用这样的方式,在保证质量的前提下兼顾速度,获得实打实的效率提升。
然而,我们上节课展示的例子非常的简单,是一个仅仅包含一个类的工具类代码。因而针对它的测试策略也非常的简单。换成更复杂的场景,我们就需要构建有效的测试策略,才能保证通过测试策略得到顺畅的开发节奏。
使用测试四象限构造测试策略
提到测试策略,很多人会不自觉地想到测试金字塔(Testing Pyramid)。这是 2009 年 Mike Cohn 在他的著作《Succedding with Agile》提到的一个隐喻,借助金字塔结构描述不同层次的测试。比如 Mike Cohn 自己就给出了一个三层的金字塔结构,分别对应单元测试(Unit Tests)、服务测试(Service Tests)和用户界面测试(User Interface Tests)。
然而测试金字塔这个隐喻,主要想说明的是自动化测试的分布,以及不同层之间的对应关系。良好的自动化测试,应该符合金字塔式的分布,也就是能够提供快速反馈的细粒度测试占据多数,而缓慢昂贵的粗粒度测试应该只有一小部分。
其实这里非常容易忽略的一点是:每当有一个处于金字塔上层的测试失败,必然有多个处于底层的测试失败。这才是测试金字塔的核心隐喻。下层的测试撑起上层的测试,而不是毫无关联的两套测试。
所以测试金字塔并不能帮助我们设计有效的测试策略,它只能帮我们检查我们的自动化测试集合是否处于良好状态,以及不同层之间的测试是否存在必要的关联。设计测试策略,测试四象限(Agile Testing Quadrants)是一个更好的框架和工具。
测试四象限是 2003 年由 Brian Marick 提出的,最开始的名字叫做 Marick 的测试矩阵(Marick’s Test Matrix)。Brian Marick 开创性地将不同种类的测试放置在不同的象限,用来说明它们的作用。后来这逐渐演化为广为人知的测试四象限:
首先我们可以看到,Brain Marick 选取了两个维度构成四象限,分别是测试的目的以及测试的受众。测试的目的被分成支持团队(Supporting Team)和评价产品(Critique Product),测试的受众被分成技术导向(Technology Facing)和业务导向(Business Facing)。
测试的受众很好理解,那么测试的目的是什么意思呢?所谓支持团队,是指测试主要目的是为开发团队提供反馈。而评价产品,则是指测试的主要目的是评估产品在不同维度上的表现。
比如登录系统的功能测试,它是团队的自动化回归测试(Regression Test)的一部分,同时它也可能是用户验收测试(UAT)的一部分。同样一个测试,它的目的是完全不同的。对于回归测试而言,它的目的主要是告诉团队功能是否被破坏;而对于用户验收测试而言,目的则是验证功能是否能满足用户的需要。因而我们在思考测试的时候,要综合考虑测试的目的与受众,而不是仅仅关注在具体的测试上。
然后,按照测试的目的与受众,可以自然地划分出四个象限:
第一象限(Q1),技术导向的支持团队的测试。这个象限中的测试是为交付团队中的技术人员提供快速反馈,是为了在组件和子系统的粒度上定位问题。常见的单元测试(Unit Testing)、组件测试(Component Testing)都属于这个象限;
第二象限(Q2),业务导向的支持团队的测试。这个象限中的测试是为交付团队提供关于业务的反馈,是根据验收条件(Acceptance Criteria)构造的各种测试。功能测试(Functional Testing)、用户故事测试(Story Testing)、示例说明(Specification by Example)都属于这个象限。需要注意,这些测试并不只对业务人员有用,对团队中所有人都非常重要;
第三象限(Q3),业务导向的评价产品的测试。这个象限中的测试评价的是产品是否能够提供业务价值。主要是从功能和用户交互的维度上评价。用户验收测试、探索性测试(Exploratory Testing)、可用性测试(Usabliltiy Testing)都属于这个象限;
第四象限(Q4),技术导向的评价产品的测试。这个象限中的测试评价的是产品的跨功能特性(Cross function requirements),比如安全性、性能、容量、负载等等。所以显而易见,性能测试(Performance Testing)、安全测试(Security Testing)等测试属于这个象限。
测试四象限可以从全局出发,帮助我们理解不同种类的测试到底发挥了什么作用。因此,测试四象限是承载测试策略的绝佳框架。而构造测试策略的过程,也就变成选择恰当的测试类型,分别放入不同象限的过程。
通过验收条件构造支持团队的测试
在使用四个象限构造测试策略时,评价产品的 Q3 和 Q4 象限是比较容易构造的。而支持团队的 Q1 和 Q2 象限则比较困难。
这一方面是因为绝大多数交付团队对于 Q3 与 Q4 象限的测试更加熟悉,另一方面也是因为 Q3 和 Q4 象限的测试相对正交,彼此之间不存在太大的关联。比如,Q4 象限的性能测试并不依赖于 Q3 象限中的 UAT、可用性测试或探索性测试。
而支持团队的 Q1 和Q2 象限的测试之间则存在更深的关联。当我们只有 Q2 象限的测试时,我们只知道出了问题,但不知道是哪个组件的问题;而当我们只有 Q1 象限的测试时,我们只知道组件出了问题,但不知道这会带来什么影响。
因而单纯地在团队中引入功能测试(Q2 象限)和单元测试(Q1 象限),并不能达到支持团队的效果。我们更需要的是,建立 Q1 和 Q2 象限测试间的关联,这是测试策略中的极为重要的一环。
那么怎么样才能有效地建立 Q1 和 Q2 象限测试的关联呢?有两个关键,一是验收条件,二是 TDD 的任务分解。
正如我们之前讲过的,验收条件是用户故事(User Story)的重要组成部分,用于帮助开发团队和客户明确产品或功能的期望表现、功能要求和可接受的标准。那么站在测试的角度上说,每一个验收条件,都可以对应一组功能测试(Q2 象限测试)。
TDD 的任务分解是将待开发的任务,分解为一组可测试的任务,这是测试驱动开发的核心。当我们同时使用验收条件澄清需求时,我们需要做的就是在验收条件的上下文中,按照不同的功能上下文,进行任务分解。
所谓功能上下文,可以是一个模块,一个组件,一个类(Class)或一个函数(function)。而通常我们使用测试驱动进行开发的时候,会持续对任务进行分解,直到功能上下文达到合适的粒度为止。因而站在测试的角度上说,每一个可测试的任务都对应着一组组件测试(Q1 象限测试)。
通过任务分解得到的 Q1 象限和 Q2 象限的测试必然存在内在的关联关系。当验收条件对应的测试失败时,必然意味着某个功能上下文中的功能不满足预期。那么该功能上下文中,必然有某个任务项没有做到位。那么该任务项对应的测试,必然也会失败。
不难发现,通过任务分解分解得到的 Q1 象限和 Q2 象限测试,还很容易满足测试金字塔的结构。因为处于金字塔上层的 Q2 象限测试失败,必然有多个处于底层的 Q1 象限测试失败。Q1 象限的测试撑起上层 Q2 象限的测试,而不是毫无关联的两套测试。
小结
当我们按照测试驱动开发的节奏与大语言模型结对编程时,构建支持团队的测试(Q1 象限和 Q2 象限)是至关重要的一步。
Q1 象限的测试能够撑起上层 Q2 象限的测试,而不是毫无关联的两套测试。通过验收条件和 TDD 的任务分解,能够帮我们有效建立起这两套测试的关联。
Q2 象限的测试,可以帮助我们验证 LLM 生成的代码是否满足验收条件的诉求;Q1 象限的测试则帮助我们聚焦到某个具体的功能上下文中,避免因 Token 限制带来的一系列麻烦。
那么现在的关键就在于功能上下文要如何划分。这是我们下节课要讨论的问题。
思考题
你觉得有哪些划分功能上下文的方法?
欢迎在留言区分享你的想法,我会让编辑置顶一些优质回答供大家学习讨论。