软件测试
查询百度百科:软件测试(英语:Software Testing),描述一种用来促进鉴定软件的正确性、完整性、安全性和质量的过程。换句话说,软件测试是一种实际输出与预期输出之间的审核或者比较过程。软件测试的经典定义是:在规定的条件下对程序进行操作,以发现程序错误,衡量软件质量,并对其是否能满足设计要求进行评估的过程。
测试的分类
- 黑盒测试:不需要写代码,给输入值,看程序是否能够输出期望的值。
- 白盒测试:需要写代码的。关注程序具体的执行流程。Junit测试属于白盒测试中的一种。
现在我们拿到一个需求,需要测试一下Junit包下的Calculator类代码编写是否正确。代码如下
package Junit; /** * 计算器类 */ public class Calculator { /** * 加法 * @param a * @param b * @return */ public int add(int a, int b){ return a +b; } /** * * @param a * @param b * @return */ public int sub(int a,int b){ return a -b; } }
按照以前的知识,我们需要在测试类中,创建Calculator类的对象,在调用Calculator类中编写的方法,查看方法的运行结果和我们期望的结果一样。但是这么做很多的缺点:如果我们要测试多个方法,必须把前面测试好的代码注释掉,以免造成影响。步骤麻烦等等。下面我们就是要Junit单元测试的知识来测试这个类。
步骤如下
1:创建一个包,存放测试用例。包名建议以.text结尾,并且与要测试的代码存放的包平级。
2:在MyTest包中,创建类(测试用例)。建议类名以Test结尾
3:在测试用例中定义测试方法,建议为test要测试的方法名称,例如要测试add方法,测试方法名称可以为testAdd()。测试方法建议空参和void返回值。
4:给测试方法加@Test,导入junit依赖环境
代码完成之后,方法代码如下
package MyTest; import org.junit.Test; public class CalculatorTest { /** * 测试add方法 */ @Test public void testAdd(){ } }
5:在测试用例中,创建对象,调用要测试的方法进行测试
package MyTest; import Junit.Calculator; import org.junit.Test; public class CalculatorTest { /** * 测试add方法 */ @Test public void testAdd(){ //创建对象 Calculator c = new Calculator(); //调用方法 int result = c.add(1, 3); } }
6:使用会使用断言操作来处理结果, Assert.assertEquals(期望的结果,运算的结果);
package MyTest; import Junit.Calculator; import org.junit.Assert; import org.junit.Test; public class CalculatorTest { /** * 测试add方法 */ @Test public void testAdd(){ //创建对象 Calculator c = new Calculator(); //调用方法 int result = c.add(1, 3); //断言操作 Assert.assertEquals(4,result); } }
7:查看下面,如果是绿色的则代码要测试的内容没有问题,红色则说明代码有错误
常用的注解
@Before
- 修饰的方法会在测试方法之前被自动执行
@After
- 修饰的方法会在测试方法执行之后自动被执行
代码举例
package MyTest; import Junit.Calculator; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; public class CalculatorTest { /** * 初始化方法: * 用于资源申请,所有测试方法在执行之前都会先执行该方法 */ @Before public void init() { System.out.println("init..."); } /** * 释放资源方法: * 在所有测试方法执行完后,都会自动执行该方法 */ @After public void close() { System.out.println("close..."); } /** * 测试add方法 */ @Test public void testAdd() { //1.创建计算器对象 System.out.println("testAdd..."); Calculator c = new Calculator(); //2.调用add方法 int result = c.add(1, 2); //System.out.println(result); //3.断言 我断言这个结果是3 Assert.assertEquals(3, result); } @Test public void testSub() { //1.创建计算器对象 Calculator c = new Calculator(); int result = c.sub(1, 2); System.out.println("testSub...."); Assert.assertEquals(-1, result); } }
代码执行之后的代码
反射
框架
半成品软件。可以在框架的基础上进行软件开发,简化编码 。反射:框架设计的灵魂
反射的概念
将类的各个组成部分封装为其他对象,这就是反射机制
好处
- 可以在程序运行过程中,操作这些对象。
- 可以解耦,提高程序的可扩展性。
Java代码在计算机中经历的阶段
阶段1:Source源代码阶段
我们编写好代码,经过Javac编译生成字节码文件。字节码文件中保存了Java的成员变量丶构造方法丶成员方法等等等。他分别对应着Java代码中的成员变量丶构造方法,成员方法等等..如下图所示
阶段2:Class类对象阶段
我们通过类加载器(ClassLoader对象)把字节码文件加载进入内存中去,此时就进入Java代码的第二个阶段。在Java中"万物皆对象",因此有一个对象专门描述进入内存中的字节码文件,这个对象的名字是Class类对象。他是用来描述所有字节码文件共同的特征和行为
阶段3 Runtime运行时阶段
当我们通过类对象的一些行为就创建真正的对象,此时就是进入到了运行时阶段
获取字节码class对象的三种方式
如果Java代码处于阶段一的时候,我们可以使用方式一获取Class对象
方式1:
- Class.forName("全类名"):将字节码文件加载进内存,返回Class对象。 多用于配置文件,将类名定义在配置文件中。读取文件,加载类
如果Java代码处于阶段二,我们除了使用方式1获取字节码Class对象还可以使用方式2获取
方式2
- 类名.class:通过类名的属性class获取 多用于参数的传递
如果Java代码处于阶段三,我们除了使用方式1丶2获取还可以使用方式3获取Class对象
方式3
- 对象.getClass():getClass()方法在Object类中定义着。 多用于对象的获取字节码的方式
举例:
定义Person类
package Demo; public class Person { private String name; private int age; public Person() { } public Person(String name, int age) { this.name = name; this.age = age; } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + '}'; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
定义测试类
package reflect; import Demo.Person; public class DemoReflect { public static void main(String[] args) throws Exception { /* 方式1:Class.forName("全类名"):将字节码文件加载进内存,返回Class对象 全类名就是包名.类名全路径 */ Class personClass1 = Class.forName("Demo.Person"); System.out.println(personClass1);//class Demo.Person //方式2:类名.class:通过类名的属性class获取 多用于参数的传递 Class personClass2 = Person.class; System.out.println(personClass2);//class Demo.Person // 方式3:对象.getClass():getClass()方法在Object类中定义着。 多用于对象的获取字节码的方式 Person p = new Person(); Class personClass3 = p.getClass(); System.out.println(personClass3);//class Demo.Person // == 比较对象 System.out.println(personClass1 == personClass2 && personClass1 == personClass3);//true // 因此我们知道不论通过哪一种方式获取的Class对象都是同一个。 } }
结论:
- 同一个字节码文件(.class)在一次程序运行过程中,只会被加载一次,不论通过哪一种方式获取的Class对象都是同一个。
Class对象的功能(方法)
获取成员变量的功能
方法中带有Declared单词的就是私有的,需要暴力反射
- Field[] getFields() :获取所有public修饰的成员变量
- Field getField(String name) 获取指定名称的 public修饰的成员变量
- Field[] getDeclaredFields() 获取所有的成员变量,不考虑修饰符
- Field getDeclaredField(String name) 获取指定名称的成员变量,不考虑修饰符
操作获取到的成员变量
设置值
- void set(Object obj, Object value)
获取值
- get(Object obj)
如果要操作私有的成员变量则必须使用暴力反射
- setAccessible(true):暴力反射。忽略访问权限修饰符的安全检查
举例:
定义TestField类
package Demo; public class TestField { private String a; String b; protected String c; public String d; @Override public String toString() { return "TestField{" + "a='" + a + '\'' + ", b='" + b + '\'' + ", c='" + c + '\'' + ", d='" + d + '\'' + '}'; } }
定义测试类
package reflect; import Demo.TestField; import java.lang.reflect.Field; public class Demo01Reflect { public static void main(String[] args) throws Exception { //方式1 获取class对象 /* 方式1:Class.forName("全类名"):将字节码文件加载进内存,返回Class对象 全类名就是包名.类名全路径 */ Class DemoClass = Class.forName("Demo.TestField"); //Field[] getFields() :获取所有public修饰的成员变量 Field[] fields = DemoClass.getFields(); //遍历数组 for (Field field : fields) { System.out.println(field);//public java.lang.String Demo.Test.d } //Field getField(String name) 获取指定名称的 public修饰的成员变量 Field d = DemoClass.getField("d"); System.out.println(d);//public java.lang.String Demo.Test.d //我们拿到了成员变量之后可以获取其值 TestField t = new TestField(); Object o = d.get(t);// get 参数为获取哪个对象的成员变量值 System.out.println(o);//null //我们也可以设置值,set的第一个参数是为哪个对象,第二个参数设置什么值 d.set(t, "张三"); System.out.println(t);//TestField{a='null', b='null', c='null', d='张三'} //Field[] getDeclaredFields() 获取所有的成员变量,不考虑修饰符 Field[] declaredFields = DemoClass.getDeclaredFields(); for (Field declaredField : declaredFields) { System.out.println(declaredField); /* 遍历后的结果 ---------------------------------- private java.lang.String Demo.Test.a java.lang.String Demo.Test.b protected java.lang.String Demo.Test.c public java.lang.String Demo.Test.d */ } //Field getDeclaredField(String name)获取指定名称的成员变量,不考虑修饰符 Field a = DemoClass.getDeclaredField("a"); System.out.println(a);//private java.lang.String Demo.Test.a //要想获取和设置私有的必须暴力反射 a.setAccessible(true); //获取私有变量 System.out.println(a.get(t));//null //设置私有变量的值 a.set(t, "李四"); System.out.println(t);//TestField{a='李四', b='null', c='null', d='张三'} } }
获取构造方法的功能
构造方法的名字都是一样,我们通过参数来区分到底使用哪个构造方法,方法中带有Declared单词的就是私有的,需要暴力反射
- Constructor<?>[ ] getConstructors() 获取所有的被public修饰的构造方法
- Constructor<T> getConstructor(class<?>... parameterTypes) 获取指定参数被public修饰的构造方法
- Constructor<T> getDeclaredConstructor(class<?>... parameterTypes)
- Constructor<?>[] getDeclaredConstructors()
使用获取到的构造方法,创建对象
- T newInstance(Object... initargs)
举例
定义TestConstructor 类
package Demo; public class TestConstructor { private String name; private int age; public TestConstructor() { } public TestConstructor(String name, int age) { this.name = name; this.age = age; } @Override public String toString() { return "TestConstructor{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
定义测试类
package reflect; import Demo.TestConstructor; import java.lang.reflect.Constructor; public class Demo02Reflect { public static void main(String[] args) throws Exception { //方式2:类名.class:通过类名的属性class获取 多用于参数的传递 Class testConstructorClass = TestConstructor.class; // 获取指定参数构造方法, Constructor c1 = testConstructorClass.getConstructor(String.class, int.class); //使用指定参数的构造方法创建对象 Object o1 = c1.newInstance("早上", 18); System.out.println(o1);//TestConstructor{name='早上', age=18} //获取无参的构造方法 Constructor c2 = testConstructorClass.getConstructor(); //使用指定参数的构造方法创建对象 Object o2 = c2.newInstance(); System.out.println(o2);//TestConstructor{name='null', age=0} //不推荐下面这种方式创建空参对象 System.out.println(testConstructorClass.newInstance());//TestConstructor{name='null', age=0} } }
获取成员方法的功能:
方法中带有Declared单词的就是私有的,需要暴力反射
- Method[] getMethods()
- Method getMethod(String name, class<?>... parameterTypes)
- Method[ ] getDeclaredMethods()
- Method getDeclaredMethod(String name, class<?>... parameterTypes)
获取到方法,执行方法:
- Object invoke(Object obj, Object... args): 传递实际对象,和方法的参数
获取方法名称:
- String getName:获取方法名
自定义类
package reflect; public class Person { private String name; private int age; public String a; protected String b; String c; private String d; public Person() { } public Person(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + ", a='" + a + '\'' + ", b='" + b + '\'' + ", c='" + c + '\'' + ", d='" + d + '\'' + '}'; } public void eat() { System.out.println("eat..."); } public void eat(String food) { System.out.println("eat..." + food); } }
定义测试类
package reflect; import java.lang.reflect.Method; public class ReflectDemo4 { public static void main(String[] args) throws Exception { //0.获取Person的Class对象 Class personClass = Person.class; //获取指定名称的方法,参数为方法名称eat Method eat_method = personClass.getMethod("eat"); Person p = new Person(); //执行方法,参数为哪个对象的方法 eat_method.invoke(p);//eat... //区分方法,可以通过方法名称和参数列表来区分 Method eat_method2 = personClass.getMethod("eat", String.class); //执行方法 eat_method2.invoke(p, "饭");//eat...饭 //获取所有public修饰的方法 Method[] methods = personClass.getMethods(); for (Method method : methods) { System.out.println(method); //获取方法的名字 String name = method.getName(); System.out.println(name); } //获取类名 String className = personClass.getName(); System.out.println(className);//reflect.Person } }
注解
定义:注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。
概念描述:
- JDK1.5之后的新特性
- 说明程序的
- 使用注解:@注解名称
作用分类:
- ①编写文档:通过代码里标识的注解生成文档【生成文档doc文档】
- ②代码分析:通过代码里标识的注解对代码进行分析【使用反射】
- ③编译检查:通过代码里标识的注解让编译器能够实现基本的编译检查【Override】
JDK中预定义的一些注解
@Override :检测被该注解标注的方法是否是继承自父类(接口)的
@Deprecated:该注解标注的内容,表示已过时
@SuppressWarnings:压制警告 一般传递参数all ,压制所有的警告
压制类中所有的警告信息
自定义注解
格式:
元注解
public @interface 注解名称{
属性列表;
}
什么是元注解
元注解:用于描述注解的注解
- 描述注解能够作用的位置
- 描述注解被保留的阶段
- 描述注解是否被抽取到api文档中
- 描述注解是否被子类继承
本质:
- 注解本质上就是一个接口,该接口默认继承Annotation接口 。注解中的属性就是接口中的抽象方法
要求:
属性的返回值类型有下列取值
- 基本数据类型
- String
- 枚举
- 注解
- 以上类型的数组
定义了属性(抽象方法),在使用时需要给属性赋值
- 如果定义属性时,使用default关键字给属性默认初始化值,则使用注解时,可以不进行属性的赋值。
- 如果只有一个属性需要赋值,并且属性的名称是value,则value可以省略,直接定义值即可。
- 数组赋值时,值使用{}包裹。如果数组中只有一个值,则{}可以省略
小结:
- 以后大多数时候,我们会使用注解,而不是自定义注解
注解给谁用?
- 编译器
- 给解析程序用
- 注解不是程序的一部分,可以理解为注解就是一个标签
来源:https://www.cnblogs.com/wurengen/p/12256725.html