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

初识Java 14-1 测试

目录

测试

单元测试

JUnit

测试覆盖率

前置条件

断言

Java提供的断言语法

Guava提供的更方便的断言

契约式设计中的断言

DbC + 单元测试

Guava中的前置条件


本笔记参考自: 《On Java 中文版》


测试

||| 如果没有经过测试,代码就不可能正常工作。

        Java大体上可以被称为一门静态类型语言。静态类型检查是一种非常有限的测试类型,它仅仅保证代码的语法和基本类型没有问题,但却不能保证我们的代码能够满足程序的目标。

        为此,就需要程序员来进行代码验证。第一步,我们需要创建测试,检查我们的代码行为是否满足了我们的目标。

单元测试

        单元测试是一个将集成测试构建到我们所创建的所有代码中的过程。并在每次测试的时候运行这些测试。这种做法可以用来检测语义错误

        单元测试通常是一小段代码。通过为每一个类构建自己的测试来检查它所有方法的行为。Java的垃圾收集和类型检查等功能共同构成了Java的安全网。将单元测试集成到构建过程中,我们就可以扩展这个安全网。从而加快开发速度。

    除此之外,还有“系统”测试,用来检测已完成的程序是否能够满足最终要求。

项目构建

        通过一些项目构建工具,我们可以把源代码生成可执行应用程序的过程自动化。这种工具可以帮助我们管理项目的外部依赖包、项目编译和打包等工作。这里简单介绍几种Java的项目构建工具(参考自默 语的博客):

  1. Ant
    1. 优点:灵活、速度快。
    2. 缺点:学习路线较为陡峭,配置复杂,难以适应大型项目。
  2. Maven
    1. 优点:功能强大,可自动下载依赖包并构建项目。
    2. 缺点:项目构建僵化,配置不够灵活,不适合小型项目。
  3. Gradle
    1. 优点:灵活、可扩展,支持远程仓库。可以根据需要自定义任务和行为。
    2. 缺点:学习成本高,版本兼容性差等。

JUnit

        JUnit是一个开源的Java测试框架,可用于编写可靠、高效的测试(并且可用于创建自动测试)。在编写单元测试时,它是一个很好的工具。

        直至笔者写下本笔记的时候,Junit已经更新到了JUnit 5版本,可以在GitHub上找到这个项目:

JUnit 5icon-default.png?t=N7T8https://github.com/junit-team/junit5接下来将会参考JUnit 5的指导手册,做一些必要的介绍(但不会包括如何下载和配置JUnit,已经有许多人写过很出色的博客来说明这部分了)

        JUnit需要运行在JVM上,而JUnit 5在运行时的最低配置是JDK 8(不过也可以用它来测试更早版本的程序)。JUnit提供了许多用于配置测试并扩展框架的注解,可以在官方提供的文档中进行查看,这里仅仅简单介绍一些我们会用到的:

注释描述
@Test表明该方法是测试方法。在JUnit 5中,该注释没有任何属性。
@TestFactory表明该方法是一个被用于动态测试的测试工厂。
@BeforeAll拥有该注解的方法会在任何测试执行之前运行一次,这种方法必须是静态的。
@AfterAll拥有该注解的方法会在任何测试执行之后运行一次,这种方法必须是静态的。
@BeforeEach通常用于创建和初始化一组公共对象,并在每次测试之前运行。
@AfterEach拥有该注解的方法会在测试结束之后运行。通常用于每次测试之后的清理(如恢复static成员,关闭文件、数据库或网络连接等)。

【例子:简单的测试】

        首先写一段简单的代码:

package validating;import java.util.ArrayList;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;}
}

        接下来编写测试代码对其进行检查。标准的做法是将测试代码放在一个独立的包中:

package validating.tests;import org.junit.jupiter.api.*;
import validating.CountedList;import java.util.Arrays;
import java.util.List;import static org.junit.jupiter.api.Assertions.*;class CountedListTest {private CountedList list;// BeforeAll和AfterAll修饰的方法都是静态的@BeforeAllstatic void beforeAllMsg() {System.out.println(">>> 开始对CountedList的测试");}@AfterAllstatic void afterAllmsg() {System.out.println();System.out.println(">>> 结束对CountedList的测试");}@BeforeEachpublic void initialize() {System.out.println();list = new CountedList();System.out.println("对id为#" + list.getId() + "的单元进行设置");for (int i = 0; i < 3; i++)list.add(Integer.toString(i));}@AfterEachpublic void cleanup() {System.out.println("对id为#" + list.getId() + "的单元进行清理");}@Testpublic void insert() {System.out.println("运行测试:输入");assertEquals(list.size(), 3); // 断言方法list.add(1, "插入");assertEquals(list.size(), 4); // 断言方法assertEquals(list.get(1), "插入");}@Testpublic void replace() {System.out.println("运行测试:替换");assertEquals(list.size(), 3);list.set(1, "替换");assertEquals(list.size(), 3);assertEquals(list.get(1), "替换");}// 只需要在测试中运行的辅助方法compare// 没有@Test之类的注解,不会自动运行private void compare(List<String> lt, String[] strs) {assertArrayEquals(lt.toArray(new String[0]), strs);}@Testpublic void order() {System.out.println("运行测试:检查顺序");compare(list, new String[]{"0", "1", "2"});}@Testpublic void remove() {System.out.println("运行测试:删除");assertEquals(list.size(), 3);list.remove(1);assertEquals(list.size(), 2);compare(list, new String[]{"0", "2"});}@Testpublic void addAll() {System.out.println("运行测试:放入多项数据");list.addAll(Arrays.asList(new String[]{"许多", "项", "数据"}));assertEquals(list.size(), 6);compare(list, new String[]{"0", "1", "2", "许多", "项", "数据"});}
}

程序执行的结果是:

        JUnit使用@Test来标注测试方法。在这些方法中,我们可以执行任何所需的操作。并且可以使用JUnit的断言方法(这些方法都以“asset”开头)来验证测试的正确性。如果需要,可以在文档中找到这些断言方法的说明。

测试覆盖率

||| 测试覆盖率,也称为代码覆盖率。是衡量代码库的测试百分比,百分比越高,测试覆盖率越大。

        可能会存在这样一种误解,必须追求覆盖率的100%:

这是有问题的,因为这个数据并不是衡量测试有效性的合理标准。有时,即使我们测试了所有需要测试的内容,测试覆盖率也只会达到60%~70%。换言之,如果盲目追求100%的覆盖率,就会浪费大量的时间。

        一般情况下,测试覆盖率只作为粗略的衡量标准。注意,必要依赖覆盖率来获取测试质量的相关信息。

前置条件

        前置条件的概念来自契约式设计,并且使用了基本的断言机制来实现。在了解契约式设计之前,需要先介绍一下断言的基本概念。

断言

        断言通过验证程序执行期间是否满足某些条件来提高程序的稳健性。断言可以应用于判断数值的范围、参数的有效性等多种场景。

Java提供的断言语法

        有许多编程结构可以用来模拟断言的效果。Java本身也提供了两种现成的断言语句:

assert boolean-expression;
assert boolean-expression: information-expression;

断言会判断表达式的值是否为true。若不是true,断言会产生一个AssertionError异常(这个异常是Throwable的子类,因此不需要指定异常规范)。

        注意:第一种断言形式产生的异常不会包含boolean-expression的任何信息。

【例子:第一种断言形式】

// 需要在运行时显示地启动断言、
// 启动断言最简单的方法是在运行程序时使用-ea标志
public class Assert1 {public static void main(String[] args) {assert false;}
}

        程序执行的结果是:

        Java的断言默认不打开,因此我们必须在运行程序时显式地启用断言。最简单的方法是使用-ea标志(也可拼写为-enableassertions)。这将在运行程序时执行任何断言语句。IDEA可以通过修改运行配置添加该标志:

        而若使用第二种的断言,就可以在异常栈中生成一个有用的消息:

【例子:第二种断言形式】

public class Assert2 {public static void main(String[] args) {assert false :"这是一条信息,用来说明发生了什么"; // information-expression}
}

         程序执行的结果是:

        information-expression可以是任何类型的对象。但通常,我们会构造一个更复杂的字符串,其中会包含与失败断言有关的对象的信息。

    可以根据类名或包名打开或关闭断言,详见JDK文档。

        还有另一种控制断言的方式:以编程的方式操作ClassLoader对象。ClassLoader中有几种方法允许动态启用和禁用断言。

【例子:通过ClassLoader开启断言】

public class LoaderAssertions {public static void main(String[] args) {ClassLoader.getSystemClassLoader().setDefaultAssertionStatus(true); // 该方法会为其之后加载的所有类设置断言状态new Loaded().go();}
}class Loaded {public void go() {assert false : "Loaded.go()";}
}

        程序执行的结果是:

        这样就不需要使用-ea标志了。在独立交付产品时,可能会需要使用独立脚本来配置其他启动参数,以保证用户无论如何都可以启动程序。

        另外,也可以在程序运行时再决定是否启用断言,可以使用静态语句做到这一点。

【例子:使用静态语句控制断言】

static {boolean assertionsEnabled = false;assert assertionsEnabled = true; // 利用赋值的副作用进行重新赋值if (!assertionsEnabled)throw new RuntimeException("断言已禁用");
}

        由于赋值的返回值是赋值操作符右边的值,因此可以利用这一点控制断言的开启。

------

Guava提供的更方便的断言

        Guava团队提供了一个Verify类来替代Java原生的断言,这个类提供了始终启动的替换断言(Guava是谷歌提供的第三方库,可以在GitHub上找到这个库)。他们建议静态导入Verify类:

【例子:Guava中的断言】

import com.google.common.base.VerifyException;import static com.google.common.base.Verify.*;public class GuavaAssertions {public static void main(String[] args) {verify(1 + 1 == 2);try {verify(1 + 1 == 4);} catch (VerifyException e) {System.out.println(e);}try {verify(1 + 1 == 4, "算错了");} catch (VerifyException e) {System.out.println(e.getMessage());}try {verify(1 + 1 == 4, "算错了:%s", "不是4");} catch (VerifyException e) {System.out.println(e.getMessage());}String s = "";s = verifyNotNull(s);s = null;try {verifyNotNull(s);} catch (VerifyException e) {System.out.println(e.getMessage());}try {verifyNotNull(s, "不应该为空:%s", "arg s");} catch (VerifyException e) {System.out.println(e.getMessage());}}
}

        程序执行的结果是:

        verify()verifyNotNull()都可以提供错误信息。但推荐使用verifyNotNull(),因为verify()提供的信息过于笼统了。

------

契约式设计中的断言

        契约式设计(DbC),即通过保证对象遵循某些规则来创建稳健的程序。这些规则由要解决问题的性质决定,而且超出了编译器可以验证的范围(也有说法认为,接口的本质就是契约)

        断言可以创建一种非正式的DbC编程风格。

        DbC假定服务提供者和服务消费者之间存在着明确的合同。接口分隔了提供者和消费者,当客户调用某些公共方法时,他们会期望调用特定的行为:对象中状态的改变,或是可预测的返回值。这种行为的设计主旨概括如下:

  1. 可以明确规定这种行为,就好像合同一样。
  2. 通过运行时检查包装上述行为,即前置条件后置条件不变项

    同任何解决方案一样,DbC也存在着它的局限。但只有知道这些局限,我们才能更好地使用它们。

        在DbC中,我们可以更多地去关注其对特定类的约束。

检查指令

        首先需要考虑断言最简单的用法,即检查指令:当我们无法仅凭借程序执行的结果得出结论时,就可以用一个检查指令来断言我们获得的结果是否正确。其思想是在代码中表达并非显而易见的结论,这不仅可以用来测试用例,还可以作为阅读代码时的文档。

前置条件测试

        确保客户(即调用此方法的代码)履行其合同部分。因此我们基本上需要在方法调用的最开始(准确的说,是在该方法开始执行任何操作之前)检查参数。

后置条件

        用于检测方法的执行结果。一般放置在方法调用的末尾,return语句之前。对复杂的计算而言,后置条件是必不可少的。我们可以将那些对方法结果的约束放在后置条件中。

不变项

        用以确保对象的状态在方法调用之间是不变的(但可以在方法执行期间偏离)。不变项只保证对象的状态信息在以下两个时间段遵守规定的规则:

  1. 进入方法前;
  2. 离开方法后。

   不变项是对对象构造后状态的保证。

        可以把不变项命名定义为一个方法,一般命名为invariant(),这个方法会在①对象构造之后以及②每个方法的开始和结束时被调用。可以这样调用该方法:

assert invariant(); // 当禁用断言时,就不会因此产生开销了

放宽DbC的限制

        尽管前置条件、后置条件和不变项十分有用,但在发布的产品中包含所有的DbC代码并不总是可行的。可以根据对特定位置代码的信任程度放宽DbC检查。以下是DbC检查的顺序,从最安全到最不安全:

  1. 禁用方法开头的不变项检查。
  2. 当有合理的单元测试来验证方法的返回值时,可以禁用后置条件检查。
  3. 如果确信方法体不会将对象置于无效状态(可使用白盒测试进行检测,即使用可以访问私有字段的单元测试来验证对象状态),则可以禁用方法调用结束时的不变项检查。
  4. 最不安全的,禁用前置条件检查。即使我们自己了解自己的代码,但无法控制客户传递给方法的参数

    不建议在禁用检查时直接删除执行检查的代码(只需要将其注释掉即可)。


DbC + 单元测试

        可以将契约式设计中的概念和单元测试进行结合:

【例子:测试一个循环队列】

        不同于以往直接编写程序,这次需要为这个队列做一些契约性的定义:

  1. 前置条件(对put():不允许将空元素添加到队列中。
  2. 前置条件(对put():将元素放入已满的队列是非法的。
  3. 前置条件(对get():尝试从空元素中获取元素是非法的。
  4. 后置条件(对get():不能从数组中获取空元素。
  5. 不变项:队列中包含对象的区域不能有任何空元素。
  6. 不变项:队列中不包含对象的区域必须只能有空值。

        ① 创建一个专用的Exception

public class CircularQueueException extends RuntimeException {public CircularQueueException(String why) {super(why);}
}

        ② 接下来进行CircularQueue类的创建:

import java.util.Arrays;public class CircularQueue {private Object[] data;private intin = 0, // 指向下一个可用的空间out = 0; // 指向下一个出队的对象private boolean wrapped = false; // 用以判断是否回到了循环队列的开头public CircularQueue(int size) {data = new Object[size];// 构造完毕后的对象必须遵守不变项的约束assert invariant();}public boolean empty() {return !wrapped && in == out;}public boolean full() {return wrapped && in == out;}public boolean isWrapped() {return wrapped;}public void put(Object item) {precondition(item != null, "放入元素为空");precondition(!full(), "试图向已满的队列放入元素");assert invariant();data[in++] = item;if (in >= data.length) {in = 0;wrapped = true;}assert invariant();}public Object get() {precondition(!empty(), "试图从空队列中获取元素");assert invariant();Object returnval = data[out];data[out] = null;out++;if (out >= data.length) {out = 0;wrapped = false;}assert postcondition(returnval != null, "在循环队列中存在空元素");assert invariant();return returnval;}// 契约式设计的相关方法private static voidprecondition(boolean cond, String msg) { // 前置条件if (!cond)throw new CircularQueueException(msg);}private static booleanpostcondition(boolean cond, String msg) { // 后置条件if (!cond)throw new CircularQueueException(msg);return true;}private boolean invariant() { // 不变项// 确定对象的data区域不会有空值for (int i = out; i != in; i = (i + 1) % data.length)if (data[i] == null)throw new CircularQueueException("在循环队列中存在值");// 确定对象的data区域之外只会有空值if (full())return true;for (int i = in; i != out; i = (i + 1) % data.length)if (data[i] != null)throw new CircularQueueException("在循环队列之外存在非空值:"+ dump());return true;}public String dump() { // 返回更多信息return "in = " + in +", out = " + out +", full() = " + full() +", empty() = " + empty() +", CircularQueue = " + Arrays.asList(data);}
}

        通常,我们会需要在代码中保留前置条件。将这些条件都封装在一个precondition()中,可以方便我们减少或关闭前置条件。注意,precondition()返回void,因为它不会和assert一起使用。

        与之相对,postcondition()invariant()都返回boolean,它们可以和assert一起使用。并且在为了性能而关闭断言时,可以直接屏蔽这些方法调用。

        ③ 创建JUnit测试

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import validating.CircularQueue;
import validating.CircularQueueException;import static org.junit.jupiter.api.Assertions.*;class CircularQueueTest {private CircularQueue queue = new CircularQueue(10);private int i = 0;@BeforeEachpublic void initialize() {while (i < 5)queue.put(Integer.toString(i++));}// 辅助用方法private void showFullness() {assertTrue(queue.full());assertFalse(queue.empty());System.out.println(queue.dump());}private void showEmptioness() {assertFalse(queue.full());assertTrue(queue.empty());System.out.println(queue.dump());}@Testpublic void full() {System.out.println("测试:Full");System.out.println(queue.dump());System.out.println(queue.get());System.out.println(queue.get());while (!queue.full())queue.put(Integer.toString(i++));String msg = "";try {queue.put("");} catch (CircularQueueException e) {msg = e.getMessage();System.out.println(msg);}assertEquals(msg, "试图向已满的队列放入元素");showFullness();}@Testpublic void empty() {System.out.println("测试:Empty");while (!queue.empty())System.out.println(queue.get());String msg = "";try {queue.get();} catch (CircularQueueException e) {msg = e.getMessage();System.out.println(msg);}assertEquals(msg, "试图从空队列中获取元素");showEmptioness();}@Testpublic void nullPut() {System.out.println("测试:NullPut");String msg = "";try {queue.put(null);} catch (CircularQueueException e) {msg = e.getMessage();System.out.println(msg);}assertEquals(msg, "放入元素为空");}@Testpublic void circularity() {System.out.println("测试:Circularity");while (!queue.full())queue.put(Integer.toString(i++));showFullness();assertTrue(queue.isWrapped());while (!queue.empty())System.out.println(queue.get());showEmptioness();while (!queue.full())queue.put(Integer.toString(i++));showFullness();while (!queue.empty())System.out.println(queue.get());showEmptioness();}
}

        程序执行的结果是:

        通过将DbC和单元测试向结合,我们不仅可以利用它们各自的优点,而且可以将一些DbC测试移动到单元测试之后(而不是替代它)。这样就能保证某些层次的测试。


Guava中的前置条件

        之前提到过,前置条件是DbC中不应该删除的部分,因为它是用来检查方法参数的有效性的。因此我们最好检测它,这时Java默认的禁用断言就会造成些许麻烦,使用其他可以始终验证方法参数的库是一个不错的选择。

        这里还是使用Google的Guava库:

【例子:使用第三方库的前置条件检测】

import java.util.function.Consumer;import static com.google.common.base.Preconditions.*;public class GuavaPreconditions {static void test(Consumer<String> c, String s) {try {System.out.println(s);c.accept(s);System.out.println("成功");} catch (Exception e) {String type = e.getClass().getSimpleName();String msg = e.getMessage();System.out.println(type + (msg == null ? "" : ": " + msg));}}public static void main(String[] args) {test(s -> s = checkNotNull(s), "X");test(s -> s = checkNotNull(s), null);test(s -> s = checkNotNull(s, "s是null"), null);test(s -> s = checkNotNull(s, "s是null,%s %s", "arg2", "arg3"), null);System.out.println();// checkArgument()会使用布尔表达式对参数进行具体的检测test(s -> checkArgument(s == "ABC"), "ABC");test(s -> checkArgument(s == "ABC"), "X");test(s -> checkArgument(s == "ABC"), null);test(s -> checkArgument(s == "ABC", "匹配失败"), null);test(s -> checkArgument(s == "ABC", "匹配失败,应该是 %s", s), null);System.out.println();// 会检测对象的状态,而不是检查参数(也可用于检查不变项)。test(s -> checkState(s.length() > 6), "长度足够长");test(s -> checkState(s.length() > 6), "不够长");test(s -> checkState(s.length() > 6), null);System.out.println();// 确保第一个参数是一个List、String或数组的有效元素索引test(s -> checkElementIndex(6, s.length()), "比6个字符长一点");test(s -> checkElementIndex(6, s.length()), "短了");test(s -> checkElementIndex(6, s.length()), null);System.out.println();// 确保其的第一个参数在0和第二个参数(包括)的范围内test(s -> checkPositionIndex(6, s.length()), "看起来和上面的差不多");test(s -> checkPositionIndex(6, s.length()), "短了");test(s -> checkPositionIndex(6, s.length()), null);}
}

        程序执行的结果是:

        上述例子只演示了String类型,但Guava的前置条件是适用于所有类型的。

        另外,Guava提供的前置条件,其每个都有三种不同的重载形式:没有消息的测试、带有一个String消息的测试和带有String及替换值的可变参数列表的测试。出于效率考虑,只允许使用%s替换标签。

        因为checkNotNull()会返回参数,因此可以通过内联的方式进行使用:

【例子:内联的checkNotNull()

import static com.google.common.base.Preconditions.checkNotNull;public class NonNullConstruction {private Integer n;private String s;NonNullConstruction(Integer n, String s) {this.n = checkNotNull(n);this.s = checkNotNull(s);}public static void main(String[] args) {NonNullConstruction nnc =new NonNullConstruction(3, "ABC");}
}

    编译器会判断是否进行内联。

相关文章:

初识Java 14-1 测试

目录 测试 单元测试 JUnit 测试覆盖率 前置条件 断言 Java提供的断言语法 Guava提供的更方便的断言 契约式设计中的断言 DbC 单元测试 Guava中的前置条件 本笔记参考自&#xff1a; 《On Java 中文版》 测试 ||| 如果没有经过测试&#xff0c;代码就不可能正常工作…...

react常用的hooks有哪些?

React常用的Hooks包括以下几种&#xff1a; 1.useState&#xff1a;用于在函数组件中创建和管理状态。它返回一个数组&#xff0c;第一个值是当前状态的值&#xff0c;第二个值是更新状态的函数。 使用时&#xff0c;首先通过解构赋值获取状态值和更新函数&#xff0c;并设置初…...

Nacos config 配置中心详解

目录 一、Nacos启动 1.预备环境准备 2.下载编译后压缩包方式 3. 启动服务器 二、 配置管理 多配置格式编辑器 编辑DIFF 示例代码 监听者查询 配置的版本及一键回滚 命名空间管理 登录管理 修改默认用户名/密码方法 关闭登录功能 会话时间 三、启动配置管理 添加依赖&#xff1a…...

C++ UDP通信

#pragma once #ifndef XUDP_H #define XUDP_H#ifdef WIN32 #include <windows.h> #define socklen_t int #else #include <arpa/inet.h> #define closesocket close //替换close函数 #include <unistd.h>#include<iostream> #endifclass XUdp { pub…...

自由程序员想接私活?那你还不得知道这几个接单平台!最后一个就是宝藏!!

相信喜欢搞钱的程序员都知道&#xff0c;平常在平台上接点私活&#xff0c;利用闲暇时间接单是搞钱的常用套路&#xff0c;可是你确定你选对平台了吗&#xff1f;不管你是刚准备接单的小白&#xff0c;还是已经干了一段时间的老油条&#xff0c;都建议你看完本期文章&#xff0…...

二叉树与递归的相爱相杀

数据结构之二叉树 一、基于二叉树的基础操作1.二叉树的构建2.二叉树的遍历①前序遍历&#xff08;深度遍历&#xff09;②中序遍历③后序遍历④层序遍历判断一棵二叉树是否是完全二叉树&#xff08;基于层序遍历的思想&#xff09; 3.二叉树的数量问题①求二叉树结点个数②求二…...

Docker 安装 reids

docker run -itd --name myredis -p 6379:6379 redis --requirepass “123456” --restartalways --appendonly yes...

opensl学习——base16编码解码、base64编码解码、ASCII码表、扩展ASCII码

文章目录 ASCII表概述base家族简单说明 Hex(十六进制)编码、Base32编码、Base64编码、base256编码base16编码与解码base64编码概述转换过程不足 3 字节处理方法例子一,不足3字节&#xff0c;只有一个字节例子二,不足3字节&#xff0c;只有两个字节 base64示例代码1代码分析 acl…...

gazebo各种插件

类别 libgazebo_ros_api_plugin.so&#xff1a;提供与Gazebo仿真环境进行通信的API接口。 libgazebo_ros_block_laser.so&#xff1a;模拟激光传感器的插件。 libgazebo_ros_bumper.so&#xff1a;模拟碰撞传感器的插件。 libgazebo_ros_camera.so&#xff1a;模拟相机传感器的…...

C语言Free空指针会怎样?

在C语言中&#xff0c;使用free函数释放一个空指针是安全的&#xff0c;不会引发任何错误或异常。具体来说&#xff0c;当使用free函数释放一个空指针时&#xff0c;free函数会忽略这个空指针&#xff0c;并且不会执行任何操作。这是因为free函数只对有效的指针进行内存释放操作…...

软件测试全套教程,软件测试自学线路图

软件测试&#xff1a; 软件测试是为了发现程序中的错误而执行程序的过程。 通俗的说&#xff0c;软件测试需要在发布软件之前&#xff0c;尽可能的找软件的错误&#xff0c;尽量避免在发布之后给用户带来不好的体验&#xff0c;并要满足用户使用的需求。 现在市面上这么多软…...

禁止浏览器缩放

禁止浏览器缩放 1. 页面中添加如下代码&#xff1a;2. css单位统一使用rem&#xff0c;如下&#xff1a; 两个条件即可实现&#xff1a; 1. 动态修改html属性fontsize的值&#xff1b; 2. css单位统一使用rem。 1. 页面中添加如下代码&#xff1a; // 定义基准字体 new functi…...

前端食堂技术周刊第 100 期:TS 5.3 Beta、ViteConf2023、Rspress 1.0、Fresh 1.5、Chrome 118

美味值&#xff1a;&#x1f31f;&#x1f31f;&#x1f31f;&#x1f31f;&#x1f31f; 口味&#xff1a;乌龙金桂 食堂技术周刊仓库地址&#xff1a;https://github.com/Geekhyt/weekly 大家好&#xff0c;我是童欧巴。欢迎来到前端食堂技术周刊&#xff0c;我们先来看下…...

汇川IT7000系列HMI使用脚本实现画面跳转时自动切换手自动模式

汇川IT7070E工业HMI使用实例(1) 用脚本切换模式 我们在使用工业HMI做画面时,可能会有这样的需求,希望切换画面时,可以根据不同的画面,自动切换相应的模式,比如有些画面是进行手动操作的,有些画面是进行自动操作的,当我们需要手动时,希望进入画面自动切换为“手动模…...

FDTD Solutions笔记

FDTD Solutions笔记 目录使用流程实例 目录 使用流程 实例 材料条件 步骤 基底 2. 添加规则膜层 3. 添加仿真区 解释&#xff1a; 仿真区为&#xff08;0,0&#xff09;&#xff0c;x方向为0.4&#xff0c;y方向是1 解释&#xff1a; 一般先用低精度进行计算 解释&#xff1a…...

SQL SELECT DISTINCT(选择不同) 语法

SQL SELECT DISTINCT 语法 SELECT DISTINCT语法用于仅返回不同的&#xff08;different&#xff09;值。 在一张表内&#xff0c;一列通常包含许多重复的值; 有时你只想列出不同的&#xff08;different&#xff09;值。 SELECT DISTINCT语句用于仅返回不同的&#xff08;diffe…...

常见的数据结构及应用

文章目录 前言数据结构介绍数组链表队列和栈树堆 总结 前言 数据结构是计算机存储、组织数据的方式。在工作中&#xff0c;我们通常会直接使用已经封装好的集合API&#xff0c;这样可以更高效地完成任务。但是作为一名程序员&#xff0c;掌握数据结构是非常重要的&#xff0c;…...

基于模型预测人工势场的船舶运动规划方法,考虑复杂遭遇场景下的COLREG(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…...

【UE5 Cesium】19-Cesium for Unreal 建立飞行跟踪器(4)

遗留问题 在上一篇博客中&#xff08;【UE5 Cesium】18-Cesium for Unreal 建立飞行跟踪器&#xff08;3&#xff09;&#xff09;&#xff0c;我们实现了飞机变速飞行的功能&#xff0c;但是还存在两个问题&#xff0c;分别是&#xff1a; &#xff08;1&#xff09;由于UE的…...

TrustZone

TrustZone技术 让我们从最重要的问题开始&#xff1a;为什么存在TrustZone技术&#xff0c;它防御什么&#xff1f;保护用 C 和 C 编写的大型程序免受黑客攻击可能是一个挑战。内存损坏漏洞是一个常见问题&#xff0c;尽管消除它们是安全工程师的核心目标&#xff0c;但从操作…...

✔ ★【备战实习(面经+项目+算法)】 10.16学习时间表(总计学习时间:5h)

✔ ★【备战实习&#xff08;面经项目算法&#xff09;】 坚持完成每天必做如何找到好工作1. 科学的学习方法&#xff08;专注&#xff01;效率&#xff01;记忆&#xff01;心流&#xff01;&#xff09;2. 每天认真完成必做项&#xff0c;踏实学习技术 认真完成每天必做&…...

React + Router

React Router 这个只是专门讲解 React Router 新开的例子。 教程来源&#xff1a;https://reactrouter.com/en/main/start/tutorial 创建新项目 yarn create vite my-react-router-app --template react-ts cd my-react-router-app yarn安装 React Router 依赖: yarn add…...

微信小程序设置动态变量设值

微信小程序设置动态变量设值 微信小程序如何动态变量设值&#xff1f; 示例代码如下&#xff1a; setValFunc() {const key this.data.currentPickerid; // 业务需求动态键值key&#xff0c;或者是上一界面获取的动态key值const value 变量值;this.setData({[${key}]: valu…...

闪站侠洗衣洗鞋多门店多用户管理系统,洗鞋店干洗店小程序开发;

闪站侠洗护软件是多分店多用户管理系统&#xff0c;一个分店可以同时关联多个用户。闪站侠洗护管理软件通过互联网为洗衣店/洗鞋店干洗店提供加盟或直营连锁管理&#xff1b; 实现会员洗衣的门店收衣->上门收衣->开单拍照->清洗护理/工厂洗涤->微&#xff5c;信/短…...

JDBC增删改查示例

数据库表 CREATE TABLE customers ( id int NOT NULL AUTO_INCREMENT, name varchar(15) DEFAULT NULL, email varchar(20) DEFAULT NULL, birth date DEFAULT NULL, photo mediumblob, PRIMARY KEY (id) ) ENGINEInnoDB AUTO_INCREMENT39 DEFAULT CHARSETgb2312;…...

emqx broker安装

emqx broker安装 Emq x百万级开源 MQTT 消息服务器 是基于 Erlang/OTP 语言平台开发 一款完全开源&#xff0c;高可用低时延的百万级分布式物联网 MQTT 5.0 消息服务器 官方地址: https://www.emqx.com/zh Centos7 安装 #下载Centos7 amd64位版本 wget https://www.emqx.c…...

如何选择国产压力测试工具?

随着互联网的飞速发展&#xff0c;软件应用的性能和稳定性变得愈发重要。无论是在线购物网站、社交媒体平台还是移动应用程序&#xff0c;用户都期望能够快速、流畅地访问和使用它们。为了确保应用程序在高负载下仍能够正常运行&#xff0c;压力测试工具变得至关重要。在国内&a…...

基于AT89C51流水花样灯proteus仿真设计

一、仿真原理图&#xff1a; 二、仿真效果图&#xff1a; 三、仿真工程&#xff1a; c51单片机流水灯花样灯proteus仿真设计资源-CSDN文库...

android U广播详解(二)

android U广播详解&#xff08;一&#xff09; 基础代码介绍 广播相关 // 用作单个进程批量分发receivers&#xff0c;已被丢弃 frameworks/base/services/core/java/com/android/server/am/BroadcastReceiverBatch.java // 主要逻辑所在类&#xff0c;包括入队、分发、结束…...

导航守卫的使用记录和beforeEach( )死循环的问题

前置导航守卫beforeEach的使用 import Vue from vue import VueRouter from vue-router // 进度条 import NProgress from nprogress import nprogress/nprogress.cssVue.use(VueRouter)// 路由表 const routes [{path: "/",redirect: "/home",},{path: …...