自動化測試關鍵字驅動的原理及實現


   自動化測試現在越來越趨向於平台化,平台化是致力於協同工作,提高效率,讓更多人參與自動化的一個過程,在我看來,平台化中,有一個更為關鍵點,就是關鍵字驅動,只有把自動化測試的代碼轉換成為大家更容易懂的自然語言,才能讓更多不懂代碼的人加入進去,才能達到平台化的目的。今天我們就來談談自動化測試中關鍵字驅動的原理及實現方式。

       先來看一個例子,老師對着同學們說了一句:去把桌子從A地點搬到B地點。當老師發出這個命令后,發現沒有人動,因為沒有明確的對象,不知道這個命令是對誰發出的,於是老師再說了一句:張三,去把桌子從A地點搬到B地點。這時候,有了明確的對象--張三,有了命令的主體--搬桌子從A到B。所以這句” 張三,去把桌子從A地點搬到B地點”中就有兩個關鍵字:張三、把桌子從A地點搬到B地點。當這兩個關鍵字缺少任何一個,這件事就不可能完成。所以,我們可以把這個場景理解為關鍵字驅動,假如我們把上面這句話轉換為代碼:

public class MoveDesk {
	
	private Person zhangsan;

	public void setZhangsan(Person zhangsan) {
		this.zhangsan = zhangsan;
	}

	public void moveDeskFromA2B(){
		zhangsan.getTheCommandFromTeacher();
		zhangsan.findTheDeskAtA();
		zhangsan.moveDeskToB();
		zhangsan.notifyTeacherHadFinished();
	}

	public static void main(String[] args) {
		MoveDesk md = new MoveDesk();
		md.setZhangsan("張三");
		md.moveDeskFromA2B();
	}

}

 

上面的代碼當然是偽代碼,但從中可以看出,其為了達到一個目的,流程都是一樣的,那兩個關鍵字在main方法中體現為:張三對應md.setZhangsan("張三"),”把桌子從A地點搬到B地點”對應md.moveDeskFromA2B()。由此我們可以這樣理解一下:每一個關鍵字對應一個具體的方法。所以,總結一下關鍵字驅動的實現特點:

1. 每一個關鍵字對應的方法(以下稱為關鍵方法)必須能夠隨時被調用,這就像老師對張三發出的命令張三必須去執行一樣。在JAVA里面為了達到這個關鍵方法隨時能被調用,其類對象必須事先就被new好,所以要有一個對象池,這個對象池就像一個班級,有很多個學生可以供老師發出命令並去執行。

2. 關鍵字必須與關鍵方法進行關聯,就像老師讓張三搬桌子,而張三跑去買了一包煙,這就不對了,所以,必須要有一個關鍵字與關鍵方法的映射表。

3. 得有一個關鍵字的解析方式,比如上面那條命令中,我們要能夠得出張三、把桌子從A地點搬到B地點這兩個關鍵字,要是不能得出關鍵字,關鍵方法就會少執行,就如上面的偽代碼中,如果去掉md.setZhangsan("張三")這一句,顯然就會報空指針異常了。

4. 上面這三條具備后,就是執行了,老師發出的命令,張三要能夠執行,當然,要是張三是個刺頭就另說了。JAVA代碼能夠交給JVM去執行,但轉換成為關鍵字后,JVM就不認識這些關鍵字了,所以,我們也必須要有一個健壯的執行引擎,讓“張三,去把桌子從A地點搬到B地點”這一句話能夠被執行成功。

綜上所述,只要我們把上面的四條通過代碼來實現,一個簡單的關鍵字框架雛形就出來了,接下來我們一條條的實現。

1.對象池:

public class RegisterCenter {
	
	public static Map<String, Object> OBJ_POOLS = new HashMap<String, Object>();
	
	static{
		OBJ_POOLS.put(MoveDesk.class.getName(), new MoveDesk());
	}
}

 

將所有含有關鍵方法的類都初始化一遍,然后放到OBJ_POOLS這個對象里,方面后面直接調用關鍵方法。(請大家試着用注解去實現,會更炫)

2.映射表:

public class KeywordReflect {
	
	public static Map<String, Map<String, String>> KEYWORD_POOLS = new HashMap<String, Map<String, String>>();
	
	static{
		KEYWORD_POOLS.put("張三", KeywordReflect.methodInfo(MoveDesk.class.getName(), "setZhangsan"));
		KEYWORD_POOLS.put("把桌子從A地點搬到B地點", KeywordReflect.methodInfo(MoveDesk.class.getName(), "moveDeskFromA2B"));
	}
	
	public static Map<String, String> methodInfo(String className, String methodName){
		Map<String, String> methodInfo = new HashMap<String, String>();
		methodInfo.put("class", className);
		methodInfo.put("method", methodName);
		return methodInfo;
	}
	
}

 

說明:上面的KEYWORD_POOLS對象的數據結構是MAP里面套了一個MAP,這是因為要明確類對象,因為不同的類里可能有相同名稱的方法,所以,為了避免混亂,必須標注好一個方法的類與方法名,這樣才能建立好一一對應的映射表。(同樣可以用注解來實現)

3.解析關鍵字

為了能夠解析出關鍵字,我們得把” 張三,去把桌子從A地點搬到B地點”這句話的關鍵字給標注出來,改造一下”${張三},去${把桌子從A地點搬到B地點}”,這樣就把關鍵字給明確的標注出來了,雖然從直觀感受上差了那么一點點,但卻是關鍵字驅動中很重要的一部分。接下來的問題轉變成為了對一段字符串中${}的解析了:

正則類:

public class RegExp {
	
	public boolean match(String reg, String str) {
        return Pattern.matches(reg, str);
    }
 
    public List<String> find(String reg, String str) {
        Matcher matcher = Pattern.compile(reg).matcher(str);
        List<String> list = new ArrayList<String>();
        while (matcher.find()) {
            list.add(matcher.group());
        }
        return list;
    }
     
}

 獲取關鍵字類:

public class ParseKeyword {
	
	public List<String> getKeywords(String p){
	    String reg = "(?<=(?<!\\\\)\\$\\{)(.*?)(?=(?<!\\\\)\\})";     
	    RegExp re = new RegExp();
	    List<String> list = re.find(reg, p);
	    return list;
	}
	
	public static void main(String[] args) {
		ParseKeyword p = new ParseKeyword();
		System.out.println(p.getKeywords("a${a}a"));
		System.out.println(p.getKeywords("a\\${a}a"));
		System.out.println(p.getKeywords("a${a\\}a"));
		System.out.println(p.getKeywords("a${a\\}a}a"));
		System.out.println(p.getKeywords("a${a}a${"));
		System.out.println(p.getKeywords("a${ab}a${a}"));
	}
}

 

說明:里面用到了正則預查模式,正則預查模式是正則中的一種相對高階的用法,掌握這個后,你的正則就會上一個台階。

4. 執行引擎:

執行引擎先要找到需要執行的語句,所以,首先應該把老師發的那個命令給讀取出來,這個命令可以存放在任何格式的文件里,只要能被讀出來即可,在這里我們保存在command.txt里:

讀取這個命令:

public class Executor {
	
	public List<String> readTxtFile(String filePath) {
		List<String> list = new ArrayList<String>();
        try {
            String encoding = "UTF8";
            File file = new File(filePath);
            if (file.isFile() && file.exists()) {
                InputStreamReader read = new InputStreamReader(new FileInputStream(file), encoding);
                BufferedReader bufferedReader = new BufferedReader(read);
                String lineTxt = null;
                while ((lineTxt = bufferedReader.readLine()) != null) {
                	list.add(lineTxt);
                }
                read.close();
                bufferedReader.close();
            } else {
                System.out.println("找不到指定的文件");
            }
        } catch (Exception e) {
            System.out.println("讀取文件內容出錯");
            e.printStackTrace();
        }
        return list;
    }
	
	public static void main(String[] args) {
		Executor e = new Executor();
		System.out.println(e.readTxtFile("src/command.txt"));
	}
	
}

 

讀取之后,流程就是:獲取關鍵字->獲取關鍵方法->執行關鍵方法.

public class Executor {
	
	private ParseKeyword pk = new ParseKeyword();
	
	public List<String> readTxtFile(String filePath) {
		List<String> list = new ArrayList<String>();
        try {
            String encoding = "UTF8";
            File file = new File(filePath);
            if (file.isFile() && file.exists()) {
                InputStreamReader read = new InputStreamReader(new FileInputStream(file), encoding);
                BufferedReader bufferedReader = new BufferedReader(read);
                String lineTxt = null;
                while ((lineTxt = bufferedReader.readLine()) != null) {
                	list.add(lineTxt);
                }
                read.close();
                bufferedReader.close();
            } else {
                System.out.println("找不到指定的文件");
            }
        } catch (Exception e) {
            System.out.println("讀取文件內容出錯");
            e.printStackTrace();
        }
        return list;
    }
	
	public void executor(){
		List<String> commands = this.readTxtFile("src/command.txt");
		for (String command : commands) {
			List<String> keywords = pk.getKeywords(command);
			for (String keyword : keywords) {
				this.invoke(keyword);
			}
		}
	}
	
	public void invoke(String keyword){
		Map<String, String> keyMethod = KeywordReflect.KEYWORD_POOLS.get(keyword);
		String className = keyMethod.get("class");
		String methodName = keyMethod.get("method");
		Object obj = RegisterCenter.OBJ_POOLS.get(className);
		Method method = this.getMethod(methodName, obj);
		try {
			method.invoke(obj);
		} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
			e.printStackTrace();
		}
	}
	
	private Method getMethod(String methodName, Object obj) {
		try {
			Method[] methods = obj.getClass().getMethods();
			for (Method m : methods) {
				if (m.getName().equals(methodName)) {
					return m;
				}
			}
		} catch (SecurityException e) {
			return null;
		}
		return null;
	}
	
	public static void main(String[] args) {
		Executor e = new Executor();
		e.executor();
	}
	
}

 

為了大家能夠看到執行結果,把MoveDesk類給改改:

public class MoveDesk {
	
	public void setZhangsan() {
		System.out.println("this is zhangsan");
	}

	public void moveDeskFromA2B(){
		System.out.println("this is test!");
	}

}

 

這樣執行之后,就能看到這兩個關鍵方法體被執行了!執行引擎中最關鍵的就是利用了JAVA的反射來執行方法!

以上的代碼經過測試運行通過!

由此一個最最簡單的關鍵字驅動架子就搭建完成了,還有如下的一些點需要大家自行去完成:關鍵方法參數處理,關鍵方法返回值處理,異常處理,結合數據驅動,結合測試框架(TESTNG/JUNIT)等。也有很大的優化空間留給大家自行發揮去吧!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM