十六、 代码校验(3)

news2024/11/26 22:48:52

本章概要

  • 测试驱动开发
    • 测试驱动 vs 测试优先
  • 日志
    • 日志信息
    • 日志等级

测试驱动开发

之所以可以有测试驱动开发(TDD)这种开发方式,是因为如果你在设计和编写代码时考虑到了测试,那么你不仅可以写出可测试性更好的代码,而且还可以得到更好的代码设计。 一般情况下这个说法都是正确的。 一旦我想到“我将如何测试我的代码?”,这个想法将使我的代码产生变化,并且往往是从“可测试”转变为“可用”。

纯粹的 TDD 主义者会在实现新功能之前就为其编写测试,这称为测试优先的开发。 我们采用一个简易的示例程序来进行说明,它的功能是反转 String 中字符的大小写。 让我们随意添加一些约束:String 必须小于或等于30个字符,并且必须只包含字母,空格,逗号和句号(英文)。

此示例与标准 TDD 不同,因为它的作用在于接收 StringInverter 的不同实现,以便在我们逐步满足测试的过程中来体现类的演变。 为了满足这个要求,将 StringInverter 作为接口:

// validating/StringInverter.java
package validating;

interface StringInverter {
	String invert(String str);
}

现在我们通过可以编写测试来表述我们的要求。 以下所述通常不是你编写测试的方式,但由于我们在此处有一个特殊的约束:我们要对 **StringInverter **多个版本的实现进行测试,为此,我们利用了 JUnit5 中最复杂的新功能之一:动态测试生成。 顾名思义,通过它你可以使你所编写的代码在运行时生成测试,而不需要你对每个测试显式编码。 这带来了许多新的可能性,特别是在明确地需要编写一整套测试而令人望而却步的情况下。

JUnit5 提供了几种动态生成测试的方法,但这里使用的方法可能是最复杂的。 **DynamicTest.stream() **方法采用了:

  • 对象集合上的迭代器 (versions) ,这个迭代器在不同组的测试中是不同的。 迭代器生成的对象可以是任何类型,但是只能有一种对象生成,因此对于存在多个不同的对象类型时,必须人为地将它们打包成单个类型。
  • Function,它从迭代器获取对象并生成描述测试的 String
  • Consumer,它从迭代器获取对象并包含基于该对象的测试代码。

在此示例中,所有代码将在 testVersions() 中进行组合以防止代码重复。 迭代器生成的对象是对 DynamicTest 的不同实现,这些对象体现了对接口不同版本的实现:

import org.junit.jupiter.api.*;

import java.util.*;
import java.util.function.*;
import java.util.stream.*;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

class DynamicStringInverterTests {
    // Combine operations to prevent code duplication:
    Stream<DynamicTest> testVersions(String id,
                                     Function<StringInverter, String> test) {
        List<StringInverter> versions = Arrays.asList(
                new Inverter1(), new Inverter2(),
                new Inverter3(), new Inverter4());
        return DynamicTest.stream(
                versions.iterator(),
                inverter -> inverter.getClass().getSimpleName(),
                inverter -> {
                    System.out.println(
                            inverter.getClass().getSimpleName() +
                                    ": " + id);
                    try {
                        if (test.apply(inverter) != "fail")
                            System.out.println("Success");
                    } catch (Exception | Error e) {
                        System.out.println(
                                "Exception: " + e.getMessage());
                    }
                }
        );
    }

    String isEqual(String lval, String rval) {
        if (lval.equals(rval))
            return "success";
        System.out.println("FAIL: " + lval + " != " + rval);
        return "fail";
    }

    @BeforeAll
    static void startMsg() {
        System.out.println(
                ">>> Starting DynamicStringInverterTests <<<");
    }

    @AfterAll
    static void endMsg() {
        System.out.println(
                ">>> Finished DynamicStringInverterTests <<<");
    }

    @TestFactory
    Stream<DynamicTest> basicInversion1() {
        String in = "Exit, Pursued by a Bear.";
        String out = "eXIT, pURSUED BY A bEAR.";
        return testVersions(
                "Basic inversion (should succeed)",
                inverter -> isEqual(inverter.invert(in), out)
        );
    }

    @TestFactory
    Stream<DynamicTest> basicInversion2() {
        return testVersions(
                "Basic inversion (should fail)",
                inverter -> isEqual(inverter.invert("X"), "X"));
    }

    @TestFactory
    Stream<DynamicTest> disallowedCharacters() {
        String disallowed = ";-_()*&^%$#@!~`0123456789";
        return testVersions(
                "Disallowed characters",
                inverter -> {
                    String result = disallowed.chars()
                            .mapToObj(c -> {
                                String cc = Character.toString((char) c);
                                try {
                                    inverter.invert(cc);
                                    return "";
                                } catch (RuntimeException e) {
                                    return cc;
                                }
                            }).collect(Collectors.joining(""));
                    if (result.length() == 0)
                        return "success";
                    System.out.println("Bad characters: " + result);
                    return "fail";
                }
        );
    }

    @TestFactory
    Stream<DynamicTest> allowedCharacters() {
        String lowcase = "abcdefghijklmnopqrstuvwxyz ,.";
        String upcase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ ,.";
        return testVersions(
                "Allowed characters (should succeed)",
                inverter -> {
                    assertEquals(inverter.invert(lowcase), upcase);
                    assertEquals(inverter.invert(upcase), lowcase);
                    return "success";
                }
        );
    }

    @TestFactory
    Stream<DynamicTest> lengthNoGreaterThan30() {
        String str = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
        assertTrue(str.length() > 30);
        return testVersions(
                "Length must be less than 31 (throws exception)",
                inverter -> inverter.invert(str)
        );
    }

    @TestFactory
    Stream<DynamicTest> lengthLessThan31() {
        String str = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
        assertTrue(str.length() < 31);
        return testVersions(
                "Length must be less than 31 (should succeed)",
                inverter -> inverter.invert(str)
        );
    }
}

在一般的测试中,你可能认为在进行一个结果为失败的测试时应该停止代码构建。 但是在这里,我们只希望系统报告问题,但仍然继续运行,以便你可以看到不同版本的 StringInverter 的效果。

每个使用 **@TestFactory ** 注释的方法都会生成一个 DynamicTest 对象的 Stream(通过 testVersions() ),每个 JUnit 都像常规的 **@Test ** 方法一样执行。

现在测试都已经准备好了,我们就可以开始实现 **StringInverter **了。 我们从一个仅返回其参数的假的实现类开始:

// validating/Inverter1.java
package validating;
public class Inverter1 implements StringInverter {
	public String invert(String str) { return str; }
}

接下来我们实现反转操作:

// validating/Inverter2.java
package validating;
import static java.lang.Character.*;
public class Inverter2 implements StringInverter {
	public String invert(String str) {
		String result = "";
		for(int i = 0; i < str.length(); i++) {
			char c = str.charAt(i);
			result += isUpperCase(c) ?
					  toLowerCase(c) :
					  toUpperCase(c);
		}
		return result;
	}
}

现在添加代码以确保输入不超过30个字符:

// validating/Inverter3.java
package validating;
import static java.lang.Character.*;
public class Inverter3 implements StringInverter {
	public String invert(String str) {
		if(str.length() > 30)
			throw new RuntimeException("argument too long!");
		String result = "";
		for(int i = 0; i < str.length(); i++) {
			char c = str.charAt(i);
			result += isUpperCase(c) ?
					  toLowerCase(c) :
					  toUpperCase(c);
        }
		return result;
	}
}

最后,我们排除了不允许的字符:

// validating/Inverter4.java
package validating;
import static java.lang.Character.*;
public class Inverter4 implements StringInverter {
	static final String ALLOWED =
		"abcdefghijklmnopqrstuvwxyz ,." +
		"ABCDEFGHIJKLMNOPQRSTUVWXYZ";
	public String invert(String str) {
		if(str.length() > 30)
			throw new RuntimeException("argument too long!");
		String result = "";
		for(int i = 0; i < str.length(); i++) {
			char c = str.charAt(i);
			if(ALLOWED.indexOf(c) == -1)
				throw new RuntimeException(c + " Not allowed");
			result += isUpperCase(c) ?
					  toLowerCase(c) :
					  toUpperCase(c);
		}
		return result;
	}
}

在这里插入图片描述

你将从测试输出中看到,每个版本的 Inverter 都几乎能通过所有测试。 当你在进行测试优先的开发时会有相同的体验。

DynamicStringInverterTests.java 仅是为了显示 TDD 过程中不同 StringInverter 实现的开发。 通常,你只需编写一组如下所示的测试,并修改单个 StringInverter 类直到它满足所有测试:

// validating/tests/StringInverterTests.java
package validating;
import java.util.*;
import java.util.stream.*;
import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*;

public class StringInverterTests {
	StringInverter inverter = new Inverter4();
	@BeforeAll
	static void startMsg() {
		System.out.println(">>> StringInverterTests <<<");
	}
    @Test
	void basicInversion1() {
		String in = "Exit, Pursued by a Bear.";
		String out = "eXIT, pURSUED BY A bEAR.";
		assertEquals(inverter.invert(in), out);
	}
	@Test
	void basicInversion2() {
		expectThrows(Error.class, () -> {
			assertEquals(inverter.invert("X"), "X");
		});
	}
	@Test
	void disallowedCharacters() {
		String disallowed = ";-_()*&^%$#@!~`0123456789";
		String result = disallowed.chars()
			.mapToObj(c -> {
				String cc = Character.toString((char)c);
				try {
					inverter.invert(cc);
					return "";
				} catch(RuntimeException e) {
					return cc;
				}
			}).collect(Collectors.joining(""));
		assertEquals(result, disallowed);
	}
	@Test
	void allowedCharacters() {
		String lowcase = "abcdefghijklmnopqrstuvwxyz ,.";
		String upcase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ ,.";
		assertEquals(inverter.invert(lowcase), upcase);
		assertEquals(inverter.invert(upcase), lowcase);
	}
	@Test
	void lengthNoGreaterThan30() {
		String str = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
		assertTrue(str.length() > 30);
		expectThrows(RuntimeException.class, () -> {
			inverter.invert(str);
		});
	}
    @Test
	void lengthLessThan31() {
		String str = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
		assertTrue(str.length() < 31);
		inverter.invert(str);
	}
}

在这里插入图片描述

你可以通过这种方式进行开发:一开始在测试中建立你期望程序应有的所有特性,然后你就能在实现中一步步添加功能,直到所有测试通过。 完成后,你还可以在将来通过这些测试来得知(或让其他任何人得知)当修复错误或添加功能时,代码是否被破坏了。 TDD的目标是产生更好,更周全的测试,因为在完全实现之后尝试实现完整的测试覆盖通常会产生匆忙或无意义的测试。

测试驱动 vs 测试优先

虽然我自己还没有达到测试优先的意识水平,但我最感兴趣的是来自测试优先中的“测试失败的书签”这一概念。 当你离开你的工作一段时间后,重新回到工作进展中,甚至找到你离开时工作到的地方有时会很有挑战性。 然而,以失败的测试为书签能让你找到之前停止的地方。 这似乎让你能更轻松地暂时离开你的工作,因为不用担心找不到工作进展的位置。

纯粹的测试优先编程的主要问题是它假设你事先了解了你正在解决的问题。 根据我自己的经验,我通常是从实验开始,而只有当我处理问题一段时间后,我对它的理解才会达到能给它编写测试的程度。 当然,偶尔会有一些问题在你开始之前就已经完全定义,但我个人并不常遇到这些问题。 实际上,可能用“面向测试的开发 ( Test-Oriented Development )”这个短语来描述编写测试良好的代码或许更好。

日志

日志信息

在调试程序中,日志可以是普通状态数据,用于显示程序运行过程(例如,安装程序可能会记录安装过程中采取的步骤,存储文件的目录,程序的启动值等)。

在调试期间,日志也能带来好处。 如果没有日志,你可能会尝试通过插入 println() 语句来打印出程序的行为。 本书中的一些例子使用了这种技术,并且在没有调试器的情况下(下文中很快就会介绍这样一个主题),它就是你唯一的工具。 但是,一旦你确定程序正常运行,你可能会将 println() 语句注释或者删除。 然而,如果你遇到更多错误,你可能又需要运行它们。因此,如果能够只在需要时轻松启用输出程序状态就好多了。

程序员在日志包可供使用之前,都只能依赖 Java 编译器移除未调用的代码。 如果 debug 是一个 **static final boolean **,你就可以这么写:

if(debug) {
	System.out.println("Debug info");
}

然后,当 **debug **为 **false **时,编译器将移除大括号内的代码。 因此,未调用的代码不会对运行时产生影响。 使用这种方法,你可以在整个程序中放置跟踪代码,并轻松启用和关闭它。 但是,该技术的一个缺点是你必须重新编译代码才能启用和关闭跟踪语句。因此,通过更改配置文件来修改日志属性,从而起到启用跟踪语句但不用重新编译程序会更方便。

业内普遍认为标准 Java 发行版本中的日志包 (java.util.logging) 的设计相当糟糕。 大多数人会选择其他的替代日志包。如 Simple Logging Facade for Java(SLF4J) ,它为多个日志框架提供了一个封装好的调用方式,这些日志框架包括 java.util.logginglogback 和 **log4j **。 SLF4J 允许用户在部署时插入所需的日志框架。

SLF4J 提供了一个复杂的工具来报告程序的信息,它的效率与前面示例中的技术几乎相同。 对于非常简单的信息日志记录,你可以执行以下操作:

import org.slf4j.*;

public class SLF4JLogging {
    private static Logger log = LoggerFactory.getLogger(SLF4JLogging.class);

    public static void main(String[] args) {
        log.info("hello logging");
    }
}

在这里插入图片描述

日志输出中的格式和信息,甚至输出是否正常或“错误”都取决于 SLF4J 所连接的后端程序包是怎样实现的。 在上面的示例中,它连接到的是 logback 库(通过本书的 build.gradle 文件),并显示为标准输出。

日志系统会检测日志消息处所在的类名和方法名。 但它不能保证这些名称是正确的,所以不要纠结于其准确性。

日志等级

SLF4J 提供了多个等级的日志消息。下面这个例子以“严重性”的递增顺序对它们作出演示:

import org.slf4j.*;

public class SLF4JLevels {
    private static Logger log =
            LoggerFactory.getLogger(SLF4JLevels.class);

    public static void main(String[] args) {
        log.trace("Hello");
        log.debug("Logging");
        log.info("Using");
        log.warn("the SLF4J");
        log.error("Facade");
    }
}

在这里插入图片描述

你可以按等级来查找消息。 级别通常设置在单独的配置文件中,因此你可以重新配置而无需重新编译。 配置文件格式取决于你使用的后端日志包实现。 如 logback 使用 XML :

<!-- validating/logback.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
	<appender name="STDOUT"
		class="ch.qos.logback.core.ConsoleAppender">
		<encoder>
			<pattern>
%d{yyyy-MM-dd'T'HH:mm:ss.SSS}
[%thread] %-5level %logger - %msg%n
			</pattern>
		</encoder>
	</appender>
	<root level="TRACE">
		<appender-ref ref="STDOUT" />
	</root>
</configuration>

你可以尝试将 **<root level =“TRACE"> **行更改为其他级别,然后重新运行该程序查看日志输出的更改情况。 如果你没有写 logback.xml 文件,日志系统将采取默认配置。

这只是 SLF4J 最简单的介绍和一般的日志消息,但也足以作为使用日志的基础 - 你可以沿着这个进行更长久的学习和实践。你可以查阅 SLF4J 文档来获得更深入的信息。

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

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

相关文章

计算机导论实验——Linux基础入门

使用Xshell登录 Linux 主机 linux命令&#xff1a; cd&#xff1a;去哪里 pwd&#xff1a;在哪里 ls&#xff1a;查看当前有什么文件 mkdir&#xff1a;创建新目录 cp&#xff1a;复制 cat&#xff1a;连接或显示文件 rm&#xff1a;删除 mv&#xff1a;用于移动或重命名文件…

B站视频“多模态大模型,科大讯飞前NLP专家串讲”记录

文章目录 多模态&#xff1a;对齐 -- align迁移学习和zero-shotClipBlip 多模态&#xff1a; 图片、文字、视频、语音等不同的表征。 表示信息的方式有多种&#xff0c;但是不同的表示方式携带的信息不完全相同。 对齐 – align 如第一个图中&#xff0c;文字内容的描述和图…

关于一篇什么是JWT的原理与实际应用

目录 一.介绍 1.1.什么是JWT 二.结构 三.Jwt的工具类的使用 3.1. 依赖 3.2.工具类 3.3.过滤器 3.4.控制器 3.5.配置 3.6. 测试类 用于生成JWT 解析Jwt 复制jwt&#xff0c;并延时30分钟 测试JWT的有效时间 测试过期JWT的解析 四.应用 今天就到这了&#xff0c;希…

基于SpringBoot的网上订餐系统

基于SpringBoot的网上订餐系统的设计与实现 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringBootMyBatisVue工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 【主要功能】 角色&#xff1a;用户、管理员管理员&#xff1a;登录、个人中心、会员管理、…

【Unity】【VR】详解Oculus Integration输入

【背景】 以下内容适用于Oculus Integration开发VR场景,也就是OVR打头的Scripts,不适用于OpenXR开发场景,也就是XR打头Scripts。 【详解】 OVR的Input相对比较容易获取。重点在于区分不同动作机制的细节效果。 OVR Input的按键存在Button和RawButton两个系列 RawButton…

MATLAB神经网络和优化算法

文章目录 1. matlab感知器神经网络初步学习2 使用建立好的神经网络进行分类程序3 线性神经网络预测程序4 BP神经网络信号拟合程序 1. matlab感知器神经网络初步学习 %% 学习目标&#xff1a;从学习第一个最简单的神经网络案例开启学习之路 %% 感知器神经网络 用于点的分类…

orgChart.js组织架构图

OrgChart.js是什么&#xff1f; 基于ES6的组织结构图插件。 特征 支持本地数据和远程数据&#xff08;JSON&#xff09;。 基于CSS3过渡的平滑扩展/折叠效果。 将图表对齐为4个方向。 允许用户通过拖放节点更改组织结构。 允许用户动态编辑组织图并将最终层次结构保存为…

c语言表达式求值--整型提升

什么是整型提升&#xff1f; C的整型算术运算总是至少以缺省整型类型的精度来进行的。 为了获得这个精度&#xff0c;表达式中的字符和短整型操作数在使用之前被转换为普通整型&#xff0c;这种转换称为整型提升。 什么叫缺省整数类型&#xff1f;缺省在计算机里面是默认的意…

第三章 内存管理 一、内存的基础知识

目录 一、什么是内存 二、有何作用 三、常用数量单位 四、指令的工作原理 五、装入方式 1、绝对装入 2、可重定位装入&#xff08;静态重定位&#xff09; 3、动态运行时装入&#xff08;动态重定位&#xff09; 六、从写程序到程序运行 七、链接的三种方式 1、静态…

MySQL建表操作和用户权限

1.创建数据库school&#xff0c;字符集为utf8 mysql> create database school character set utf8; 2.在school数据库中创建Student和Score表 mysql> create table school.student( -> Id int(10) primary key, -> Stu_id int(10) not null, -> C_n…

服务运营 |摘要:学术+业界-近期前沿运筹医疗合作精选

推文作者&#xff1a;李舒湉 编者按 本文归纳整理了近期INFORMS Journal on Applied Analytics中的相关业界合作研究。 这些研究成果体现了运筹学在医疗健康领域实践的效果。文中的学术业界合作使用了不同的研究工具。第一篇文章使用仿真模型帮助诊所进行不同拥挤程度下诊所使用…

【Java学习之道】日期与时间处理类

引言 在前面的章节中&#xff0c;我们介绍了Java语言的基础知识和核心技能&#xff0c;现在我们将进一步探讨Java中的常用类库和工具。这些工具和类库将帮助我们更高效地进行Java程序开发。在本节中&#xff0c;我们将一起学习日期与时间处理类的使用。 一、为什么需要日期和…

并发编程——1.java内存图及相关内容

这篇文章&#xff0c;我们来讲一下java的内存图及并发编程的预备内容。 首先&#xff0c;我们来看一下下面的这两段代码&#xff1a; 下面&#xff0c;我们给出上面这两段代码在运行时的内存结构图&#xff0c;如下图所示&#xff1a; 下面&#xff0c;我们来具体的讲解一下。…

解锁机器学习-梯度下降:从技术到实战的全面指南

目录 一、简介什么是梯度下降&#xff1f;为什么梯度下降重要&#xff1f; 二、梯度下降的数学原理代价函数&#xff08;Cost Function&#xff09;梯度&#xff08;Gradient&#xff09;更新规则代码示例&#xff1a;基础的梯度下降更新规则 三、批量梯度下降&#xff08;Batc…

java io读取数据

1.字节流读取数据 2.字节流读取数据&#xff1a; read&#xff08;&#xff09; package wwx;import jdk.swing.interop.SwingInterOpUtils;import java.io.*; import java.nio.charset.StandardCharsets;public class Test {public static void main(String[] args) {FileInp…

1.1 向量与线性组合

一、向量的基础知识 两个独立的数字 v 1 v_1 v1​ 和 v 2 v_2 v2​&#xff0c;将它们配对可以产生一个二维向量 v \boldsymbol{v} v&#xff1a; 列向量 v v [ v 1 v 2 ] v 1 v 的第一个分量 v 2 v 的第二个分量 \textbf{列向量}\,\boldsymbol v\kern 10pt\boldsymbol …

机器人制作开源方案 | 杠杆式6轮爬楼机器人

1. 功能描述 本文示例将实现R281b样机杠杆式6轮爬楼机器人爬楼梯的功能&#xff08;注意&#xff1a;演示视频中为了增加轮胎的抓地力&#xff0c;在轮胎上贴了双面胶&#xff0c;请大家留意&#xff09;。 2. 结构说明 杠杆式6轮爬楼机器人是一种专门用于爬升楼梯或不平坦地面…

thinkphp6 - 超详细使用阿里云短信服务发送验证码功能,TP框架调用对接阿里云短信发验证码(详细示例代码,一键复制开箱即用)

效果图 在thinkphp 5/6 框架(只要不是太低的版本就能用)中,实现接入调用阿里云短信服务详细教程,整个配置过程及示例代码保证小白也能轻松完成! 直接复制就行,改个阿里云参数就能用了。

MongoDB 简介和安装

一、MongoDB 相关概念 1.1 业务应用场景 1.1.1 三高需求 传统的关系型数据库&#xff08;如 MySQL&#xff09; &#xff0c;在数据操作的 “三高” 需求以及应对 Web2.0 的网站需求面前&#xff0c;显得力不从心。”三高“ 需求如下所示&#xff0c;而 MongoDB 可应对 “三高…

C++入门指南:类和对象总结友元类笔记(下)

C入门指南:类和对象总结友元类笔记&#xff08;下&#xff09; 一、深度剖析构造函数1.1 构造函数体赋值1.2 初始化列表1.3 explicit关键字 二、static成员2.1 概念2.2 特性 三、友元3.1 友元函数3.2 友元类 四、 内部类4.1 概念4.2 特征 五、拷贝对象时的一些编译器优化六、深…