自動化測試如今愈來愈趨向於平臺化,平臺化是致力於協同工做,提升效率,讓更多人蔘與自動化的一個過程,在我看來,平臺化中,有一個更爲關鍵點,就是關鍵字驅動,只有把自動化測試的代碼轉換成爲你們更容易懂的天然語言,才能讓更多不懂代碼的人加入進去,才能達到平臺化的目的。今天咱們就來談談自動化測試中關鍵字驅動的原理及實現方式。java
先來看一個例子,老師對着同窗們說了一句:去把桌子從A地點搬到B地點。當老師發出這個命令後,發現沒有人動,由於沒有明確的對象,不知道這個命令是對誰發出的,因而老師再說了一句:張三,去把桌子從A地點搬到B地點。這時候,有了明確的對象--張三,有了命令的主體--搬桌子從A到B。因此這句」 張三,去把桌子從A地點搬到B地點」中就有兩個關鍵字:張3、把桌子從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. 得有一個關鍵字的解析方式,好比上面那條命令中,咱們要可以得出張3、把桌子從A地點搬到B地點這兩個關鍵字,要是不能得出關鍵字,關鍵方法就會少執行,就如上面的僞代碼中,若是去掉md.setZhangsan("張三")這一句,顯然就會報空指針異常了。this
4. 上面這三條具有後,就是執行了,老師發出的命令,張三要可以執行,固然,要是張三是個刺頭就另說了。JAVA代碼可以交給JVM去執行,但轉換成爲關鍵字後,JVM就不認識這些關鍵字了,因此,咱們也必需要有一個健壯的執行引擎,讓「張三,去把桌子從A地點搬到B地點」這一句話可以被執行成功。spa
綜上所述,只要咱們把上面的四條經過代碼來實現,一個簡單的關鍵字框架雛形就出來了,接下來咱們一條條的實現。指針
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這個對象裏,方面後面直接調用關鍵方法。(請你們試着用註解去實現,會更炫)blog
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)等。也有很大的優化空間留給你們自行發揮去吧!