java高级技术
java高级技术
1.0 junit单元测试
junit是一个可以快捷对函数进行测试的第三方的包
我们如果写了一个方法,想要测试一下是否有bug,如果选择在main方法中主动去调用测试,但这种测试方
法面临着很多问题,比如:
-
需要手动查看控制台输出,人工判断结果是否正确
-
多个测试场景混在一起,难以定位具体哪个场景失败
-
无法自动化验证,每次都要人工检查
-
异常情况下程序可能直接终止,后续测试无法执行
此时我们就可以使用junit来进行自动化测试,其优点有:
-
自动断言验证,测试失败会明确报告期望值和实际值
-
每个 @Test 方法独立运行,失败不影响其他测试
-
IDE 提供可视化测试报告,清晰显示通过/失败状态
-
可批量运行所有测试,无需逐个手动执行
下面来看一个例子
首先我们来写一个工具类:
package com.String.test;
public class StringUtil{
public static void printString(String name){
if(name==null) {
System.out.println("不能为空");
}
System.out.println(name);
}
public static int getIndexMax(String data){
if(data==null) {
return -1;
}
else {
return data.length();
}
}
}
再准备一个测试类对这个工具类的方法进行测试:
我们使用注解,也就是下面的@Test对测试方法进行修饰
package com.String.test;
import org.junit.Test;
public class StringTest {
@Test
public void testPrintNumber(){
StringUtil.printString("");
StringUtil.printString(null );
StringUtil.printString("admin");
}
@Test
public void testMaxIndex(){
System.out.println(StringUtil.getIndexMax("null"));
System.out.println(StringUtil.getIndexMax(""));
System.out.println(StringUtil.getIndexMax("1"));
}
}
我们此时运行测试类:

就可以自动化完成测试,如果我们输入的测试样例会出错,此时就会提醒错误,此时一次完成了多个方法的
测试,但是观察我们的第二个方法,发现这是一个寻找字符串最大索引的方法,查看我们的输出结果:

发现输出结果似乎不正确,因为如果单单像上面这样写,在进行测试的时候只能反应报错的方法,当我们的
返回值与期待值不符合时,不会显示错误,想要解决这个问题,需要使用到断言:
具体的用法为:
package com.String.test;
import org.junit.Assert;
import org.junit.Test;
public class StringTest {
@Test
public void testPrintNumber(){
StringUtil.printString("");
StringUtil.printString(null );
StringUtil.printString("admin");
}
@Test
public void testMaxIndex(){
int out1=StringUtil.getIndexMax(null);
Assert.assertEquals("与预期不符合",-1,out1);
int out2=StringUtil.getIndexMax("admin");
Assert.assertEquals("与预期不符",4,out2);
}
}
其中Assert.assertEquals("与预期不符合",-1,out1); 传入的第一个参数是当预测值与实际值不符合时
的报错信息,第二个是预测值,第三个参数是传入的实际值
通过断言,如果有人更改我们的方法,因为返回的值与原来不符,也可以检测出来
需要注意的是,单元测试的方法需要是公开的,无参数和返回值的
1.1 junit4 常用注解

这几个注解的使用很清楚
先看一下Before和After
package com.String.test;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
public class StringTest {
@Before
public void printBefor(){
System.out.println("before执行了一次");
}
@After
public void printAfter(){
System.out.println("after执行了一次");
}
@Test
public void testPrintNumber(){
StringUtil.printString("");
StringUtil.printString(null );
StringUtil.printString("admin");
}
@Test
public void testMaxIndex(){
int out1=StringUtil.getIndexMax(null);
Assert.assertEquals("与预期不符合",-1,out1);
int out2=StringUtil.getIndexMax("admin");
Assert.assertEquals("与预期不符",4,out2);
}
}
此时的输出为
before执行了一次
不能为空
null
admin
after执行了一次
before执行了一次
after执行了一次
可以看到在执行每一个测试方法之前时,都运行了Before修饰的方法,在执行每一个测试方法之后,都执行
了After修饰的方法
一般Before用来初始化资源After用来释放资源
需要注,JUnit4 中的 @Before/@After 只会在它所在的测试类里对每个 @Test 方法生效,不会作用于其他
测试类。
如果先要实现对多个测试类生效,可以讲Before和After修饰的方法写到一个抽象类中,然后让测试类继承
这个抽象类即可
AfterClass 和BeforeClass 有一点需要注意,那就是只能修饰静态方法
适合对通用资源的初始化和释放
对于junit5 来说,这几个注解的作用和使用方法没变,不过注解名发生了改变,需要注意一下
1.2 反射
反射就是加载类,将类作为一个对象加载到内存中,然后获取其中的方法和属性
反射的第一步是获得Class对象,获得Class对象的方法有三种,下面一一展示
package com.String.test;
public class TestClass {
public static void main(String[] args) throws Exception{
Class c1=Student.class;
System.out.println(c1);
Class c2 =Class.forName("com.String.test.Student");
System.out.println(c2);
Student student=new Student(11,"bob");
Class c3=student.getClass();
System.out.println(c3);
if(c1==c2&&c1==c3&&c2==c1){
System.out.println("同一个对象");
}
}
}
接下来是获取构造器对象:

package com.String.test;
import org.junit.Test;
import java.lang.reflect.Constructor;
import java.util.concurrent.Callable;
public class TestConstructer {
@Test
public void testConstructer()throws Exception{
Class c=Student.class;
Constructor [] constructors=c.getDeclaredConstructors();//获取全部构造器
for (Constructor constructor : constructors) {
System.out.println(constructor+":"+constructor.getParameterCount());
}
Constructor constructor1=c.getDeclaredConstructor();//获取无参构造器
Constructor constructor2=c.getDeclaredConstructor(int.class,String.class);//根据参数获取构造器
System.out.println(constructor1+":"+constructor1.getParameterCount());
System.out.println(constructor2+":"+constructor2.getParameterCount());
}
}
获取类构造器的作用依然是初始化对象返回

我们来尝试一下通过构造器对象创建学生类
package com.String.test;
import org.junit.Test;
import java.lang.reflect.Constructor;
import java.util.concurrent.Callable;
public class TestConstructer {
@Test
public void testConstructer()throws Exception{
Class c=Student.class;
Constructor constructor1=c.getDeclaredConstructor();//获取无参构造器
Student student1=(Student) constructor1.newInstance();
System.out.println(student1);
Constructor constructor2=c.getDeclaredConstructor(int.class,String.class);
Student student2=(Student) constructor2.newInstance(11,"bob");
}
}
发现对于无参构造器,我们可以直接获得其产生的对象,但对于有参构造器,产生报错
为什么会这样呢,因为我们的有参构造器是一个私有的构造器,所以无法直接调用构造器进行对象创建,那
我们应该怎么解决呢?
这时候就需要使用暴力反射,也就是禁止对该构造器的访问权限进行检测
通过constructor2.setAccessible(true);实现,加上去之后为:
package com.String.test;
import org.junit.Test;
import java.lang.reflect.Constructor;
import java.util.concurrent.Callable;
public class TestConstructer {
@Test
public void testConstructer()throws Exception{
Class c=Student.class;
Constructor constructor1=c.getDeclaredConstructor();//获取无参构造器
Student student1=(Student) constructor1.newInstance();
System.out.println(student1);
Constructor constructor2=c.getDeclaredConstructor(int.class,String.class);
constructor2.setAccessible(true);
Student student2=(Student) constructor2.newInstance(11,"bob");
}
}
此时再运行就不会报错了
接下来我们来学习获取成员变量,常用的api如下:

package com.String.test;
import org.junit.Test;
import java.lang.reflect.Field;
public class TestField {
@Test
public void testFiled()throws Exception{
Class c=Student.class;
Field[] fields = c.getDeclaredFields();
for (Field field : fields) {
System.out.println(field.getType()+field.getName());
}
Field field1=c.getDeclaredField("age");//获得成员变量
System.out.println(field1.getName());
Student student=new Student();
field1.setAccessible(true);
field1.set(student,111);//给当前成员变量对象赋值,并将其传到对象中
int age=(Integer) field1.get(student);//获得先前赋的值
System.out.println(age);
}
}
获取成员方法对象和上面没什么区别
package com.String.test;
import org.junit.Test;
import java.lang.reflect.Method;
public class TestMethod {
@Test
public void testMethod()throws Exception{
Class c=Student.class;
Method[] methods = c.getDeclaredMethods();
for (Method method : methods) {
System.out.println(method.getName());
}
Method method1=c.getDeclaredMethod("print", int.class);
//获取单个方法,由于存在方法重载,所以要指明方法名和参数,如果不加参数说明指定的就是无参方法
Student student=new Student();
method1.setAccessible(true);//如果我们要调用无参方法,则需要关闭访问权限检查
Object result=method1.invoke(student,111);//由于方法是基于对象的,所以调用方法要指明对象和参数
}
}
1.3 反射的作用
反射可以得到一个类的全部成分然后操作,可以破坏封装性,基本的主流框架都会用到反射
下面是一个简单例子:获得给定对象的所有字段名
package com.String.test;
import java.io.PrintWriter;
import java.lang.reflect.Field;
public class ObjectFrame {
public static void saveObject(Object o) throws Exception{
Class c=o.getClass();
Field [] fields=c.getDeclaredFields();
PrintWriter out=new PrintWriter("./data.txt");
for (Field field : fields) {
System.out.println(field.getName());
field.setAccessible(true);
out.write(field.getName()+":"+field.get(o)+"\r\n");
}
out.close();
}
}
这只是一个简单样例,实际使用方式还有很多
1.4 注解
像我们之前使用的@Test和@Override 都是注解,用于对程序进行标记,让虚拟机进行特殊处理
首先来学习怎么自定义注解
我们先定义一个自定义注解:
package com.String.test;
public @interface Mytest {
String value();
int age() default 21;
}
上面每一个属性后面都需要加括号,还可以给默认值
再去使用
package com.String.test;
@Mytest("bob")
public class AnotationTest {
@Mytest(value = "bob",age = 11)
public static void main(String[] args) {
}
}
类,方法,属性前都可以加我们的自定义注解,需要注意的是,如果自定义注解的值只有一个且名字为
value,那我们再使用注解时可以省略调属性名和等于号,只写值就可以了
注解本身就是一个接口,定义的属性都是方法,定义的值都是方法的返回值,我们在@使用注解时就是在定
义一个实现类对象
1.5 元注解
我们使用元注解对注解进行注解,常用的元注解有两个,我们先看第一个:

对于我们先前写的注解,如果使用元注解:
package com.String.test;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
public @interface Mytest {
String value();
int age() default 21;
}
此时我们的自定义注解就只能注解方法
如果我们想要注解多个内容,可以传递多个参数
package com.String.test;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
@Target({ElementType.METHOD,ElementType.TYPE})
public @interface Mytest {
String value();
int age() default 21;
}
像这样也是可以的
我们再来看第二个元注解:

该元注解声明了注解的生命周期,当我们选择第一个参数SOURCE时,我们在源码阶段,也就是直接写代码
的代码文件中是存在的,但当我们对其进行导出成字节码文件,比如Jar包时,就不存在了,第二个选项保留
到字节码中,但运行时加载到内存中就不存在了,最后一个选项将我们的自定义注解永久保留
package com.String.test;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.TYPE})
public @interface Mytest {
String value();
int age() default 21;
}
我们一般只使用RetentionPolicy.RUNTIME,期望我们的注解一直存活
1.6 注解解析
注解解析就是判断类上,方法上,成员变量上是否存在注解,并将其解析出来
下面是解析注解的方法:

下面是使用方式:
package com.String.test;
import org.junit.Test;
import java.lang.reflect.Method;
public class AnotationTest{
@Test
public void TestAno() throws Exception{
Class c= AnoTest.class;
if(c.isAnnotationPresent(Mytest.class)){//判断该类中是否存在注解
Mytest mytest=(Mytest) c.getDeclaredAnnotation(Mytest.class);
System.out.println(mytest.age());
}
Method method=c.getDeclaredMethod("print");
if(method.isAnnotationPresent(Mytest.class)){
Mytest mytest=method.getDeclaredAnnotation(Mytest.class);
System.out.println(String.join(",", mytest.card()));
}
}
}
@Mytest(value = "bob",card = {"1","2"})
class AnoTest {
@Mytest(value = "bob",card = {"1","2"})
public static void print() {
System.out.println(111);
}
}
1.7 动态代理
动态代理就是用代理类去执行全部功能,核心类只执行核心功能
package com.proxy.test;
public interface Star {
String sing(String name);
String dance(String name);
}
package com.proxy.test;
public class Bigstar implements Star{
private String name;
public Bigstar(String name){
this.name=name;
}
@Override
public String sing(String name){
System.out.println("sing"+name);
return "Sing Finish";
}
@Override
public String dance(String name){
System.out.println("danging"+name);
return "Dance Finish";
}
}
package com.proxy.test;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class CreatProxy {
public static Star creatProxy(Bigstar bigstar){
Star starproxy=(Star) Proxy.newProxyInstance(CreatProxy.class.getClassLoader(),
new Class[]{Star.class},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName() == "sing") {
System.out.println("准备唱歌");
}
else if(method.getName()=="dance"){
System.out.println("准备跳舞");
}
return method.invoke(bigstar,args);
}
}
);
return starproxy;
}
}
package com.proxy.test;
public class ProxyTest {
public static void main(String[] args) {
Bigstar yui=new Bigstar("yui");
Star proxy=CreatProxy.creatProxy(yui);
String rs;
rs=proxy.sing("a song");
System.out.println(rs);
rs=proxy.dance("dancing");
System.out.println(rs);
}
}
这就完成了整个使用代理加测试的全部流程

浙公网安备 33010602011771号