新网站怎么做论坛推广,赣州新闻最新消息,wordpress 压力,关于网站建设中原创文章的一些想法目录
测试
单元测试
JUnit
测试覆盖率
前置条件
断言
Java提供的断言语法
Guava提供的更方便的断言
契约式设计中的断言
DbC 单元测试
Guava中的前置条件 本笔记参考自#xff1a; 《On Java 中文版》 测试
||| 如果没有经过测试#xff0c;代码就不可能正常工作…目录
测试
单元测试
JUnit
测试覆盖率
前置条件
断言
Java提供的断言语法
Guava提供的更方便的断言
契约式设计中的断言
DbC 单元测试
Guava中的前置条件 本笔记参考自 《On Java 中文版》 测试
||| 如果没有经过测试代码就不可能正常工作。 Java大体上可以被称为一门静态类型语言。静态类型检查是一种非常有限的测试类型它仅仅保证代码的语法和基本类型没有问题但却不能保证我们的代码能够满足程序的目标。 为此就需要程序员来进行代码验证。第一步我们需要创建测试检查我们的代码行为是否满足了我们的目标。
单元测试 单元测试是一个将集成测试构建到我们所创建的所有代码中的过程。并在每次测试的时候运行这些测试。这种做法可以用来检测语义错误。 单元测试通常是一小段代码。通过为每一个类构建自己的测试来检查它所有方法的行为。Java的垃圾收集和类型检查等功能共同构成了Java的安全网。将单元测试集成到构建过程中我们就可以扩展这个安全网。从而加快开发速度。 除此之外还有“系统”测试用来检测已完成的程序是否能够满足最终要求。 项目构建 通过一些项目构建工具我们可以把源代码生成可执行应用程序的过程自动化。这种工具可以帮助我们管理项目的外部依赖包、项目编译和打包等工作。这里简单介绍几种Java的项目构建工具参考自默 语的博客
Ant 优点灵活、速度快。缺点学习路线较为陡峭配置复杂难以适应大型项目。Maven 优点功能强大可自动下载依赖包并构建项目。缺点项目构建僵化配置不够灵活不适合小型项目。Gradle 优点灵活、可扩展支持远程仓库。可以根据需要自定义任务和行为。缺点学习成本高版本兼容性差等。
JUnit JUnit是一个开源的Java测试框架可用于编写可靠、高效的测试并且可用于创建自动测试。在编写单元测试时它是一个很好的工具。 直至笔者写下本笔记的时候Junit已经更新到了JUnit 5版本可以在GitHub上找到这个项目
JUnit 5https://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 ArrayListString {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(ListString 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假定服务提供者和服务消费者之间存在着明确的合同。接口分隔了提供者和消费者当客户调用某些公共方法时他们会期望调用特定的行为对象中状态的改变或是可预测的返回值。这种行为的设计主旨概括如下
可以明确规定这种行为就好像合同一样。通过运行时检查包装上述行为即前置条件、后置条件和不变项。 同任何解决方案一样DbC也存在着它的局限。但只有知道这些局限我们才能更好地使用它们。 在DbC中我们可以更多地去关注其对特定类的约束。
检查指令 首先需要考虑断言最简单的用法即检查指令当我们无法仅凭借程序执行的结果得出结论时就可以用一个检查指令来断言我们获得的结果是否正确。其思想是在代码中表达并非显而易见的结论这不仅可以用来测试用例还可以作为阅读代码时的文档。
前置条件测试 确保客户即调用此方法的代码履行其合同部分。因此我们基本上需要在方法调用的最开始准确的说是在该方法开始执行任何操作之前检查参数。
后置条件 用于检测方法的执行结果。一般放置在方法调用的末尾return语句之前。对复杂的计算而言后置条件是必不可少的。我们可以将那些对方法结果的约束放在后置条件中。
不变项 用以确保对象的状态在方法调用之间是不变的但可以在方法执行期间偏离。不变项只保证对象的状态信息在以下两个时间段遵守规定的规则
进入方法前离开方法后。 不变项是对对象构造后状态的保证。 可以把不变项命名定义为一个方法一般命名为invariant()这个方法会在①对象构造之后以及②每个方法的开始和结束时被调用。可以这样调用该方法
assert invariant(); // 当禁用断言时就不会因此产生开销了
放宽DbC的限制 尽管前置条件、后置条件和不变项十分有用但在发布的产品中包含所有的DbC代码并不总是可行的。可以根据对特定位置代码的信任程度放宽DbC检查。以下是DbC检查的顺序从最安全到最不安全
禁用方法开头的不变项检查。当有合理的单元测试来验证方法的返回值时可以禁用后置条件检查。如果确信方法体不会将对象置于无效状态可使用白盒测试进行检测即使用可以访问私有字段的单元测试来验证对象状态则可以禁用方法调用结束时的不变项检查。最不安全的禁用前置条件检查。即使我们自己了解自己的代码但无法控制客户传递给方法的参数。 不建议在禁用检查时直接删除执行检查的代码只需要将其注释掉即可。 DbC 单元测试 可以将契约式设计中的概念和单元测试进行结合
【例子测试一个循环队列】 不同于以往直接编写程序这次需要为这个队列做一些契约性的定义
前置条件对put()不允许将空元素添加到队列中。前置条件对put()将元素放入已满的队列是非法的。前置条件对get()尝试从空元素中获取元素是非法的。后置条件对get()不能从数组中获取空元素。不变项队列中包含对象的区域不能有任何空元素。不变项队列中不包含对象的区域必须只能有空值。 ① 创建一个专用的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(ConsumerString 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);}
} 编译器会判断是否进行内联。