十六、代码校验(1)

本章概要

  • 测试
    • 单元测试
    • JUnit
    • 测试覆盖率的幻觉

你永远不能保证你的代码是正确的,你只能证明它是错的。

让我们先暂停编程语言特性的学习,看看一些代码基础知识。特别是能让你的代码更加健壮的知识。

测试

如果没有测试过,它就是不能工作的。

Java是一个静态类型的语言,程序员经常对一种编程语言明显的安全性感到过于舒适,“能通过编译器,那就是没问题的”。但静态类型检查是一种非常局限性的测试,只是说明编译器接受你代码中的语法和基本类型规则,并不意味着你的代码达到程序的目标。随着你代码经验的丰富,你逐渐了解到你的代码从来没有满足过这些目标。迈向代码校验的第一步就是创建测试,针对你的目标检查代码的行为。

单元测试

这个过程是将集成测试构建到你创建的所有代码中,并在每次构建系统时运行这些测试。这样,构建过程不仅能检查语法的错误,同时也能检查语义的错误。

“单元”是指测试一小部分代码 。通常,每个类都有测试来检查它所有方法的行为。“系统”测试则是不同的,它检查的是整个程序是否满足要求。

C 风格的语言,尤其是 C++,通常会认为性能比安全更重要。用 Java 编程比 C++(一般认为大概快两倍)快的原因是 Java 的安全性保障:比如垃圾回收以及改良的类型检测等特性。通过将单元测试集成到构建过程中,你扩大了这个安全保障,因而有了更快的开发效率。当发现设计或实现的缺陷时,可以更容易、更大胆地重构你的代码。

我自己的测试经历开始于我意识到要确保书中代码的正确性,书中的所有程序必须能够通过合适的构建系统自动提取、编译。这本书所使用的构建系统是 Gradle。 你只要在安装 JDK 后输入 gradlew compileJava,就能编译本书的所有代码。自动提取和自动编译的效果对本书代码的质量是如此的直接和引人注目,(在我看来)这会很快成为任何编程书籍的必备条件——你怎么能相信没有编译的代码呢? 我还发现我可以使用搜索和替换在整本书进行大范围的修改,如果引入了一个错误,代码提取器和构建系统就会清除它。

随着程序越来越复杂,我在系统中发现了一个严重的漏洞。编译程序毫无疑问是重要的第一步, 对于一本要出版的书而言,这看来是相当具有革命意义的发现(由于出版压力, 你经常打开一本程序设计的书会发现书中代码的错误)。但是,我收到了来自读者反馈代码中存在语义问题。当然,这些问题可以通过运行代码发现。我在早期实现一个自动化执行测试系统时尝试了一些不太有效的方式,但迫于出版压力,我明白我的程序绝对有问题,并会以 bug 报告的方式让我自食恶果。我也经常收到读者的抱怨说,我没有显示足够的代码输出。我需要验证程序的输出,并且在书中显示验证的输出。我以前的意见是读者应该一边看书一边运行代码,许多读者就是这么做的并且从中受益。然而,这种态度背后的原因是,我无法保证书中的输出是正确的。

从经验来看,我知道随着时间的推移,会发生一些事情,使得输出不再正确(或者一开始就不正确)。为了解决这个问题,我利用 Python 创建了一个工具(你将在下载的示例中找到此工具)。本书中的大多数程序都产生控制台输出,该工具将该输出与源代码清单末尾的注释中显示的预期输出进行比较,所以读者可以看到预期的输出,并且知道这个输出已经被构建程序验证过。

JUnit

最初的 JUnit 发布于 2000 年,大概是基于 Java 1.0,因此不能使用 Java 的反射工具。因此,用旧的 JUnit 编写单元测试是一项相当繁忙和冗长的工作。我发现这个设计令人不爽,并编写了自己的单元测试框架作为 注解 一章的示例。这个框架走向了另一个极端,“尝试最简单可行的方法”(极限编程中的一个关键短语)。从那之后,JUnit 通过反射和注解得到了极大的改进,大大简化了编写单元测试代码的过程。在 Java8 中,他们甚至增加了对 lambdas 表达式的支持。本书使用当时最新的 Junit5 版本

在 JUnit 最简单的使用中,使用 **@Test **** ** 注解标记表示测试的每个方法。JUnit 将这些方法标识为单独的测试,并一次设置和运行一个测试,采取措施避免测试之间的副作用。

让我们尝试一个简单的例子。CountedList 继承 ArrayList ,添加信息来追踪有多少个 CountedLists 被创建:

// validating/CountedList.java
// Keeps track of how many of itself are created.
package base;import java.util.*;public class CountedList extends ArrayList<String> {private static int counter = 0;private int id = counter++;public CountedList() {System.out.println("CountedList #" + id);}public int getId() {return id;}
}

标准做法是将测试放在它们自己的子目录中。测试还必须放在包中,以便 JUnit 能发现它们:

import java.util.*;
import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*;
public class CountedListTest {
private CountedList list;@BeforeAllstatic void beforeAllMsg() {System.out.println(">>> Starting CountedListTest");}@AfterAllstatic void afterAllMsg() {System.out.println(">>> Finished CountedListTest");}@BeforeEachpublic void initialize() {list = new CountedList();System.out.println("Set up for " + list.getId());for(int i = 0; i < 3; i++)list.add(Integer.toString(i));}@AfterEachpublic void cleanup() {System.out.println("Cleaning up " + list.getId());}@Testpublic void insert() {System.out.println("Running testInsert()");assertEquals(list.size(), 3);list.add(1, "Insert");assertEquals(list.size(), 4);assertEquals(list.get(1), "Insert");}@Testpublic void replace() {System.out.println("Running testReplace()");assertEquals(list.size(), 3);list.set(1, "Replace");assertEquals(list.size(), 3);assertEquals(list.get(1), "Replace");}// A helper method to simplify the code. As// long as it's not annotated with @Test, it will// not be automatically executed by JUnit.private void compare(List<String> lst, String[] strs) {assertArrayEquals(lst.toArray(new String[0]), strs);}@Testpublic void order() {System.out.println("Running testOrder()");compare(list, new String[] { "0", "1", "2" });}@Testpublic void remove() {System.out.println("Running testRemove()");assertEquals(list.size(), 3);list.remove(1);assertEquals(list.size(), 2);compare(list, new String[] { "0", "2" });}@Testpublic void addAll() {System.out.println("Running testAddAll()");list.addAll(Arrays.asList(new String[] {"An", "African", "Swallow"}));assertEquals(list.size(), 6);compare(list, new String[] { "0", "1", "2","An", "African", "Swallow" });}
}

在这里插入图片描述

如出现以下情况,需要把spring boot 的版本降至 3 以下:

在这里插入图片描述

<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId>
<!--        <version>3.1.2</version>--><version>2.7.5</version><relativePath/> <!-- lookup parent from repository -->
</parent>

注意:如果还是不行,可以重新创建一个 spring boot 3.0 以下的项目测试。

**@BeforeAll **** ** 注解是在任何其他测试操作之前运行一次的方法。 **@AfterAll **** ** 是所有其他测试操作之后只运行一次的方法。两个方法都必须是静态的。

**@BeforeEach **** 注解是通常用于创建和初始化公共对象的方法,并在每次测试前运行。可以将所有这样的初始化放在测试类的构造函数中,尽管我认为 **@BeforeEach ** ** 更加清晰。JUnit为每个测试创建一个对象,确保测试运行之间没有副作用。然而,所有测试的所有对象都是同时创建的(而不是在测试之前创建对象),所以使用 **@BeforeEach **** ** 和构造函数之间的唯一区别是 **@BeforeEach **** ** 在测试前直接调用。在大多数情况下,这不是问题,如果你愿意,可以使用构造函数方法。

如果你必须在每次测试后执行清理(如果修改了需要恢复的静态文件,打开文件需要关闭,打开数据库或者网络连接,etc),那就用注解 **@AfterEach **** **。

每个测试创建一个新的 CountedListTest 对象,任何非静态成员变量也会在同一时间创建。然后为每个测试调用 initialize() ,于是 list 被赋值为一个新的用字符串“0”、“1” 和 “2” 初始化的 CountedList 对象。观察 **@BeforeEach **** ** 和 **@AfterEach **** ** 的行为,这些方法在初始化和清理测试时显示有关测试的信息。

insert()replace() 演示了典型的测试方法。JUnit 使用 **@Test **** ** 注解发现这些方法,并将每个方法作为测试运行。在方法内部,你可以执行任何所需的操作并使用 JUnit 断言方法(以"assert"开头)验证测试的正确性(更全面的"assert"说明可以在 Junit 文档里找到)。如果断言失败,将显示导致失败的表达式和值。这通常就足够了,但是你也可以使用每个 JUnit 断言语句的重载版本,它包含一个字符串,以便在断言失败时显示。

断言语句不是必须的;你可以在没有断言的情况下运行测试,如果没有异常,则认为测试是成功的。

compare() 是“helper方法”的一个例子,它不是由 JUnit 执行的,而是被类中的其他测试使用。只要没有 **@Test **** ** 注解,JUnit 就不会运行它,也不需要特定的签名。在这里,compare() 是私有方法 ,表示仅在测试类中使用,但他同样可以是 public 。其余的测试方法通过将其重构为 compare() 方法来消除重复的代码。

本书使用 build.gradle 控制测试,运行本章节的测试,使用命令:gradlew validating:test,Gradle 不会运行已经运行过的测试,所以如果你没有得到测试结果,得先运行:gradlew validating:clean

可以用下面这个命令运行本书的所有测试:

gradlew test

尽管可以用最简单的方法,如 CountedListTest.java 所示那样,JUnit 还包括大量的测试结构,你可以到官网上学习它们。

JUnit 是 Java 最流行的单元测试框架,但也有其它可以替代的。你可以通过互联网发现更适合的那一个。

测试覆盖率的幻觉

测试覆盖率,同样也称为代码覆盖率,度量代码的测试百分比。百分比越高,测试的覆盖率越大。

对于没有知识但处于控制地位的人来说,很容易在没有任何了解的情况下也有概念认为 100% 的测试覆盖是唯一可接受的值。这有一个问题,因为 100% 并不意味着是对测试有效性的良好测量。你可以测试所有需要它的东西,但是只需要 65% 的覆盖率。如果需要 100% 的覆盖,你将浪费大量时间来生成剩余的代码,并且在向项目添加代码时浪费的时间更多。

当分析一个未知的代码库时,测试覆盖率作为一个粗略的度量是有用的。如果覆盖率工具报告的值特别低(比如,少于百分之40),则说明覆盖不够充分。然而,一个非常高的值也同样值得怀疑,这表明对编程领域了解不足的人迫使团队做出了武断的决定。覆盖工具的最佳用途是发现代码库中未测试的部分。但是,不要依赖覆盖率来得到测试质量的任何信息。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/101355.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

计算机毕业设计选什么题目好?springboot智慧养老中心管理系统

✍✍计算机编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java实战 |…

Response Status Code 301、302

目录 Information Django redirect Influence Information HTTP状态码301、302和304分别表示以下情况&#xff1a; codeinformation301&#xff08;Moved Permanently&#xff09; 永久重定向。当请求的资源已经被永久地移动到了一个新的URI时&#xff0c;服务器会返回这个…

VUEX的基础使用存值及异步

目录 什么是VUEX 有什么作用 安装 取值 异步 什么是VUEX VUEX 是一个用于状态管理的状态容器模式&#xff08;state management pattern&#xff09;库&#xff0c;用于 Vue.js 应用程序。它允许你在应用程序中集中管理和共享状态&#xff0c;并提供了一组用于更改状态的规则…

[安洵杯 2019]easy_web - RCE(关键字绕过)+md5强碰撞+逆向思维

[安洵杯 2019]easy_web 1 解题流程1.1 阶段一1.2 阶段二2 思考总结1 解题流程 1.1 阶段一 1、F12发现提示md5 is funny ~;还有img标签中,有伪协议和base64编码 2、url地址是index.php?img=TXpVek5UTTFNbVUzTURabE5qYz0&cmd=   这就有意思了,这里的img明显是编码后的…

C++ 命名空间-----namespace(超详细解析,小白必看系列)

目录 一、前言 &#x1f34e;什么是C 二、关键字 三、命名空间&#xff08;重点&#xff09; &#x1f350;C语言--命名冲突 &#x1f349;C--命名空间的定义 &#x1f353;C--命名空间的使用 四、C输入&输出 五、共勉 一、前言 既博主学过C语言后又一新的语言&a…

大厂设计师力推的14款平面图设计工具!

从事设计行业的工人或多或少会接触到平面图。例如&#xff0c;在建造新房、办公室、酒店等任何类型的建筑时&#xff0c;都需要使用平面图来保证项目的准确性。因此&#xff0c;掌握绘制平面图软件的技巧也非常重要。在保证效率的同时&#xff0c;结果的准确性也非常高。在本文…

seata分布式事务理论概述

分布式事务产生的原因&#xff1a; 数据库分库分表 应用的SOA化。就是业务的服务化(面向服务架构) 分布式事务的解决方案&#xff1a; 1、两阶段提交协议2PC 这里的两阶段提交和redolog binlog的两阶段提交不是一个东西&#xff0c;redo log和bin log的两阶段提交保证的是…

Vue2 Watch的语法

Watch语法 一、监听普通数据类型&#xff08;1&#xff09;把要监听的msg值看作方法名&#xff0c;来进行监听。&#xff08;2&#xff09;把要监听的msg值看作对象&#xff0c;利用hanler方法来进行监听 二、监听对象&#xff1a;&#xff08;1&#xff09;监听对象需要用到深…

Python算法练习 10.11

leetcode 394 字符串解码 给定一个经过编码的字符串&#xff0c;返回它解码后的字符串。 编码规则为: k[encoded_string]&#xff0c;表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。 你可以认为输入字符串总是有效的&#xff1b;输入字符串中没…

c语言练习85:通讯录的实现(基于顺序表实现)

通讯录的实现(基于顺序表实现&#xff09; 基于动态顺序表实现通讯录 C语⾔基础要求&#xff1a;结构体、动态内存管理、顺序表、⽂件操作 1、功能要求 1&#xff09;⾄少能够存储100个⼈的通讯信息 2&#xff09;能够保存⽤⼾信息&#xff1a;名字、性别、年龄、电话、地址…

飞凌嵌入式受邀参加「NXP创新技术论坛」

2023年10月10日&#xff0c;「NXP创新技术论坛」在深圳湾万丽酒店举行&#xff0c;飞凌嵌入式作为NXP金牌合作伙伴受邀参加此次论坛&#xff0c;与众多智能工业行业的伙伴深入交流市场趋势与行业洞察&#xff0c;共同促进未来市场的发展。 本次论坛&#xff0c;飞凌嵌入式展示了…

ansible 调研

参考&#xff1a;自动化运维工具——ansible详解&#xff08;一&#xff09; - 珂儿吖 - 博客园 (cnblogs.com) ansible是新出现的自动化运维工具&#xff0c;基于Python开发&#xff0c;集合了众多运维工具&#xff08;puppet、chef、func、fabric&#xff09;的优点&#xf…

使用Plotly模拟远古博弈游戏_掷骰子

不乏投资大师、量化基金经理从着迷博弈游戏开始迈出步伐...... 开始学习使用python包Plotly模拟掷骰子。 安装Plotly 终端输入命令&#xff1a;python3 -m pip install --user plotly 创建骰子类 掷骰子 分析结果 绘制直方图 投掷一个骰子点数的分布 投掷两个骰子1000次的点数…

【QT】Ubuntu 编译安装 QT 5.12.7 源码

直接通过源码来编译安装 QT&#xff0c;难度太大&#xff0c;耗时较长&#xff0c;一般不是特别推荐使用源码安装。 目录 1、下载 QT 源码包 2、搭建安装环境(下载依赖库) 3、编译QT源码的脚本 1、下载 QT 源码包 QT5.12.7源码下载地址: download | QT 5.12.7 选择任意一…

spring boot+ vue+ mysql开发的一套厘米级高精度定位系统源码

UWB室内高精度定位系统源码&#xff0c;自主版权演示 UWB技术最核心的能力就是精准的定位与测距&#xff0c;当然它还具备通信功能。不过&#xff0c;目前主流通信技术已经相当成熟&#xff0c;无需UWB兼顾去做通信传输。而且&#xff0c;如果使用UWB通信功能&#xff0c;反而会…

多媒体播放软件 Infuse mac中文特点介绍

Infuse mac是一款多媒体播放器应用&#xff0c;它支持播放多种格式的视频文件、音频文件和图片文件&#xff0c;并且可以通过AIrPlay将媒体内容投放到其他设备上。Infuse还支持在线视频流媒体播放和本地网络共享&#xff0c;用户可以通过它来访问家庭网络上的媒体文件。 Infuse…

什么是React的虚拟DOM(Virtual DOM)?它的作用是什么?

聚沙成塔每天进步一点点 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 欢迎来到前端入门之旅&#xff01;感兴趣的可以订阅本专栏哦&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发…

Linux是什么,其特点是啥

与大家熟知的 Windows 操作系统软件一样&#xff0c;Linux 也是一个操作系统软件&#xff0c;其 logo 是一只企鹅。与 Windows 不同之处在于&#xff0c;Linux 是一套开放源代码程序的、可以自由传播的类 Unix 操作系统软件。Linux&#xff0c;全称GNU/Linux&#xff0c;是一种…

JAXB 使用记录 bean转xml xml转bean 数组 继承

JAXB 使用记录 部分内容引自 https://blog.csdn.net/gengzhy/article/details/127564536 基础介绍 JAXBContext类&#xff1a;是应用的入口&#xff0c;用于管理XML/Java绑定信息 Marshaller接口&#xff1a;将Java对象序列化为XML数据 Unmarshaller接口&#xff1a;将XML数…

Spark入门

Spark 1.Spark概述 2.Spark特点 3.RDD概述 1. Spark概述 什么是Spark 回顾&#xff1a;Hadoop主要解决&#xff0c;海量数据的存储和海量数据的分析计算。 Spark是一种基于内存的快速、通用、可扩展的大数据分析计算引擎。 Hadoop与Spark历史 Hadoop与Spark框架对比 Dr…