1. Junit 单元测试
1. 测试简介
测试一般有两种,根据测试代码是否可见分为以下两种测试。
-
黑盒测试:不需要写代码,给输入值,看程序是否能够输出期望的值。
黑盒测试就如上图一样,像一个黑色的盒子,测试人员在测试时无需关注代码内部的实现逻辑,只需要向黑盒中输入一个值,观察输出的值是否符合预期输出结果的情况。 -
白盒测试:需要写代码。根据程序的具体执行流程。
白盒测试则相反,没有像黑盒测试一样对代码进行隐藏,测试人员会利用程序内部的逻辑结构及有关信息,通过在不同点检查程序状态,检验程序中的每条通路是否都能按预定要求进行正确工作。
2. JUnit 单元测试
1. JUnit 简介
- 在我们写代码的时候,经常会需要测试代码的内部逻辑,这时我们可以使用 JUnit 来进行白盒测试。
- JUnit 是一个 Java 编程语言的单元测试框架。它由Kent Beck和Erich Gamma建立,逐渐成为源于Kent Beck的sUnit的xUnit家族中最为成功的一个。
- JUnit有它自己的JUnit扩展生态圈。多数Java的开发环境都已经集成了JUnit作为单元测试的工具。JUnit 在测试驱动的开发方面有很重要的发展,是起源于 JUnit 的一个统称为 xUnit 的单元测试框架之一。
2. JUnit 配置
1. IDEA 中配置 JUnit
IDEA 中默认安装了 JUnit 插件,在 IDEA 的 File(文件) --> Setting(设置) --> Plugins(插件) 中搜索 JUnit 即可看到,如果自己的 IDEA没有,可以搜索 JUnit 并安装。
2. Eclipse 中配置 JUnit
Eclipse 中配置 JUnit 就会稍微麻烦一点了。
① 找到自己的项目,右击点击项目文件 --> 点击 Build Path
--> 点击 Add Libraries
② 点击 JUnit
③ 选择自己需要的 JUnit 版本,并点击 Finish
即可。
4. JUnit 的使用
在学习 JUnit 之前,我们测试的时候,一般都是定义一个测试类来对我们的程序进行测试。
- Demo类
public class Demo {
public void printHelloWorld(){
System.out.println("Hello World");
}
public int add(int a, int b){
return a+b;
}
public int sub(int a, int b){
return a-b;
}
}
- DemoTest 类
public class DemoTest {
public static void main(String[] args) {
// 创建对象
Demo demo = new Demo();
// 调用
//demo.printHelloWorld();
/*
int num = demo.add(1,2);
System.out.println(num);
*/
int num = demo.sub(1,2);
System.out.println(num);
}
}
但是当我们需要测试多个方法的时候,我们就需要把测试类中调用的方法注释掉,才能测试下一个方法,这样测试起来会很麻烦,于是下面便介绍 JUnit测试。
测试步骤
-
① 定义一个测试类
- 建议:
- 测试类名:被测试的类名Test
- 包名:xxx.xxx.xxx.test
- 建议:
-
② 定义一个测试方法
-
建议:
- 方法名:test测试的方法名
- 返回值:void
- 参数列表:空参
-
③ 给方法加
@Test
-
④ 导入 JUnit 依赖环境
点击报错位置的红色灯,再点击Add 'JUnit' to classpath
即可。
示例
import org.junit.Test;
public class DemoTest {
@Test
public void testPrintHelloWorld(){
// 创建对象
Demo demo = new Demo();
// 调用方法
demo.printHelloWorld();
}
@Test
public void testAdd(){
// 创建对象
Demo demo = new Demo();
// 调用方法
System.out.println(demo.add(1,3));
}
@Test
public void testSub(){
// 创建对象
Demo demo = new Demo();
// 调用方法
System.out.println(demo.sub(1,3));
}
}
测试结果正确的方法就会出现绿色对号,出现异常的方法就会出现红色灯泡。
5. 异常处理
一般我们使用断言(Assert)
来测试异常的结果。Assert可以帮助我们假定预期输出结果与真实输出结果是否相同。
// 格式
Assert.assertEquals(expected,actual);
这里我们假定输出结果为3。
@Test
public void testAdd(){
// 创建对象
Demo demo = new Demo();
// 调用方法
int num = demo.add(1,3);
//System.out.println(num);
// 断言处理
Assert.assertEquals(3,num); //假定输出结果为3
}
可以看到,当真实输出结果和预期输出结果不同时,输出预期值和真实值,并报错。此时我们就需要对我们的代码进行处理
6. @Before 和 @After
@Before
:初始化方法,修饰的方法会在测试方法之前被自动执行
@Before
public void init(){
System.out.println("init...");
};
@After
:释放资源方法,修饰的方法会在测试方法执行之后自动被执行
@After
public void close(){
System.out.println("close...");
}
结果:
2. 反射:框架设计的灵魂
1. 相关概念
-
框架:半成品软件。可以在框架的基础上进行软件开发,简化编码。
-
反射:将类的各个组成部分封装为其他对象,这就是反射机制
通过反射,我们可以更容易的对 类对象阶段更好地操作成员变量,构造方法,成员方法等。 -
反射的好处:
- ① 可以在程序运行过程中,操作这些对象。
- ② 可以解耦,提高程序的可扩展性。
2. 成员变量,构造方法,方法对象
① Field:成员变量
- 设置值
void set(Object obj, Object value)
- 获取值
get(Object obj)
- 忽略访问权限修饰符的安全检查
setAccessible(true) //暴力反射
② Constructor:构造方法
- 创建对象
T newInstance(Object... initargs)
③ Method:方法对象
- 执行方法
Object invoke(Object obj, Object... args)
- 获取方法名称
String getName:获取方法名
3. 获取 Class 对象的方式
- ①
Class.forName("全类名")
:将字节码文件加载进内存,返回Class对象。多用于配置文件,将类名定义在配置文件中。读取文件,加载类 - ②
类名.class
:通过类名的属性class获取。多用于参数的传递 - ③
对象.getClass()
:getClass()
方法在Object类中定义。多用于对象的获取字节码的方式
同一个字节码文件(.class
)在一次程序运行中,只会被加载一次,不论通过哪种方式获取的Class对象都是同一个。
示例
public class Demo {
public int a;
public char b;
public Demo(){ }
public void function() {}
}
class ReflectDemo{
public static void main(String[] args) {
// 获取Class对象
Class cls = Demo.class;
System.out.println(cls);
}
}
3. Class 对象的功能介绍
1. 获取成员变量
方法名 | 说明 |
---|---|
Field[] getFields() | 获取所有public修饰的成员变量,包括从父类继承的成员变量 |
Field getField(String name) | 获取指定名称的 public修饰的成员变量,包括从父类继承的成员变量 |
Field[] getDeclaredFields() | 获取所有的成员变量,不考虑修饰符,不包括从父类继承的成员变量 |
Field getDeclaredField(String name) | 获取指定名称的成员变量,不考虑修饰符,不包括从父类继承的成员变量 |
2. 获取构造方法
方法名 | 说明 |
---|---|
Constructor<?>[] getConstructors() | 返回所有的构造方法,考虑修饰符 |
Constructor<T> getConstructor(类<?>... parameterTypes) | 返回指定参数类型的构造方法,考虑修饰符 |
Constructor<T> getDeclaredConstructor(类<?>... parameterTypes) | 返回指定参数类型的构造方法,不考虑修饰符 |
Constructor<?>[] getDeclaredConstructors() | 返回所有的构造方法,不考虑修饰符 |
3. 获取成员方法
方法名 | 说明 |
---|---|
Method[] getMethods() | 获取所有的成员方法,考虑修饰符 |
Method getMethod(String name, 类<?>... parameterTypes) | 获取指定的成员方法,考虑修饰符 |
Method[] getDeclaredMethods() | 获取所有的成员方法,不考虑修饰符 |
Method getDeclaredMethod(String name, 类<?>... parameterTypes) | 获取的指定的成员方法,不考虑修饰符 |
4. 获取全类名
方法名 | 说明 |
---|---|
String getName() | 获取类的全类名 |
5. 记忆方法
- 在方法名末尾有s的是返回一个数组,没有s的是返回单个方法或变量。
- 在方法名中加
Declared
的是返回所有的方法或变量,不加Declared
的只返回 public 访问权限的方法或变量 - 有参数的获取方法都是在方法名中没有在结尾处加s的,返回的是指定参数类型的方法和变量
3. 注解
1. 相关概念
注解和注释差不多,只不过所面向的对象不一样。
-
注解:说明程序,给计算机看的
-
注释:描述程序,给人看的
-
注解定义:注解(Annotation),也叫
元数据
。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。
2. 注解的作用
注解一般有以下三种作用:
- ① 编写文档:通过代码里标识的注解生成文档【生成文档doc文档】
- ② 代码分析:通过代码里标识的注解对代码进行分析【使用反射】
- ③ 编译检查:通过代码里标识的注解让编译器能够实现基本的编译检查【Override】
3. 注解的分类
注解一般分为预定义注解和自定义注解。
1. 预定义注解
预定义注解也就是 JDK 中事先定义好的一些注解。常见的一些注解如下:
- @Override :检测被该注解标注的方法是否是继承自父类(接口)的。
class Demo_father {
public void show(){
System.out.println("1");
}
}
public class Demo extends Demo_father{
public int a;
public char b;
public Demo(){ }
public void function() {}
@Override
public void show() { //继承父类的方法
super.show();
}
}
- @Deprecated:该注解标注的内容,表示已过时,如果使用该方法,会报编译警告。
class Demo_father {
public void show(){
System.out.println("1");
}
}
public class Demo extends Demo_father{
public int a;
public char b;
public Demo(){ }
public void function() {}
@Override
public void show() {
super.show();
}
@Deprecated
public void print(){
System.out.println("Hello World");
}
public void test(){
print();
}
}
被 @Deprecated
注解的方法在调用时被画横线,并不意味着不能用,但不推荐使用。
- @SuppressWarnings:压制警告,一般传递参数all
@SuppressWarnings("all")
。
使用前,出现警告
使用后,警告消失
2. 自定义注解
@Override
代码
① 格式:
元注解
public @interface 注解名称{
属性列表;
}
示例:MyAnnotation就是一个注解,并能使用,没有报错。
② 本质:注解本质上就是一个接口,该接口默认继承Annotation
接口
public interface MyAnnotation extends java.lang.annotation.Annotation {}
③ 属性:接口中的抽象方法
- 两个要求:
- (1) 属性的返回值类型有下列取值
- 基本数据类型
- String
- 枚举
- 注解
- 以上类型的数组
- (2) 定义了属性,在使用时需要给属性赋值
- 如果定义属性时,使用 default 关键字给属性默认初始化值,则使用注解时,可以不进行属性的赋值。
- 如果只有一个属性需要赋值,并且属性的名称是 value ,则 value 可以省略,直接定义值即可。
- 数组赋值时,值使用
{}
包裹。如果数组中只有一个值,则{}
可以省略
- (1) 属性的返回值类型有下列取值
3. 元注解
元注解用来描述注解的注解,主要有以下几个:
-
① @Target:描述注解能够作用的位置
- ElementType取值:
- TYPE:可以作用于类上
- METHOD:可以作用于方法上
- FIELD:可以作用于成员变量上
- ElementType取值:
-
② @Retention:描述注解被保留的阶段
@Retention(RetentionPolicy.RUNTIME)
:当前被描述的注解,会保留到class字节码文件中,并被JVM读取到
-
③ @Documented:描述注解是否被抽取到api文档中
-
④ @Inherited:描述注解是否被子类继承
4. 解析注解
① 获取注解定义位置的对象(Class,Method,Field)
② 获取指定的注解
③ 调用注解中的抽象方法获取配置的属性值
示例
import java.lang.annotation.*;
@Documented
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation {
String show();
}
@MyAnnotation(show = "Hello World")
class Demo_father {
}
public class Demo {
public static void main(String[] args) {
// 获取字节码文件
Class<Demo_father> demo_fatherClass = Demo_father.class;
// 获取注解对象
MyAnnotation myAnnotation = demo_fatherClass.getAnnotation(MyAnnotation.class);
// 调用注解中的抽象方法,获取返回值
System.out.println(myAnnotation.show());
}
}
结果: