当前位置: 首页 > news >正文

【JUnit实战3_11】第六章:关于测试的质量(下)

JUnit in Action, Third Edition

《JUnit in Action》全新第3版封面截图

写在前面
都说开卷有益,这一章给我的感受颇深。关于作者提到的每一个基本原则,建议大家结合之前工作中的相关场景进行理解,会有不一样的体会。本章最后提到的变异测试虽然没有过多展开,但通过自行查阅相关资料,也解决了之前困扰我很久的一个问题:测试用例用于验证代码逻辑是否正确,但用例本身的质量又通过什么来衡量呢?这就是变异测试试图回答的终极命题,也让我对测试这一领域更加心存敬畏。和我一起来一探究竟吧。

第六章 测试质量(下)

(接上篇)

原则三:构造函数要尽量简单

(详见 上篇笔记)


原则四:遵循 Demeter 法则(最少知识原则)

一个类应当仅了解它必须知道的内容。

其他常见表述:

  • Talk to your immediate friends.
  • Don’t talk to strangers.
// before
class Car {private Driver driver;Car(Context context) {this.driver = context.getDriver();}
}// after
Car(Driver driver) {this.driver = driver;
}

切记:

直接限定对象,而不是中转搜寻对象;只获取程序必需的对象(引入 context 就是个反例)。

小知识:Miško Hevery 社会化类比

将类之间的关系比作社交关系,提倡类之间应该像社交场景下的独立个体一样,保持 清晰的边界最小化依赖

要点提炼:

  • 明确职责边界:每个类应该像社会中的个人一样,有自己明确的职责和能力范围
  • 最小化社交圈:类之间的依赖关系要尽可能少,就像一个人不需要认识全社会的人也能正常工作
  • 自给自足:类应该尽可能独立完成自己的任务,而不是过度依赖其他类
  • 契约化协作:类之间通过清晰的接口(契约)进行协作,而不是紧密耦合

换言之,A 与 B 相熟,但与 C 都不熟,那么 C 就不应该知道 AB 之间共享的信息。这是最理想的情况。但如果 A 刻意隐瞒与 C 也认识,那么这种平衡就会打破,关键信息就可能在看不见的隐秘途径间传递,从而加大测试难度。

原则五:避免隐藏依赖和全局状态

// before
public void makeReservation() {Reservation reservation = new Reservation();reservation.makeReservation();
}
public class Reservation {public void makeReservation() {manager.initDatabase(); //manager is a reference to a global//DBManager, already initialized//require the global DBManager to do more action}
}// after
public void makeReservation() {DBManager manager = new DBManager();manager.initDatabase();Reservation reservation = new Reservation(manager);reservation.makeReservation();
}

切记:引入全局变量时并非仅仅引入该变量本身,也包括与它存在依赖关系的 所有业务逻辑

原则六:优先考虑泛型方法

在最为核心的底层业务逻辑中,如果大量使用通用的静态方法,会给测试造成极大干扰,导致大量的测试冗余代码,也不利于测试逻辑的植入。

相比于静态方法,更推荐的做法是利用 OOP 的多态特性,为后续测试提供更多的 接合点/连接点(articulation points,即多采用 父类接口 + 子类实现 的多态设计构建各功能模块。这样一来,后续测试只需要创建一个针对测试的子类实现就能轻松替换原逻辑,具备更好的可扩展性和可维护性。

相比之下,静态方法的灵活性就差了很多,因为缺乏有效的连接点,不得不在测试用例中大量重复调用静态方法,也无法灵活切换到测试场景下。

// before
public static Set union(Set s1, Set s2) {Set result = new HashSet(s1);result.addAll(s2);return result;
}// after
public static <E> Set<E> union(Set<E> s1, Set<E> s2) {Set<E> result = new HashSet<>(s1);result.addAll(s2);return result;
}

原则七:优先考虑组合而非继承

继承仅在满足 IS-A 关联关系时考虑使用。

最佳实践:应当尽量让代码在运行时保持最大的灵活性。
这样就能确保对象状态之间的切换变得容易,从而使代码更易于测试。

令人感到讽刺的是,Java 的内部库也存在违反这一原则的情况:

  • Stack 继承了 Vector:按理 Stack 又不是 VectorIS-A 不成立),不该继承;
  • Properties 继承了 Hashtable:同上,IS-A 关系也不成立,也不该继承。

原则八:优先使用多态而非条件判断

直接上代码:

// before
public class DocumentPrinter {// snippublic void printDocument() {switch (document.getDocumentType()) {case WORD_DOCUMENT:printWORDDocument();break;case PDF_DOCUMENT:printPDFDocument();break;case TEXT_DOCUMENT:printTextDocument();break;default:printBinaryDocument();break;}}// snip
}// after:
public class DocumentPrinter {// snippublic void printDocument(Document document) {document.printDocument();}
}
public abstract class Document {// snippublic abstract void printDocument();
}
public class WordDocument extends Document{// snippublic void printDocument() {printWORDDocument();}
}
public class PDFDocument extends Document {// snippublic void printDocument() {printPDFDocument();}
}
public class TextDocument extends Document {// snippublic void printDocument() {printTextDocument();}
}

6.5 测试驱动开发简介

概念:测试驱动开发(TDD 是这样一种编程实践:它强调 测试先行,然后编写代码以通过这些测试;接着审查代码并 适当重构,以进一步改进设计。

TDD 旨在产出 可胜任工作的整洁代码

TDD 让测试用例成为待测方法的 第一位用户,与传统的流程(先编写待测方法,然后再测试验证该方法)不同。

传统开发流程:

[code, test, (repeat)]

TDD 开发流程:

[test, code, (repeat)]

现实中的真实流程还应加入 重构

[test, code, _refactor_, (repeat)]

TDD 两步法:

  • 编写新代码之前先编写一个失败的测试;
  • 编写能让失败测试通过的最少量代码。

6.6 JUnit 最佳实践

先编写未通过的测试(Write failing tests first

初始的未通过状态,表明此时还没有正确实现既定的业务逻辑。一旦养成习惯,开发新模块时就会先写测试再写功能模块,然后重构、优化。该过程熟练后,测试通过的时候,往往也是该模块大功告成的时候。

6.7 行为驱动开发简介

概念:行为驱动开发(Behavior-driven Testing 是这样一种开发手段:它强调 直接满足业务需求,其核心理念是由业务战略、需求和目标驱动,并将这些因素提炼、转化为最终的 IT 解决方案。

BDD 旨在构建值得构建的软件,并以解决用户痛点为己任。

软件的商业价值究竟体现在什么地方?
BDD 给出的回答是:体现在能够胜任实际工作的 功能特性(features 上。功能特性 是可交付、有形的碎片化功能,可有效帮助相关业务达成商业目的。

为实现业务目标——

  • 业务分析师会与客户协作,确定所需的软件功能,例如:
    • 从多个备选路线中,为用户提供某种方法直达目的地;
    • 为客户提供最优路径抵达目的地的方式。
  • 然后基于用户视角,将这些功能拆分为具体的叙事(stories)逻辑,例如:
    • 寻找中转次数最少的路线;
    • 寻找用时最少的路线。

这些叙事逻辑需要用具体的测试用例进行描述,并最终转化为用户叙事的验收标准。

BDD 固定句式:

  • Given:假定……成立
  • When:当满足……条件时
  • Then:则(应当满足)……(断言结果)

6.8 变异测试简介

100% 的代码覆盖率并不意味着你的代码就完美无瑕了——测试代码可能还不够好。

最极端的情况:在测试中略过所有断言。
例如,输出结果过于复杂难以验证,只能将其打印或写入日志,让其他人来决定怎么处理。

变异测试(Mutation testing:又称 变异分析(mutation analysis程序变异(program mutation,主要用于设计新的软件测试、评估现有软件测试的质量。

变异测试的目的在于 检验测试质量,并确保测试符合预期

基本原理:对程序 P 进行细微修改。每个修改后的版本 M 称为 P变体(mutantP 则是 M父体(parent;原始版本的行为与变异体不同,测试执行需要检测并拒绝这些变异体,这个过程称为 杀死变异体(killing the mutant。测试套件的编写质量可通过其被杀变异体的占比来衡量,也可以设计新的测试用例来杀死更多变异体。

注意

变异测试的内容已超纲,书中并未详细展开。具体情况可参考普渡大学计算机科学教授 Aditya P. Mathur 教授编著的 Foundations of Software Testing / 2e(2013 年 5 月出版)。

根据该书定义,将一个 活跃变体(live mutant 同其父体程序区分开的过程,又称作 杀死(killing 一个变体。

原文:Note that distinguishing a mutant from its parent is sometimes referred to as killing a mutant.

考虑下列简化程序逻辑:

if(!a) {b = 1;
} else {b = 2;
}

一个强大的变异测试满足以下条件:

  • 测试到达了变异后的 if 条件;
  • 测试会沿着与初始正确分支不同的分支继续执行;
  • 变更后的 b 值会传播到程序输出结果中,并被测试所验证;
  • 由于该方法返回了错误的 b 值,测试终将失败。

编写良好的测试必须能判定变异测试的失败,从而证明它们最初覆盖了必要的逻辑条件。

最有名的 Java 变异测试框架是 Pitest(https://pitest.org/)。

6.9 开发周期中的测试

为了考察不同类型的测试在整个开发周期中的分布情况,作者将整个开发周期(带 CI/CD 工作流)划分为四个核心阶段(开发阶段、集成阶段、验收压力测试阶段、预生产阶段),并给出了各阶段的特点和对应测试类型的主要任务。

以下是开发周期的四个核心阶段:

接着是不同测试类型基于上述分类的分布情况:

其中左下角的准生产环境虽然过于理想,但对于开发者建立完备的知识结构还是有益处的。作者也承认很多公司可能没有如此理想的阶段划分,甚至出于各种现实因素考虑不得不简化甚至取消整个测试环节(躺枪)。但从开发者个人发展的长远考虑看,还是应该从自身做起,重视测试环节。

JUnit 最佳实践:持续的回归测试

这里揭示了软件功能持续迭代的真实过程:利用代码的可复用性,新功能的开发总是建立在微调现有功能模块的基础上的。有了 JUnit 单元测试,微调对原来的功能特性造成的影响可以很快得到响应,因为这些测试用例是可以自动运行的。用变更前的测试用例来防范新变更产生的问题,其实也是回归测试的一种。任何类型的测试都可以用作回归测试,但最基础且最有效的防御手段,还是 在每次变更后运行单元测试

至于单元测试本身的可靠性如何,可以通过上节的变异测试进行定量评估。

http://www.dtcms.com/a/525755.html

相关文章:

  • 最新选题-基于Spark的二氧化碳排放量数据分析系统设计
  • 438.找到字符串中所有字母异位词
  • 鞍山手机网站设计广东省室内设计公司排名
  • 适合seo优化的网站制作网站鼠标特效
  • 【无标题】叽叽喳喳
  • 多线程六脉神剑第四剑:读写锁 (ReaderWriterLockSlim)
  • 网站设关键字wordpress搭建问答系统
  • 泉州高端网站建设微信h5免费制作网站模板下载
  • 第13章-人员管理
  • Maya Python入门:属性连接connectAttr()、创建节点createNode()
  • Java学习之旅第三季-17:Lambda表达式
  • 企业电子商务网站建设和一般建设网站收费标准
  • 【深度学习】深度学习核心:优化与正则化超详细笔记
  • 南昌做网站哪个好如何做好网站推广工作
  • 网站网速慢网站正在建设中_敬请期待
  • 影刀:自动化测试网页应用
  • 做彩票网站要什么接口只放一个图片做网站
  • git重写历史
  • 免费下载app软件网站寻找网站建设公司
  • 动易手机网站外贸商城源码
  • 简述网站建设流程中的各个步骤wordpress破解主题商务
  • 2025年--Lc213-2000. 反转单词前缀-Java版
  • safari针对带有loading=lazy属性img的无奈
  • 需求上线部署流程
  • wap网站生成微信小程序网页微信版官网登录仅传输文件
  • php 数据录入网站网站设计制作公司
  • 网络与信息安全基础
  • 权重的网站网站建设一般多少钱网址
  • Why is it called “callback function“
  • axios响应发生错误时的情况列表