单元测试是指程序员写的测试代码给自己的类中的方法进行预期正确性的验证。
单元测试一旦写好了这些测试代码,就可以一直使用,可以实现一定程度上的自动化测试。
单元测试一般要使用框架进行。
什么是框架?
框架是前人或者一些牛逼的技术公司在实战或者研发中设计的一些优良的设计方案或者成型的 代码功能,作为一个完整的技术体系发行出来称为框架。
框架可以让程序员快速拥有一个强大的解决方案,可以快速的开发功能,提高效率并且直接就有了很好的性能。
单元测试的经典框架:Junit
Junit
是什么
Junit
是Java语言编写的第三方单元测试框架Junit
框架的方案可以帮助我们方便且快速的测试我们的代码的正确性。单元测试概念
Junit
编写的一小段代码,用来对某个类中的某个方法进行功能测试或业务逻辑测试。Junit
单元测试框架的作用
Junit框架的使用步骤:
下载这个框架。(别人设计好的技术体系)
框架一般是jar包的形式,jar包里面都是class文件。(Java工程的最终形式)class文件就是我们调用的核心代码
现在不需要大家去官网下载,因为很多知名框架其实IDEA工具早就整合好了,程序员可以直接使用。
Junit已经被IDEA下载好了,可以直接导入到项目使用的。
直接用Junit测试代码即可
public
修饰的,没有返回值,没有参数@Test
修饰如何运行测试方法
选中方法名 --> 右键 --> Run ‘测试方法名’ 运行选中的测试方法
选中测试类名 --> 右键 --> Run ‘测试类类名’ 运行测试类中所有测试方法
选中模块名 --> 右键 --> Run ‘All Tests’ 运行模块中的所有测试类的所有测试方法
如何查看测试结果
Junit
常用注解(Junit 4.xxxx版本)
@Test
测试方法!@Before
:用来修饰实例方法,该方法会在每一个测试方法执行之前执行一次。@After
:用来修饰实例方法,该方法会在每一个测试方法执行之后执行一次。@BeforeClass
:用来静态修饰方法,该方法会在所有测试方法之前只执行一次。@AfterClass
:用来静态修饰方法,该方法会在所有测试方法之后只执行一次。开始执行的方法:初始化资源。
执行完之后的方法:释放资源。
Junit常用注解(Junit5.xxxx版本)
@Test
测试方法!@BeforeEach
:用来修饰实例方法,该方法会在每一个测试方法执行之前执行一次。@AfterEach
:用来修饰实例方法,该方法会在每一个测试方法执行之后执行一次。@BeforeAll
:用来静态修饰方法,该方法会在所有测试方法之前只执行一次。@AfterAll
:用来静态修饰方法,该方法会在所有测试方法之后只执行一次。public class UserServiceTest {
// @Before:用来修饰实例方法,该方法会在每一个测试方法执行之前执行一次。
@Before
public void before(){
System.out.println("===before===");
}
// @After:用来修饰实例方法,该方法会在每一个测试方法执行之后执行一次。
@After
public void after(){
System.out.println("===after===");
}
// @BeforeClass:用来静态修饰方法,该方法会在所有测试方法之前只执行一次。
@BeforeClass
public static void beforeClass(){
System.out.println("===beforeClass===");
}
// @AfterClass:用来静态修饰方法,该方法会在所有测试方法之后只执行一次。
@AfterClass
public static void afterClass(){
System.out.println("===afterClass===");
}
/**
* 测试方法的要求:
* 1.必须public修饰
* 2.没有返回值没有参数
* 3. 必须使注解@Test修饰
*/
@Test
public void testLogin(){
UserService userService = new UserService();
String rs = userService.login("admin","123456");
// 断言预期结果的正确性。
/**
* 参数一:测试失败的提示信息。
* 参数二:期望值。
* 参数三:实际值
*/
// public static void assertEquals(String message, Object expected, Object actual)
Assert.assertEquals("登录业务功能方法有错误,请检查!","success",rs);
}
@Test
public void testChu(){
UserService userService = new UserService();
userService.chu(10 , 2);
}
}
反射,注解,代理,泛型是Java的高级技术,是以后框架的底层原理必须使用到的技术。
反射:是Java独有的技术。是Java技术显著的特点。
反射是指对于任何一个类,在"运行的时候"都可以直接得到这个类全部成分。
反射的核心思想和关键就是得到:编译以后的class文件对象。
反射提供了一个Class类型,就是可以得到编译以后的class类对象。
HelloWorld.java -> javac -> HelloWorld.class
Class c = HelloWorld.class;
反射是工作在运行时的技术,因为只有运行之后才会有class类对象
反射的核心思想和关键就是得到:编译以后的class文件对象
反射是在运行时获取类的字节码文件对象:然后可以解析类中的全部成分
反射是通过先得到编译以后的Class类对象:字节码文件。然后才可以得到类中的全部成分,进行一些功能设计。
反射为一个类的全部成分都设计了一个类型来代表这个对象:
Class
: 字节码文件的类型Constructor
: 构造器的类型Field
: 成员变量的类型Method
: 方法的类型反射技术的第一步永远是先得到Class类对象:有三种方式获取
类名.class
通过类的对象.getClass()方法
Class.forName(“类的全限名”)
public static Class<?> forName(String className)
Class类下的方法:
Class类对象的获取有三种方式:
String getSimpleName();
获得类名字符串:类名String getName();
获得类全名:包名+类名// 反射的第一步永远是先得到类的Class文件对象: 字节码文件。
// 1.类名.class
Class c1 = Student.class;
System.out.println(c1);
// 2.对象.getClass()
Student swk = new Student();
Class c2 = swk.getClass();
System.out.println(c2);
// 3.Class.forName("类的全限名")
// 直接去加载该类的class文件。
Class c3 = Class.forName("com.itheima._03反射_获取Class类对象.Student");
System.out.println(c3);
System.out.println(c1.getSimpleName()); // 获取类名本身(简名)
System.out.println(c1.getName()); // 获取类的全限名
// Student s1 = (Student) c1.newInstance(); // 调用无参数构造器得到对象,被淘汰了!
反射中Class类型获取构造器提供了很多的API:
1. Constructor getConstructor(Class... parameterTypes)
根据参数匹配获取某个构造器,只能拿public修饰的构造器,几乎不用!
2. Constructor getDeclaredConstructor(Class... parameterTypes)
根据参数匹配获取某个构造器,只要申明就可以定位,不关心权限修饰符,建议使用!
3. Constructor[] getConstructors()
获取所有的构造器,只能拿public修饰的构造器。几乎不用!!太弱了!
4. Constructor[] getDeclaredConstructors()
获取所有声明的构造器,只要你写我就能拿到,无所谓权限。建议使用!!
获取类的全部构造器对象: Constructor[] getDeclaredConstructors()
获取所有申明的构造器,只要你写我就能拿到,无所谓权限。建议使用!!
获取类的某个构造器对象:Constructor getDeclaredConstructor(Class... parameterTypes)
根据参数匹配获取某个构造器,只要申明就可以定位,不关心权限修饰符,建议使用!
对于类Student
来说
public class Student {
private String name ;
private int age ;
private Student(){
System.out.println("无参数构造器被执行~~~~");
}
public Student(String name, int age) {
System.out.println("有参数构造器被执行~~~~");
this.name = name;
this.age = age;
}
//...
}
获取该类运行时的构造器可以用一下代码实现
public class TestStudent {
// 1. getConstructors:
// 获取全部的构造器:只能获取public修饰的构造器。
// Constructor[] getConstructors()
@Test
public void getConstructors(){
// a.反射第一步先得到Class类对象
Class c = Student.class ;
// b.getConstructors():定位全部构造器,只能拿public修饰的!
Constructor[] cons = c.getConstructors();
// c.遍历这些构造器
for (Constructor con : cons) {
System.out.println(con.getName()+"===>"+con.getParameterCount());
}
}
// 2.getDeclaredConstructors():
// 获取全部的构造器:只要你敢写,这里就能拿到,无所谓权限是否可及。
@Test
public void getDeclaredConstructors(){
// a.反射第一步先得到Class类对象
Class c = Student.class ;
// b.getDeclaredConstructors():定位全部构造器,只要申明了就可以拿到
Constructor[] cons = c.getDeclaredConstructors();
// c.遍历这些构造器
for (Constructor con : cons) {
System.out.println(con.getName()+"===>"+con.getParameterCount());
}
}
// 3.getConstructor(Class... parameterTypes)
// 获取某个构造器:只能拿public修饰的某个构造器
@Test
public void getConstructor() throws Exception {
// a.反射第一步先得到Class类对象
Class c = Student.class ;
// b.getConstructor():定位某个构造器,根据参数匹配,只能拿public修饰的!
// Constructor con = c.getConstructor(); // 报错!
Constructor con = c.getConstructor(String.class ,int.class); // 有参数的!!
// c.构造器名称和参数
System.out.println(con.getName()+"===>"+con.getParameterCount());
}
// 4.getDeclaredConstructor
// 获取某个构造器:只要你敢写,这里就能拿到,无所谓权限是否可及。
@Test
public void getDeclaredConstructor() throws Exception {
// a.反射第一步先得到Class类对象
Class c = Student.class ;
// b.getDeclaredConstructor():定位某个构造器,根据参数匹配,只要申明了就可以获取
Constructor con = c.getDeclaredConstructor(); // 可以拿到!定位无参数构造器!
//Constructor con = c.getDeclaredConstructor(String.class , int.class); // 有参数的!!
// c.构造器名称和参数
System.out.println(con.getName()+"===>"+con.getParameterCount());
}
}
获取到构造器之后,可以通过该构造器初始化对象
反射获取Class
中构造器对象Constructor
的作用,就是用来初始化并得到类的一个对象返回
Constructor
的API
T newInstance(Object... initargs)
创建对象,注入构造器需要的数据
void setAccessible(true)
修改访问权限,true
代表暴力攻破权限,false
表示保留不可访问权限
public class TestStudent02 {
// 1.调用无参数构造器得到一个类的对象返回。
@Test
public void createObj01() throws Exception {
// a.反射第一步是先得到Class类对象
Class c = Student.class ;
// b.定位无参数构造器对象
Constructor constructor = c.getDeclaredConstructor();
// c.暴力打开私有构造器的访问权限
constructor.setAccessible(true);
// d.通过无参数构造器初始化对象返回
Student swk = (Student) constructor.newInstance(); // 最终还是调用无参数构造器的!
System.out.println(swk);
}
// 2.调用有参数构造器得到一个类的对象返回。
@Test
public void createObj02() throws Exception {
// a.反射第一步是先得到Class类对象
Class c = Student.class ;
// b.定位有参数构造器对象
Constructor constructor = c.getDeclaredConstructor(String.class , int.class);
// c.通过无参数构造器初始化对象返回
Student swk = (Student) constructor.newInstance("孙悟空",10000); // 最终还是调用有参数构造器的!
System.out.println(swk);
}
}
Field getField(String name);
Field getDeclaredField(String name);
Field[] getFields();
Field[] getDeclaredFields();
对于Dog
类
public class Dog {
private String name;
private int age ;
private String color ;
public static String school;
public static final String SCHOOL_1 = "宠物学校";
public Dog() {
}
// ...
}
获取其成员变量
public class FieldDemo {
/**
* 1.获取全部的成员变量。
*/
@Test
public void getDeclaredFields(){
// a.先获取class类对象
Class c = Dog.class;
// b.获取全部申明的成员变量对象
Field[] fields = c.getDeclaredFields();
for (Field field : fields) {
System.out.println(field.getName()+"===>"+field.getType());
}
}
/**
2.获取某个成员变量对象
*/
@Test
public void getDeclaredField() throws Exception {
// a.先获取class类对象
Class c = Dog.class;
// b.定位某个成员变量对象 :根据名称定位!!
Field ageF = c.getDeclaredField("age");
System.out.println(ageF.getName()+"--->"+ageF.getType());
}
}
给成员变量取值和复制
void set(Object obj, Object value)
:给对象注入某个成员变量数据Object get(Object obj)
:获取对象的成员变量的值。void setAccessible(true)
:暴力反射,设置为可以直接访问私有类型的属性。Class getType()
:获取属性的类型,返回Class对象。String getName()
:获取属性的名称。public class FieldDemo02 {
@Test
public void setField() throws Exception {
// a.反射的第一步获取Class类对象
Class c = Dog.class ;
// b.定位name成员变量
Field nameF = c.getDeclaredField("name");
// c.为这个成员变量赋值!
Dog taiDi = new Dog();
nameF.setAccessible(true); // 暴力反射!
/**
* 参数一:被赋值的对象。
* 参数二:该成员变量的值。
*/
nameF.set(taiDi , "勇敢的泰迪");
System.out.println(taiDi);
// d.获取成员变量的值
String value = nameF.get(taiDi)+"";
System.out.println(value);
}
}
反射获取类的Method方法对象:
Method getMethod(String name,Class...args)
:根据方法名和参数类型获得对应的方法对象,只能获得public的Method getDeclaredMethod(String name,Class...args)
:根据方法名和参数类型获得对应的方法对象,包括private的Method[] getMethods()
:获得类中的所有成员方法对象,返回数组,只能获得public修饰的且包含父类的Method[] getDeclaredMethods()
:获得类中的所有成员方法对象,返回数组,只获得本类申明的方法Method的方法执行:
Object invoke(Object obj, Object... args)
Dog
类
public class Dog {
private String name ;
public Dog(){
}
public Dog(String name) {
this.name = name;
}
public void run(){
System.out.println("狗跑的贼快~~");
}
private void eat(){
System.out.println("狗吃骨头");
}
private void eat(String name){
System.out.println("狗吃"+name);
}
public static void inAddr(){
System.out.println("在吉山区有一只单身狗!");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
反射获取方法
public class MethodDemo01 {
/**
* 1.获得类中的所有成员方法对象
*/
@Test
public void getDeclaredMethods(){
// a.先获取class类对象
Class c = Dog.class ;
// b.获取全部申明的方法!
Method[] methods = c.getDeclaredMethods();
// c.遍历这些方法
for (Method method : methods) {
System.out.println(method.getName()+"====>"
+ method.getParameterCount()+"===>" + method.getReturnType());
}
}
/**
* 2. 获取某个方法对象
*/
@Test
public void getDeclardMethod() throws Exception {
// a.先获取class类对象
Class c = Dog.class;
// b.定位它的某个方法
Method run = c.getDeclaredMethod("run");
// c.触发方法执行!
Dog jinMao = new Dog();
Object rs = run.invoke(jinMao); // 触发jinMao对象的run()方法执行!
System.out.println(rs);// 如果方法没有返回值,结果是null
/**
* 参数一:方法名称
* 参数二:方法的参数个数和类型(可变参数!)
*/
Method eat = c.getDeclaredMethod("eat",String.class);
eat.setAccessible(true); // 暴力反射!
/**
* 参数一:被触发方法所在的对象
* 参数二:方法需要的入参值
*/
Object rs1 = eat.invoke(jinMao,"肉");
System.out.println(rs1);// 如果方法没有返回值,结果是null
}
}
// 泛型只能工作在编译阶段,运行阶段泛型就消失了,
// 反射工作在运行时阶段。
List<Double> scores = new ArrayList<>();
scores.add(99.3);
scores.add(199.3);
scores.add(89.5);
// 拓展:通过反射暴力的注入一个其他类型的数据进去。
// a.先得到集合对象的Class文件对象
Class c = scores.getClass();
// b.从ArrayList的Class对象中定位add方法
Method add = c.getDeclaredMethod("add", Object.class);
// c.触发scores集合对象中的add执行(运行阶段,泛型不能约束了)
add.invoke(scores,"波仔");
System.out.println(scores);
更重要的用途是适合:做Java高级框架,基本上主流框架都会基于反射设计一些通用技术功能。
Mybatis框架:
你给任何一个对象数据我都可以直接帮你解析字段并且把对应数据保存起来。
Student (注册,把信息字段都存储起来)
Teacher (注册,把信息字段都存储起来)
Manager (注册,把信息字段都存储起来)
我现在用反射技术开发一个框架实现:
任何对象只要给我,我就可以把信息和字段都解析并存储起来。
反射适合做通用技术框架的底层实现,在框架的底层源码中我们经常看到反射的影子!!
public class AnnotationDemo01 {
}
@FunctionalInterface
interface A{
void test();
}
自定义注解的格式:
修饰符 @interface 注解名{
// 注解属性
}
- 自定义注解用@interface关键字。
- 使用注解的格式:@注解名称。
- 注解默认可以标记很多地方。
@Book
@MyTest
public class MyBook {
@Book
@MyTest
private MyBook(){
}
@Book
@MyTest
public static void main(@MyTest String[] args) {
@MyTest
@Book
int age = 12;
}
}
@interface Book{
}
@interface MyTest{
}
属性的格式
属性适用的数据类型:
(int, short, long, double, byte, char, boolean, float)
String,Class
- 注解可以有属性,属性名必须带()
- 在用注解的时候,属性必须赋值,除非这个属性有默认值
@MyBook(name="《精通Java基础》",authors = {"播仔","Dlei","播妞"} , price = 99.9 )
public class AnnotationDemo01 {
@MyBook(name="《精通MySQL数据库入门到删库跑路》",authors = {"小白","小黑"} ,
price = 19.9 , address = "北京")
public static void main(String[] args) {
}
}
// 自定义一个注解
@interface MyBook{
String name();
String[] authors(); // 数组
double price();
String address() default "广州";
}
//@Book(value = "/deleteBook.action")
//@Book("/deleteBook.action")
//@Book(value = "/deleteBook.action" , age = 12)
//@Book("/deleteBook.action")
public class AnnotationDemo01{
}
@interface Book{
String value();
int age() default 10;
}
元注解有两个:
@Target
:约束自定义注解只能在哪些地方使用,但是默认的注解可以在类,方法,构造器,成员变量,… 使用。@Retention
:声明注解的生命周期@Target
@Retention
@Target
约束自定义注解可以标记的范围。@Retention
用来约束自定义注解的存活范围。
public class AnnotationDemo01{
// @MyTest
private String name;
@MyTest
public static void main( String[] args) {
}
@MyTest
public void testRun(){
}
}
//@Target({ElementType.METHOD , ElementType.FIELD}) // 申明只能注解方法和成员变量!
@Target(ElementType.METHOD ) // 申明只能注解方法
@Retention(RetentionPolicy.RUNTIME) // 申明注解从写代码一直到运行还在,永远存活!!
@interface MyTest{
}
我们会使用注解注释一个类的成分,那么就设计到要解析出这些注解的数据。开发中经常要知道一个类的成分上面到底有哪些注解,注解有哪些属性数据,这都需要进行注解的解析。
与注解解析相关的接口
1. Annotation
: 注解类型,该类是所有注解的父类。注解都是一个Annotation
的对象
1. AnnotatedElement
:该接口定义了与注解解析相关的方法
2所有的类成分Class, Method , Field , Constructor:都实现了AnnotatedElement
接口,他们都拥有解析注解的能力:
* Annotation[] getDeclaredAnnotations()
获得当前对象上使用的所有注解,返回注解数组。
* T getDeclaredAnnotation(Class
根据注解类型获得对应注解对象
* boolean isAnnotationPresent(Class
判断当前对象是否使用了指定的注解,如果使用了则返回true,否则false
解析注解数据的原理
public class AnnotationDemo01 {
@Test
public void parseClass(){
// 1.定位Class类对象
Class c = BookStore.class ;
// 2.判断这个类上是否使用了某个注解
if(c.isAnnotationPresent(Book.class)){
// 3.获取这个注解对象
Book book = (Book) c.getDeclaredAnnotation(Book.class);
System.out.println(book.value());
System.out.println(book.price());
System.out.println(Arrays.toString(book.authors()));
}
}
@Test
public void parseMethod() throws Exception {
// 1.定位Class类对象
Class c = BookStore.class ;
// 2.定位方法对象
Method run = c.getDeclaredMethod("run");
// 3.判断这个方法上是否使用了某个注解
if(run.isAnnotationPresent(Book.class)){
// 3.获取这个注解对象
Book book = (Book) run.getDeclaredAnnotation(Book.class);
System.out.println(book.value());
System.out.println(book.price());
System.out.println(Arrays.toString(book.authors()));
}
}
}
@Book(value = "《Java基础到精通》" , price = 99.5 , authors = {"波仔","波妞"})
class BookStore{
@Book(value = "《Mybatis持久层框架》" , price = 199.5 , authors = {"dlei","播客"})
public void run(){
}
}
@Target({ElementType.TYPE,ElementType.METHOD}) // 类和成员方法上使用
@Retention(RetentionPolicy.RUNTIME) // 注解永久存活
@interface Book{
String value();
double price() default 100;
String[] authors();
}
需求:定义若干个方法,只要加了MyTest注解,就可以被自动触发执行。
分析:
@MyTest
注解的方法就能被触发执行!!
- 注解和反射可以配合解决一些框架思想
- 注解可以实现标记的成分做特殊处理!!
public class TestDemo{
@MyTest
public void test01(){
System.out.println("===test01===");
}
public void test02(){
System.out.println("===test02===");
}
@MyTest
public void test03(){
System.out.println("===test03===");
}
@MyTest
public void test04(){
System.out.println("===test04===");
}
public static void main(String[] args) throws Exception {
TestDemo t = new TestDemo();
// 模拟测试类的启动按钮,实现有注解标记的方法就要触发执行。
// 1.得到类对象
Class c = TestDemo.class;
// 2.获取类中全部方法对象
Method[] methods = c.getDeclaredMethods();
// 3.遍历全部方法,有注解就触发执行
for (Method method : methods) {
if(method.isAnnotationPresent(MyTest.class)){
// 触发此方法执行。
method.invoke(t);
}
}
}
}
@Target(ElementType.METHOD) // 只能注解方法!
@Retention(RetentionPolicy.RUNTIME) // 一直都活着
@interface MyTest{
}
引入:
开发步骤:
- 动态代理非常的灵活,可以为任意的接口实现类对象做代理
- 动态代理可以为被代理对象的所有接口的所有方法做代理,
- 动态代理可以在不改变方法源码的情况下,实现对方法功能的增强,
- 动态代理类不仅简化了编程工作,而且提高了软件系统的可扩展性,因为Java 反射机制可以生成任意类型的动态代理类。
- 动态代理同时也提高了开发效率。
- 缺点:只能针对接口或者接口的实现类对象做代理对象,普通类是不能做代理对象的。
public class ProxyUtil {
/**
* 做一个被代理的业务对象返回!
* @param obj
* @return
*/
public static <T> T getProxy(Object obj) {
/**
参数一:类加载器:负责加载到时候做好的业务代理对象!
参数二:被代理业务对象的全部实现的接口,以便代理对象可以知道要为哪些方法做代理。
参数三:代理真正的执行方法,也就是代理的处理逻辑!
*/
return (T) Proxy.newProxyInstance(obj.getClass().getClassLoader(),
obj.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] params) throws Throwable {
// proxy : 业务代理对象本身。用不到
// method: 代表当前正在被代理执行的方法!!
// params: 代表的是执行方法的参数,数组的形式!
long startTime = System.currentTimeMillis();
// 真正触发真实的方法执行
Object rs = method.invoke(obj,params);
long endTime = System.currentTimeMillis();
System.out.println(method.getName()+"方法耗时:"+(endTime - startTime)/1000.0+"s");
return rs; // 返回方法执行的结果!!
}
});
}
}