使用Groovy構建自己的腳本環境


場景

在進行Web服務端開發的時候,發布前通常需要測試一遍。對於一個大一點的項目,最好的辦法是寫個自動化測試程序。
以Groovy為例,寫測試代碼之前通常的有如下幾個操作

  1. 引用相關的類庫
  2. import相關的類
  3. 對庫不熟悉的時候你很可能得先把庫的文檔好好看一遍

對於你來說,你需要的可能僅僅是post,get等幾個簡單的操作而已,而上面的操作更是可能占用你整個開發過程的大部分時間。

Orz....項目進度沒跟上,又要加班了。。。。

要是有一種語言,本身自帶post,get這樣的函數那該多好啊,測試程序嘩啦嘩啦就寫完了!

解決方案

通過Groovy構建一個腳本環境,自帶post,get這些常用的函數

效果

再也不用手動引用庫了,再也不用手動import類了,再也不用復習好長好長的文檔了,寫測試腳本再也腰不疼、腿不麻了!

原理

groovy本身是一個強大的腳本引擎,同時也是高度可定制化的。
在groovy編譯腳本的時候,可以為腳本指定一個基類,這個基類的所有方法和屬性都可以直接在腳本引用。

實現

首先,先新建一個工程,這里用gradle作為構建工具,取工程名為httpbatch

新建build.gradle

apply plugin: 'groovy'
apply plugin: 'eclipse'
apply plugin: 'application'
mainClassName = 'com.kasonyang.httpbatch.Application'
repositories {
    mavenCentral()
}
dependencies {
	compile 'org.codehaus.groovy:groovy-all:2.3.10'
	compile 'org.apache.httpcomponents:httpclient:4.5'
}

工程引用了apache的httpclient組件,並指定主類為com.kasonyang.httpbatch.Application

生成Eclipse工程

$ gradle eclipse

然后我們就可以使用Eclipse導入工程了。

創建主類Application.groovy

package com.kasonyang.httpbatch
import com.kasonyang.httpbatch.test.TestException
import org.codehaus.groovy.control.CompilerConfiguration
class Application {

	static void printUsage(){
		println """
usage : httpbatch file #execute a file
     or httpbatch -s   #enter shell mode
"""
	}

	static void main(String[] args) {
		if(args.length<1){
			printUsage()
			return
		}
		def reader,scriptStr = ''
		switch(args[0]){
			case '-s':
				print ">"
				reader	= System.in
				reader.eachLine {it->
					scriptStr +=  it + '\n'
					if(it == ''){
						runScript(scriptStr)
						scriptStr = ''
					}//else{
					print '>'
					//}
				}
				break
			default:
				def file = new File(args[0])
				runScript(file)
				break
		}
	}

	private static String getExceptionStack(Exception ex,String clsName){
		String msg = ""
		for(stack in ex.stackTrace){
			if(stack.className == clsName){
				def fileName = stack.fileName
				def lineNumber = stack.lineNumber.toString()
				msg += ("\tFile:${fileName}(${lineNumber})")
			}
		}
		msg
	}

	private static runScript(def it){
		def config = new CompilerConfiguration()
		config.scriptBaseClass = 'com.kasonyang.httpbatch.HttpBatchScript'
		def shell = new GroovyShell(config)
		def scriptClass
		try{
			//shell.evaluate(file)
			def script = shell.parse(it)
			scriptClass = script.class.name
			def stateReturn = script.run()
			//System.out.println(stateReturn)
		}catch(TestException ex){
			println "test fail:"
			println getExceptionStack(ex,scriptClass)
			println "\texcepted:${ex.excepted}\n\tactual:${ex.actual}"
		}catch(Exception ex){
			println ex.message
			println getExceptionStack(ex,scriptClass)
		}catch(RuntimeException ex){
			println ex.message
			println getExceptionStack(ex,scriptClass)
		}
	}
}

Application指定了腳本的基類為com.kasonyang.httpbatch.HttpBatchScript,這個類就是主題的主體。

先上代碼

package com.kasonyang.httpbatch
import com.kasonyang.httpbatch.test.TestException
import com.kasonyang.httprequest.HttpRequest
import com.kasonyang.httprequest.HttpResponse
import groovy.lang.Binding
import groovy.lang.Script;
abstract class HttpBatchScript extends Script {

	private http = new HttpRequest();
	HttpResponse $$

	Closure beforeGo,beforePost,afterGo,afterPost

	Closure testFail

	private String base = ''
	private String path = ''

	private String trimPath(String path,boolean left=true,boolean right=true){
		int start =(left && path.startsWith('/')) ? 1 : 0;
		int end = (right && path.endsWith('/')) ? path.length()-1 : path.length();
		path.substring(start,end)
	}

	private def getUri(uri){
		base + (base?'/':'') + path + (path?'/':'') + trimPath(uri,true,false)
	}

	def base(){
		this.base = ''
	}

	/**
	 * set the base path of request
	 * @param path the base path
	 * @return
	 */
	def base(String path){
		this.base = trimPath(path)
	}

	def enter(){
		this.path = ''
	}

	/**
	 * enter a directory in base
	 * @param path
	 * @return
	 */
	def enter(String path){
		this.path = trimPath(path)
	}

	/**
	 * submit a get request
	 * @param uri the request uri
	 * @param params the query params
	 * @param callback call after request
	 * @return
	 */
	HttpResponse go(String uri,Map params,Closure callback){
		def httpGet = http.createGet(getUri(uri),params)
		this.beforeGo?.call(httpGet)
		def response = http.execute(httpGet)
		this.$$ = response
		if(callback) callback.call()
		this.afterGo?.call(response)
		return this.$$
	}
	HttpResponse  go(String uri,Closure callback){
		return go(uri,[:],callback)
	}
	HttpResponse  go(String uri,Map params){
		return go(uri,params,null)
	}
	HttpResponse  go(String uri){
		return this.go(uri,null)
	}

	/**
	 * submit a post request
	 * @param uri the request uri
	 * @param params the post params
	 * @param callback call after request
	 * @return
	 */
	HttpResponse  post(String uri,Map params,Closure callback){
		def httpPost = http.createPost(getUri(uri),params)
		this.beforePost?.call(httpPost)
		def response = http.execute(httpPost)
		this.$$ = response
		if(callback) callback.call()
		this.afterPost?.call(response)
		return this.$$
	}
	HttpResponse  post(String uri,Closure callback){
		return post(uri,[:],callback)
	}
	HttpResponse  post(String uri,Map params){
		return post(uri,params,null)
	}
	HttpResponse  post(String uri){
		return this.post(uri,null)
	}

	/**
	 * set the beforeGo callback,which whill be call before every get request
	 * @param callback
	 */
	void beforeGo(Closure callback){
		this.beforeGo = callback
	}

	/**
	 * set the beforePost callback,which whill be call before every post request
	 * @param callback
	 */
	void beforePost(Closure callback){
		this.beforePost = callback
	}

	/**
	 * set the callback,which whill be call when test fail
	 * @param cb
	 */
	void testFail(Closure cb){
		this.testFail = cb
	}

	/**
	 * set the callback,which whill be call after every get request
	 * @param callback
	 */
	void afterGo(Closure callback){
		this.afterGo = callback
	}

	/**
	 * set the callback,which whill be call after every post request
	 * @param callback
	 */
	void afterPost(Closure callback){
		this.afterPost = callback
	}

	/**
	 * test whether it is true
	 * @param value
	 */
	void testTrue(Object value){
		testEquals(true,value)
	}

	/**
	 * test whether actual equals the excepted
	 * @param excepted
	 * @param actual
	 */
	void testEquals(Object excepted,Object actual){
		if(excepted != actual){
			def ex = new TestException(excepted,actual)
			if(this.testFail){
				testFail(ex)
			}else{
				throw ex
			}
		}
	}

	/**
	 * test whether it is null
	 * @param value
	 */
	void testNull(Object value){
		testEquals(null,value)
	}

}

這個類主要定義了一個public屬性$$還有幾個public方法,也就是post、go,和一些其它可能需要用到的函數。

因為get方法在groovy里有特殊意義,這里使用go方法名代替了get。

提示:這里使用了另外兩個類,HttpRequest和HttpResponse,是自定義的兩個Class,由於篇幅的原因,這里就不再貼代碼了,具體實現可前往Github查看。

我已經把全部源碼放到了Github,感興趣的可以前往查看。

地址:https://github.com/kasonyang/httpbatch

構建項目

$ gradle installDist

程序被輸出到build/install/httpbatch目錄下,將bin目錄添加到環境變量PATH中。

使用

  • 創建腳本文件 "example.hb"
go "YOU_URL"//對你要測試的URL提交get請求
testEquals 200,$$.statusCode//狀態碼為 200?
def text = $$.text //get the response as text
def json = $$.json//get the response as json
println text //output the response
//這里添加你的測試邏輯代碼
println "Test successfully!"
  • 執行腳本文件
$ httpbatch example.hb


免責聲明!

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



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