單元測試
- 針對最小的功能單元(方法)進行正確性測試
- 編寫正規(guī)的單元測試框架
- 傳統(tǒng)的無法執(zhí)行自動化測試,且無法得到測試報告
Junit單元測試框架
- Junit的作用:
具體步驟
- 測試類取名:原類名+Test(大駝峰)
- 測試方法取名:test+原函數(shù)名稱(小駝峰)
- 測試方法:必須public,無參,無返回值
- 測試方法上面必須加上@Test方法,不加不會跑的
- 測試方法只能判斷程序能否正常運行,并不能判斷功能是否正確,需要加斷言(Assert)
public class StringUtilTest {
@Test // 測試方法
public void testPrintNumber(){
// StringUtil.printNumber("admin");
StringUtil.printNumber(null);
}
}
公共的(Public):JUnit框架在運行測試時,需要能夠訪問到測試方法。如果測試方法不是公共的,JUnit框架可能無法訪問到它,從而無法執(zhí)行測試。
無參數(shù)的:JUnit框架在運行測試時,會自動調(diào)用測試方法,而不需要手動傳入?yún)?shù)。如果測試方法需要參數(shù),JUnit框架無法知道應(yīng)該傳入什么值,因此,測試方法不能有參數(shù)。
無返回值的(Void):JUnit框架通過斷言(Assert)來檢查測試是否通過,而不是通過返回值。因此,測試方法不需要返回值。
Assert斷言(重要)
@Test // 測試方法
public void testGetMaxIndex(){
int index1 = StringUtil.getMaxIndex(null);
System.out.println(index1);
int index2 = StringUtil.getMaxIndex("admin");
System.out.println(index2);
// 斷言機制:程序員可以通過預(yù)測業(yè)務(wù)方法的結(jié)果。
Assert.assertEquals("方法內(nèi)部有bug!", 4, index2);
}
- Assert.assertEquals(提示信息,希望的輸出,實際輸出)
自動化測試
- 自動化測試:IDEA中直接右鍵run就行(run整個類就是run所有有Test注解的方法)。
- 右鍵項目 run all_tests,測試所有
其他常見注解
public class StringUtilTest {
@Before //每次測試類的每個方法之前都執(zhí)行一次
public void test1(){
System.out.println("---> test1 Before 執(zhí)行了---------");
}
@BeforeClass //整個類只執(zhí)行一次
public static void test11(){
System.out.println("---> test11 BeforeClass 執(zhí)行了---------");
}
@After
public void test2(){
System.out.println("---> test2 After 執(zhí)行了---------");
}
@AfterClass
public static void test22(){
System.out.println("---> test22 AfterClass 執(zhí)行了---------");
}
@Test // 測試方法
public void testPrintNumber(){
// StringUtil.printNumber("admin");
StringUtil.printNumber(null);
}
}
- 在JUnit 5之后改名了:
反射
- 作用:加載類,并編程解析類中的各種成分(比如成員變量、方法、構(gòu)造器等)
- 關(guān)鍵:如何獲取類的各種信息
加載類
- 三種方法:
(方法三必須先new一個對象才能得到對應(yīng)的類的class對象)
public static void main(String[] args) throws Exception {
Class c1 = Student.class;
System.out.println(c1.getName()); // 全類名
System.out.println(c1.getSimpleName()); // 簡名:Student
Class c2 = Class.forName("com.itheima.d2_reflect.Student");
System.out.println(c1 == c2);
Student s = new Student();
Class c3 = s.getClass();
System.out.println(c3 == c2);
}
(forName的參數(shù)應(yīng)該是類的全類名:包名.類名)
獲取類的構(gòu)造器
- 不加Declare只能拿public的,加了就都能拿
- 拿單個構(gòu)造器應(yīng)該表明參數(shù)類型(String.class, int.class)(不然不知道是納哪個構(gòu)造器)
- 拿單個的時候會報異常,可以throws拋出
public void testGetConstructors(){
// 1、反射第一步:必須先得到這個類的Class對象
Class c = Cat.class;
// 2、獲取類的全部構(gòu)造器
// Constructor[] constructors = c.getConstructors();
Constructor[] constructors = c.getDeclaredConstructors();
// 3、遍歷數(shù)組中的每個構(gòu)造器對象
for (Constructor constructor : constructors) {
System.out.println(constructor.getName() + "--->"
+ constructor.getParameterCount());
}
}
Constructor constructor1 = c.getDeclaredConstructor();//無參構(gòu)造器
// 3、獲取有參數(shù)構(gòu)造器
Constructor constructor2 = c.getDeclaredConstructor(String.class, int.class);
獲取構(gòu)造器的作用:初始化對象返回(相當(dāng)于new)
newInstance 初始化對象
Cat cat2 = (Cat) constructor2.newInstance("叮當(dāng)貓", 3);
找到對應(yīng)的構(gòu)造器,相當(dāng)于new一個對象(實際開發(fā)還是用new),這里注意需要強轉(zhuǎn)(Cat)
setAccessible 修改權(quán)限(暴力反射)
// 3、獲取有參數(shù)構(gòu)造器
Constructor constructor2 =
c.getDeclaredConstructor(String.class, int.class);
System.out.println(constructor2.getName() + "--->"
+ constructor2.getParameterCount());
constructor2.setAccessible(true); // 禁止檢查訪問權(quán)限
Cat cat2 = (Cat) constructor2.newInstance("叮當(dāng)貓", 3);
System.out.println(cat2);
將原本private的構(gòu)造器硬在外部禁止檢查訪問權(quán)限,無論如何都會new出一個對象。
獲取類的成員變量
- 反射第一步:得到class對象
- 其他跟構(gòu)造器的差不多,包括declare,setAccessible等
- getName拿到名字,getType拿到數(shù)據(jù)類型
public void testGetFields() throws Exception {
// 1、反射第一步:必須是先得到類的Class對象
Class c = Cat.class;
// 2、獲取類的全部成員變量。
Field[] fields = c.getDeclaredFields();
// 3、遍歷這個成員變量數(shù)組
for (Field field : fields) {
System.out.println(field.getName() + "---> "+ field.getType());
}
// 4、定位某個成員變量
Field fName = c.getDeclaredField("name");
System.out.println(fName.getName() + "--->" + fName.getType());
Field fAge = c.getDeclaredField("age");
System.out.println(fAge.getName() + "--->" + fAge.getType());
}
獲取成員變量的作用:賦值(set)和取值(get)
// 賦值
Cat cat = new Cat();
fName.setAccessible(true); // 禁止訪問控制權(quán)限
fName.set(cat, "卡菲貓");
System.out.println(cat);
// 取值
String name = (String) fName.get(cat);
System.out.println(name);
- 必須有對象才能有對象的成員變量
- set(變量名,值)
- get(對象名),需要強轉(zhuǎn)。
setAccessible 修改權(quán)限(暴力反射)
fName.setAccessible(true); // 禁止訪問控制權(quán)限
獲取類的成員方法
- getName拿方法名,getParameterCount拿參數(shù)個數(shù),getReturnType拿返回類型
- getDeclaredMethod要寫參數(shù)類型,不然不知道找的是哪個同名函數(shù)
public void testGetMethods() throws Exception {
// 1、反射第一步:先得到Class對象。
Class c = Cat.class;
// 2、獲取類的全部成員方法。
Method[] methods = c.getDeclaredMethods();
// 3、遍歷這個數(shù)組中的每個方法對象
for (Method method : methods) {
System.out.println(method.getName() + "--->"
+ method.getParameterCount() + "---->"
+ method.getReturnType());
}
// 4、獲取某個方法對象
Method run = c.getDeclaredMethod("run", String.class); // 拿run方法,無參數(shù)的
獲取類方法的作用:執(zhí)行invoke
Cat cat = new Cat();
run.setAccessible(true); // 禁止檢查訪問權(quán)限
Object rs = run.invoke(cat); // 調(diào)用無參數(shù)的run方法,用cat對象觸發(fā)調(diào)用的。
//這里無返回值則返回null
System.out.println(rs);
eat.setAccessible(true); // 禁止檢查訪問權(quán)限
String rs2 = (String) eat.invoke(cat, "魚兒");
System.out.println(rs2);
反射的應(yīng)用場景
示例
框架:
public class ObjectFrame {
// 目標(biāo):保存任意對象的字段和其數(shù)據(jù)到文件中去
public static void saveObject(Object obj) throws Exception {
PrintStream ps = new PrintStream(new FileOutputStream("junit-reflect-annotation-proxy-app\\src\\data.txt", true));
// obj是任意對象,到底有多少個字段要保存。
Class c = obj.getClass();
String cName = c.getSimpleName();
ps.println("---------------" + cName + "------------------------");
// 2、從這個類中提取它的全部成員變量
Field[] fields = c.getDeclaredFields();
// 3、遍歷每個成員變量。
for (Field field : fields) {
// 4、拿到成員變量的名字
String name = field.getName();
// 5、拿到這個成員變量在對象中的數(shù)據(jù)。
field.setAccessible(true); // 禁止檢查訪問控制
String value = field.get(obj) + "";
ps.println(name + "=" + value);
}
ps.close();
}
}
測試:
public class Test5Frame {
@Test
public void save() throws Exception {
Student s1 = new Student("黑馬吳彥祖", 45, '男', 185.3, "藍(lán)球,冰球,閱讀");
Teacher t1 = new Teacher("播妞", 999.9);
// 需求:把任意對象的字段名和其對應(yīng)的值等信息,保存到文件中去。
ObjectFrame.saveObject(s1);
ObjectFrame.saveObject(t1);
}
}
注解(Annotation)
- java中的特殊標(biāo)記
- 作用:讓其他程序根據(jù)注解信息決定怎么執(zhí)行該程序。
- 可以用在類上、構(gòu)造器上、方法上、成員變量上、參數(shù)上等。
自定義注解
/**
* 自定義注解
*/
public @interface MyTest1 {
String aaa();
boolean bbb() default true;
String[] ccc();
}
如何使用?
@MyTest1(aaa="牛魔王", ccc={"HTML", "Java"})
public class AnnotationTest1 {
@MyTest1(aaa="鐵扇公主", bbb=false, ccc={"Python", "前端", "Java"})
public void test1(){
}
public static void main(String[] args) {
}
}
(有參數(shù)的一定要填,有默認(rèn)參數(shù)的不必須要填)
特殊屬性名:value
- 如果注解中只有一個value屬性(或多余的數(shù)值為默認(rèn)值default),使用@注解時可以不寫value名稱
public @interface MyTest2 {
String value(); // 特殊屬性
int age() default 23;
}
@MyTest2("孫悟空")
public class AnnotationTest1 {
@MyTest1(aaa="鐵扇公主", bbb=false, ccc={"Python", "前端", "Java"})
public void test1(){
}
public static void main(String[] args) {
}
}
注解的原理:本質(zhì)是一個接口?。。?/h3>
- 把注解編譯成class再反
- 本質(zhì)是一個繼承Annotation接口的接口??!!
- 使用注解時創(chuàng)建實現(xiàn)類對象,把參數(shù)傳進去
元注解
- 定義:修飾注解的注解
- 常見:@Target和Retention
@Target
- TYPE:比如Field(變量),TYPE(類)等,多個用{,}隔開
@Target({ElementType.TYPE, ElementType.METHOD}) // 當(dāng)前被修飾的注解只能用在類上,方法上。
public @interface MyTest3 {
}
@MyTest3
public class AnnotationTest2 {
// @MyTest3 這里會報錯
private String name;
@MyTest3
public void test(){
}
}
Retention
@Retention(RetentionPolicy.RUNTIME) // 控制下面的注解一直保留到運行時
public @interface MyTest3 {
}
- RUNTIME:一直保留到運行階段(常用)
注解的解析
- 定義:判斷類上/方法上等上是否存在注解,并把注解里的內(nèi)容解析出來
- 方法
解析案例
MyTest4:
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest4 {
String value();
double aa() default 100;
String[] bbb();
}
Demo:
@MyTest4(value = "蜘蛛精", aaa=99.5, bbb = {"至尊寶", "黑馬"})
@MyTest3
public class Demo {
@MyTest4(value = "孫悟空", aaa=199.9, bbb = {"紫霞", "牛夫人"})
public void test1(){
}
}
AnnotationTest3:
public class AnnotationTest3 {
@Test
public void parseClass(){
// 1、先得到Class對象
Class c = Demo.class;
// 2、解析類上的注解
// 判斷類上是否包含了某個注解
if(c.isAnnotationPresent(MyTest4.class)){//這里是.class而不是直接輸出類
MyTest4 myTest4 =
(MyTest4) c.getDeclaredAnnotation(MyTest4.class);
System.out.println(myTest4.value());
System.out.println(myTest4.aaa());
System.out.println(Arrays.toString(myTest4.bbb()));
}
}
@Test
public void parseMethod() throws Exception {
// 1、先得到Class對象
Class c = Demo.class;
Method m = c.getDeclaredMethod("test1");
// 2、解析方法上的注解
// 判斷方法對象上是否包含了某個注解
if(m.isAnnotationPresent(MyTest4.class)){
MyTest4 myTest4 =
(MyTest4) m.getDeclaredAnnotation(MyTest4.class);
System.out.println(myTest4.value());
System.out.println(myTest4.aaa());
System.out.println(Arrays.toString(myTest4.bbb()));
}
}
}
應(yīng)用場景
- 與反射配合做框架
示例
定義MyTest注解:
@Target(ElementType.METHOD) // 注解只能注解方法。
@Retention(RetentionPolicy.RUNTIME) // 讓當(dāng)前注解可以一直存活著。
public @interface MyTest {
}
定義若干個方法:
public class AnnotationTest4 {
// @MyTest 不會被執(zhí)行
public void test1(){
System.out.println("===test1====");
}
@MyTest
public void test2(){
System.out.println("===test2====");
}
@MyTest
public void test3(){
System.out.println("===test3====");
}
@MyTest
public void test4(){
System.out.println("===test4====");
}
public static void main(String[] args) throws Exception {
.......
}
}
}
模擬Junit程序:通過反射配合注解解析
public static void main(String[] args) throws Exception {
AnnotationTest4 a = new AnnotationTest4();
// 啟動程序!
// 1、得到Class對象
Class c = AnnotationTest4.class;
// 2、提取這個類中的全部成員方法
Method[] methods = c.getDeclaredMethods();
// 3、遍歷這個數(shù)組中的每個方法,看方法上是否存在@MyTest注解,存在
// 觸發(fā)該方法執(zhí)行。
for (Method method : methods) {
if(method.isAnnotationPresent(MyTest.class)){
// 說明當(dāng)前方法上是存在@MyTest,觸發(fā)當(dāng)前方法執(zhí)行。
method.invoke(a);
}
}
}
調(diào)用結(jié)果:
- 1、得到Class對象
- 2、提取這個類中的全部成員方法
- 3、遍歷這個數(shù)組中的每個方法,看方法上是否存在@MyTest注解,執(zhí)行(必須創(chuàng)建對象,重新new一個)
動態(tài)代理
- 意義:對象身上事兒太多,需要通過代理轉(zhuǎn)移部分職責(zé);
- 含義:對象有什么方法想被代理,代理也得有,但不真正做,而是一些其他操作+調(diào)用對象的。
- 怎么做:對象聲明接口,代理設(shè)置實現(xiàn)類。
- 接口:
public interface Star {
String sing(String name);
void dance();
}
- 用明星類實現(xiàn)該接口:
public class BigStar implements Star{
private String name;
public BigStar(String name) {
this.name = name;
}
public String sing(String name){
System.out.println(this.name + "正在唱:" + name);
return "謝謝!謝謝!";
}
public void dance(){
System.out.println(this.name + "正在優(yōu)美的跳舞~~");
}
}
- 讓代理類實現(xiàn)該接口ProxyUtil(工具類,靜態(tài)方法)
import java.lang.reflect.Proxy;
public class ProxyUtil {
public static Star createProxy(BigStar bigStar){
.......
return starProxy;
}
}
newProxyInstance:
newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
參數(shù)1:用于指定一個類加載器
參數(shù)2:指定生成的代理長什么樣子,也就是有哪些方法(接收的接口數(shù)組)
參數(shù)3:用來指定生成的代理對象要干什么事情(new一個接口的匿名內(nèi)部類對象)
Star starProxy = (Star) Proxy.newProxyInstance(ProxyUtil.class.getClassLoader(),
new Class[]{Star.class}, new InvocationHandler() {
@Override // 回調(diào)方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 代理對象要做的事情,會在這里寫代碼
if(method.getName().equals("sing")){
System.out.println("準(zhǔn)備話筒,收錢20萬");
}else if(method.getName().equals("dance")){
System.out.println("準(zhǔn)備場地,收錢1000萬");
}
return method.invoke(bigStar, args);
}
});
回調(diào)方法invoke
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 代理對象要做的事情,會在這里寫代碼
if(method.getName().equals("sing")){//當(dāng)前正在代理sing方法
System.out.println("準(zhǔn)備話筒,收錢20萬");
}else if(method.getName().equals("dance")){
System.out.println("準(zhǔn)備場地,收錢1000萬");
}
return method.invoke(bigStar, args);//返回方法的返回值
}
- proxy:代理對象
- method:方法;
- args:方法的參數(shù)
如何調(diào)用?test:
public class Test {
public static void main(String[] args) {
BigStar s = new BigStar("楊超越");
Star starProxy = ProxyUtil.createProxy(s);
String rs = starProxy.sing("好日子");
System.out.println(rs);
starProxy.dance();
}
}
(可通過單步調(diào)試追蹤調(diào)用鏈條)
應(yīng)用場景:AOP
動態(tài)代理設(shè)計模式主要用于以下幾種場景:
- 接口適配:當(dāng)一個類已經(jīng)實現(xiàn)了某個接口,但是需要對該接口的某些方法進行修改時,可以使用動態(tài)代理。動態(tài)代理可以在不修改原有代碼的情況下,對接口的方法進行增強。
- AOP(面向切面編程):動態(tài)代理是實現(xiàn)AOP的一種方式。在AOP中,通常會在業(yè)務(wù)方法執(zhí)行前后插入一些公共的代碼,如日志、事務(wù)管理等。動態(tài)代理可以在運行時動態(tài)地為目標(biāo)對象生成一個代理對象,然后通過代理對象調(diào)用目標(biāo)方法,從而實現(xiàn)在目標(biāo)方法執(zhí)行前后插入公共代碼。
- 遠(yuǎn)程調(diào)用:在遠(yuǎn)程方法調(diào)用(RMI)中,客戶端實際上是調(diào)用的是本地的一個代理對象,這個代理對象負(fù)責(zé)與遠(yuǎn)程服務(wù)器通信。這個代理對象就是通過動態(tài)代理生成的。
- 權(quán)限控制:動態(tài)代理可以用于實現(xiàn)權(quán)限控制。例如,當(dāng)調(diào)用某個方法時,可以通過動態(tài)代理檢查用戶是否有權(quán)限執(zhí)行該方法。
- 性能監(jiān)控:動態(tài)代理可以用于監(jiān)控方法的執(zhí)行時間,從而實現(xiàn)性能監(jiān)控。例如,可以在方法執(zhí)行前后獲取系統(tǒng)時間,然后計算出方法的執(zhí)行時間。
- 單元測試:在單元測試中,經(jīng)常需要模擬一些對象。這些模擬對象可以通過動態(tài)代理生成。例如,可以生成一個代理對象,這個代理對象的所有方法都返回默認(rèn)值。
什么是AOP?
**面向切面編程(Aspect-Oriented Programming,AOP)**是一種編程范式,其目標(biāo)是提高模塊化的能力,特別是對于橫切關(guān)注點(cross-cutting concerns)的處理。橫切關(guān)注點是那些分散在多個模塊中,但不能很好地通過傳統(tǒng)的面向?qū)ο缶幊蹋∣OP)模塊化的問題,例如日志記錄、事務(wù)管理、安全性等。
在AOP中,一個切面(Aspect)代表一個橫切關(guān)注點,它可以包含一些通用的代碼。這些代碼可以被定義為通知(Advice),然后通過切入點(Pointcut)插入到目標(biāo)對象的方法中。通知定義了何時(例如,方法調(diào)用前、后或異常拋出時)以及如何(執(zhí)行何種代碼)應(yīng)用切面。
例如,你可能有一個用于記錄日志的切面,它的通知在每個方法調(diào)用前后記錄日志,切入點定義了這個切面應(yīng)用于哪些方法。文章來源:http://www.zghlxwxcb.cn/news/detail-850981.html
AOP可以幫助我們將這些橫切關(guān)注點從業(yè)務(wù)邏輯中分離出來,使得業(yè)務(wù)邏輯更加清晰,同時也更易于維護和重用。文章來源地址http://www.zghlxwxcb.cn/news/detail-850981.html
到了這里,關(guān)于Day14:單元測試、Junit單元測試框架、反射、注解的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!