十六、代码校验(1)

news2024/11/18 12:39:53

本章概要

  • 测试
    • 单元测试
    • 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;
	@BeforeAll
    static void beforeAllMsg() {
        System.out.println(">>> Starting CountedListTest");
    }
    
    @AfterAll
    static void afterAllMsg() {
        System.out.println(">>> Finished CountedListTest");
    }
    
    @BeforeEach
    public 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));
    }
    
    @AfterEach
    public void cleanup() {
    	System.out.println("Cleaning up " + list.getId());
    }
    
    @Test
    public void insert() {
        System.out.println("Running testInsert()");
        assertEquals(list.size(), 3);
        list.add(1, "Insert");
        assertEquals(list.size(), 4);
        assertEquals(list.get(1), "Insert");
    }
    
    @Test
    public 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);
    }
    
    @Test
    public void order() {
        System.out.println("Running testOrder()");
        compare(list, new String[] { "0", "1", "2" });
    }
    
    @Test
    public void remove() {
    	System.out.println("Running testRemove()");
    	assertEquals(list.size(), 3);
    	list.remove(1);
    	assertEquals(list.size(), 2);
    	compare(list, new String[] { "0", "2" });
    }
    
    @Test
    public 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.coloradmin.cn/o/1082134.html

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

相关文章

EPLAN_003#常用功能(三)

一、图形编辑 按空格键结束 二、对象捕捉、设计模式 设计模式类似于CAD中 基于基准点 复制等功能 三、比例缩放、拉伸、修剪、修改长度、圆角、倒角 四、标注 五、窗口宏、符号宏&#xff08;在编辑菜单中&#xff0c;或者右键&#xff09; 快捷键&#xff1a;CTRLF5 打开的时候…

Matlab之数组、包含分配给类别的值函数categorical

一、功能 categorical 是为一组有限的离散类别&#xff08;例如 High、Med 和 Low&#xff09;赋值的数据类型。这些类别可以采用您指定的数学排序&#xff0c;例如 High > Med > Low&#xff0c;但这并非必须。分类数组可用来有效地存储并方便地处理非数值数据&#xf…

2023-10-11 LeetCode每日一题()

2023-10-11每日一题 一、题目编号 2512. 奖励最顶尖的 K 名学生二、题目链接 点击跳转到题目位置 三、题目描述 给你两个字符串数组 positive_feedback 和 negative_feedback &#xff0c;分别包含表示正面的和负面的词汇。不会 有单词同时是正面的和负面的。 一开始&…

ROS学习笔记(六)---服务通信机制

1. 服务通信是什么 在ROS中&#xff0c;服务通信机制是一种点对点的通信方式&#xff0c;用于节点之间的请求和响应。它允许一个节点&#xff08;服务请求方&#xff09;向另一个节点&#xff08;服务提供方&#xff09;发送请求&#xff0c;并等待响应。 服务通信机制在ROS中…

极坐标和直角坐标的积分转换

如下图所示&#xff0c;当 θ 不变&#xff0c; ρ 增大为 ρ Δ ρ ( 即 ρ 1 ) 时 \theta不变&#xff0c;\rho增大为\rho \Delta \rho(即\rho_1)时 θ不变&#xff0c;ρ增大为ρΔρ(即ρ1​)时, 面积的增量为&#xff1a; 1 2 θ ( ρ Δ ρ ) 2 − 1 2 θ ρ 2 θ ρ…

第二章 进程与线程 十九、死锁的概念

目录 一、定义 二、死锁、饥饿和死循环的区别 三、死锁的必要条件 四、死锁的处理策略 五、总结 一、定义 死锁是指两个或多个进程等待对方释放自己所持有的资源&#xff0c;导致所有进程都被阻塞&#xff0c;无法继续执行。这种情况可能会导致系统瘫痪&#xff0c;需要通…

(二)实现Bean属性依赖注入功能【手撸Spring】

一、前言 在上一篇手撸Spring之实现一个简易版IoC容器&#xff0c;我们先把最简单的IoC架子搭了起来&#xff0c;即实现了一个Bean的注入&#xff0c;体会了依赖反转的过程。这里提到的依赖反转&#xff0c;大家肯定非常耳熟了&#xff0c;也就是将业务对Bean的依赖反转了&…

【网络】网络编程——带你手搓简易TCP服务端(echo服务器)+客户端(四种版本)

这里写自定义目录标题 前言正式开始用生活中的例子来讲解TCP服务端和客户端代码讲解服务端基本框架创建套接字 bindlisten监听accept接收连接通信单线程版多进程①版多进程②版多线程版线程池版 客户端 收尾 前言 本篇主要讲解套接字编程&#xff0c;以TCP服务端和客户端为主…

MySQL为什么用b+树

索引是一种数据结构&#xff0c;用于帮助我们在大量数据中快速定位到我们想要查找的数据。 索引最形象的比喻就是图书的目录了。注意这里的大量&#xff0c;数据量大了索引才显得有意义&#xff0c;如果我想要在[1,2,3,4]中找到4这个数据&#xff0c;直接对全数据检索也很快&am…

SP605官方开发板不能扫到链的问题

很早之前的板子&#xff0c;近些天需要重新搞FPGA&#xff0c;所以又拿出来&#xff0c;应该以前都是在win7下开发&#xff0c;现在都win10了&#xff0c;vivado都不支持sp6&#xff0c;所以先得下载一个14.7版本&#xff0c;但是出现了新的问题&#xff0c;就是不能扫到链。 …

windows认证机制_NTLM

文章目录 概念本地认证 NTLMLMNTML原理网络认证 Net NTLM实际测试利用hash传递浏览上传⽂件使用 mimikatz hash传递获取域控RDPmimikatz工具使用案例 概念 原文参考&#xff1a;xiu --》http://www.xiusafe.com/ 域渗透就是基于 windows 域环境的渗透&#xff0c;而域渗透涉及…

第二证券:中概股,昨夜狂飙!

当地时间10月10日&#xff0c;美股三大股指集体收高&#xff0c;其间道指涨0.40%&#xff0c;标普指数涨0.52%&#xff0c;纳斯达克指数涨0.58%。多位美联储官员鸽派讲话支撑不再加息&#xff0c;投资者等待美联储9月会议纪要、美国CPI通胀数据及企业三季报等要素支撑美股持续走…

基于Docker来部署Nacos的注册中心

基于Docker来部署Nacos的注册中心 准备MySQL数据库表nacos.sql&#xff0c;用来存储Nacos的数据。 最终表结构如下&#xff1a; 在本地nacos/custom.env文件中&#xff0c;有一个MYSQL_SERVICE_HOST也就是mysql地址&#xff0c;需要修改为你自己的虚拟机IP地址&#xff1a; …

铝型材公司【Brilliance Group】申请1080万美元纳斯达克IPO上市

来源&#xff1a;猛兽财经 作者&#xff1a;猛兽财经 猛兽财经获悉&#xff0c;总部位于新加坡的铝型材公司【Brilliance Group】近期已向美国证券交易委员会&#xff08;SEC&#xff09;提交招股书&#xff0c;申请在纳斯达克IPO上市&#xff0c;股票代码为(KHIW),Brilliance…

python爬虫练手项目之获取某地企业名录

因为很多网站都增加了登录验证&#xff0c;所以需要添加一段利用cookies跳过登陆验证码的操作 import pandas as pd import requests from lxml import etree # 通过Chrome浏览器F12来获取cookies&#xff0c;agent&#xff0c;headers cookies {ssxmod_itna2:eqfx0DgQGQ0QGDC…

python每日一练(5)

&#x1f308;write in front&#x1f308; &#x1f9f8;大家好&#xff0c;我是Aileen&#x1f9f8;.希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流. &#x1f194;本文由Aileen_0v0&#x1f9f8; 原创 CSDN首发&#x1f412; 如…

网页报错 Uncaught TypeError: a(...).tooltip is not a function

网页报错 Uncaught TypeError: a(…).tooltip is not a function 网页上f12看了一下控制台有一个报错 &#xff1a;Uncaught TypeError: a(…).tooltip is not a function 排查查找了一下原因&#xff0c;是网页嵌套&#xff0c;报错原因是引入了两次jquery&#xff0c;注释掉…

数学建模、统计建模、计量建模整体框架的理解以及建模的步骤

数学建模、统计建模、计量建模整体框架的理解以及建模的步骤 引言正文模型的设定模型的估计建模中可能遇到的四种数据类型 模型的检验模型的应用 最后 引言 这篇博客主要写给统计或者数学专业的小白&#xff0c;以供快速上手建模比赛&#xff1b;本人将在这里整合参加建模比赛…

基于nodemailer实现邮件发送

概述 node中可用nodemailer实现邮件的发送。本文使用QQ邮箱实现邮件的发送。 实现效果 实现 1. QQ邮箱配置 首先需要开启POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV服务&#xff0c;如下图所示。 生成授权码 2. 发送邮件 发送邮件的代码比较简单&#xff0c;如下&#xf…

197、管理 RabbitMQ 的虚拟主机

开启Rabbitmq的一些命令&#xff1a; 小黑窗输入&#xff1a; rabbitmq-plugins enable rabbitmq_management 启动控制台插件&#xff0c; 就是启动登录rabbitmq控制台的页面&#xff0c;rabbitmq_management 代表了RabbitMQ的管理界面。 rabbitmq-server 启动rabbitMQ服务器…