1.Label:每個 JMeter 的 element (例如 HTTP Request )都有一個 Name 屬性,這里顯示的就是 Name 屬性的值
2.#Samples:表示你這次測試中一共發出了多少個請求,如果測試計劃模擬10個用戶,每個用戶迭代10次,這里就會顯示100
3.Average:平均響應時間 — 默認情況下是單個 Request 的平均響應時間,當使用了事務控制器時,也可以以事務為單位顯示平均響應時間
4.Median:中位數,也就是 50 %用戶的響應時間
5.90% Line: 90 %用戶的響應時間
6.Min:最小響應時間
7.Max:最大響應時間
8.Error%:錯誤率,本次測試中出現錯誤的請求的數量 / 請求的總數
9.Throughput:吞吐量 —— 默認情況下表示每秒完成的請求數( Request per Second )
10.KB/Sec:每秒從服務器端接收到的數據量
以下是為什么敏捷方法行之有效的原因:
1. 敏捷方法和傳統的計劃驅動方法的兩個主要區別
i. 預測性計劃(Predictive Planning)和自適應計劃(Adaptive Planning)
計劃驅動方法首先計劃要做的工作(plan your work),然后著手工作以完成計劃(work your plan)。這是一種帶有預測性質的方法,其衡量項目成功的標準則是我們是否按計劃、按時、按預算完成了工作。這種方法在很多領域里是適用的。但是對于軟件開發而言,如果我們的需求沒有辦法做到不變更的話,我們就無法保證我們的計劃以及其后的工作是不會變更的。Martin Fowler 向現場觀眾提出了一個問題,大意是你們當中有多少人的軟件開發項目的需求是一成不變的,結果沒有一位觀眾舉手。因此,敏捷方法引入了自適應計劃的概念,既然我們無法保證需求不變更,那么就讓我們隨時準備接受變更,接受挑戰吧。自適應計劃將計劃驅動的流程縮短為以數周為單位的循環周期,在每一個周期中,我們根據當前的情況不斷地調整計劃以及計劃的執行過程,同時不斷地產生能夠工作的代碼,并且不斷地將代碼部署到應用環境中去。當然要實現這個目標我們需要一些具體方法的支持,如:自
測試代碼(Self-Testing Code),持續集成(Continuous Integration),重構(Refactoring),和簡潔設計(Simple Design)等等這些技術層面上的方法。Martin Fowler 指出,一些公司和項目之所以受困于敏捷方法,原因之一是他們忽略了這些技術層面的方法,而僅僅實施了
項目管理層面的方法。
ii. 以流程為本(Process First)和以人為本(People First)
在傳統的方法論中,我們總是需要事先定義好工作的方法和流程,然后“工人們”被要求遵照這些方法和流程來工作。在軟件開發領域,很多人把軟件開發過程等同于軟件本身,也就是說,軟件開發的過程也如同軟件程序般象機器一樣運行,組件之間環環相扣,嚴密地協同工作。問題是軟件開發的核心是人,人相對于機器零件和流水線而言,是相對不可預測的和不那么精密的。所以敏捷方法反其道而行之,提倡將“首先定義流程,然后要求軟件開發人員遵照流程工作”變為“讓參與軟件開發的人員自己來定義和選擇適合他們的流程”。簡單來說就是以人為本,不把人當螺絲釘,發揮人的主觀能動性,當然前提是需要團隊成員有較高的平均素質。
2. 溝通(Communication)
Neal Ford 讓我們回顧或想象一下失敗的軟件開發項目,它們的失敗是由于技術因素還是人的因素呢?《人件》的作者認為都是人的因素。人類的社會性決定了溝通的重要。Neal 舉了幾個有趣的例子,如:監獄里的犯人寧愿和其他人渣待在一起也不愿被關禁閉。很多國家禁止駕駛員駕駛時打
移動電話,那為什么和乘客聊天就沒有問題呢?原因是直接對話是最為有效和便捷的溝通方式,信息的傳遞在對話過程中非常順暢和完整。雖然現在的移動通訊已經非常先進,信號質量也很高,但是我們的通話過程仍然是有損的,我們的大腦這個時候就需要努力地試圖將通話信息拼湊得更完整以便能夠理解對方的意思,因此才會分散駕駛的注意力。隨后,Martin Fowler 舉了另一個例子,拿他做水果蛋糕的方法和他在酒店的浴室中沖涼的方法來進行比較。因為做水果蛋糕的整個流程和配料都是非常固定的,所以他只需要按步照搬地烹飪即可做出味道非常一致(地好或者差)的水果蛋糕。而在酒店中沖涼就有些不同,因為每一個酒店浴室的開關設計幾乎都是不一樣的,所以他需要不斷地調整開關來獲得一個理想的水溫,也就是需要不斷地重復“調整開關”(輸入),“用手試溫”(輸出)這個過程。相對于做水果蛋糕,在酒店浴室沖涼更好地反應了軟件開發的特征,這就是在軟件開發領域中,如果我們善于根據用戶反饋的信息來做出新的判斷和調整,就有可能提高產品的質量和用戶的滿意度。
溝通的確是一個非常重要的環節,它是敏捷方法的核心。在敏捷方法中,
單元測試是程序員和代碼組件的溝通,
功能測試是程序員以及QA和系統的溝通,故事墻(Story Wall)和回顧(Retrospective)是團隊和成員之間的溝通,功能演示(Showcase 或者 Demo)是團隊通過產品和最終用戶的溝通,持續集成(Continuous Integration)是產品和企業計算環境的溝通。溝通好了,什么事情都可以妥善解決,溝通得不好,好事也會變壞事。和廣大技術愛好者交流溝通也是酷殼存在的目的和意義。
整個演講時長一個小時,本文只是節選了我認為比較有意思的觀點加上本人的理解寫成,如有錯誤之處歡迎指正,不同看法歡迎交流。
做了幾年的
功能測試,也經手了好幾個Web應用的項目,在做項目當中積累了一些經驗。在這里我對通用一些功能模塊的測試點做個記錄,一來梳理一下
測試用例設計的思路,以便加快相似項目的測試用例的設計,二來有利于設計出更加全面完善的測試用例。以后隨著自己的
測試技術的進步,也可以在這里對測試用例進行補充,查漏補缺。
1. 注冊用戶信息
(1)將某個必填項留空,檢查系統是否對必填項為空的情況做了必要的處理;
(2)在某個必填項中僅輸入空格,檢查系統是否能夠正確處理;
(3)按[Tab]鍵,光標是否能夠按照從左到右,由上到下的順序在輸入域間切換;
(4)單擊[Enter],檢查是否相當于單擊了[注冊]按鈕,將注冊信息提交到系統中;
(5)檢查系統是否對用戶名中的空格做處理;
(6)輸入已經存在的用戶名,檢查系統是否對“用戶名”做重名校驗;
(7)用戶名大小寫校驗:若有已注冊用戶“abc”,輸入用戶名“ABC”和正確的密碼也可以成功登錄;若以用戶名“Abc”注冊用戶信息,則系統提示用戶名重名,用戶已存在;
(8)輸入字符數等于域允許的最大字符數,檢查系統是否能正確保存該信息;
(9)輸入字符數大于域允許 的最大字符數,檢查系統是否對域輸入長度進行驗證,并對超過的字符做合理的處理;
(10)檢驗系統是否對特殊字符做了處理;
(11)輸入的確認密碼與設置密碼不一致,檢查系統是否做了密碼校驗;
(12)在“密碼”和“確認密碼”輸入域里輸入密碼,均未顯示明文;
(13)過期處理:在注冊頁面填寫所有的注冊信息,之后停留30分鐘,再單擊[注冊]按鈕,系統提示網頁已過期;
(14)頁面切換校驗:在用戶注冊頁面輸入所有所需的用戶信息,單擊瀏覽器工具欄上的[后退]按鈕,然后再單擊[前進]按鈕,系統進入到“用戶注冊”頁面,密碼和確認密碼輸入域應該被清空,其它輸入域的信息仍然被保留。
2. 管理員登錄
(1)回車驗證:填入管理員帳號和密碼,直接按[Enter]鍵,相當于單擊了[登錄]鍵;
(2)登錄次數的驗證:輸入多次錯誤的管理員帳號和密碼,驗證超過系統允許的錯誤次數,則帳戶被鎖定;
(3)權限驗證:管理員帳號正確登錄后,可以訪問所有被授權的頁面;
(4)注入式登錄:利用sql漏洞,使用不存在的用戶登錄。如用戶名輸入為admin'OR'1'='1,密碼輸入為x'OR'1'='1,此時系統應該報告用戶名或密碼不正確;
(5)用已鎖定的用戶登錄,系統應該提示鎖定用戶無法登錄;
(6)Tab驗證:按[Tab]鍵光標應該能夠按照從左到右,由上到下的順序在輸入域間切換。
3. 注冊用戶登錄
(1)回車驗證:同管理員登錄;
(2)輸入登錄密碼中包含空格,檢驗系統是否對密碼中的空格做處理;
(3)檢驗登錄密碼不區分大小寫;
(4)登錄次數的驗證:同管理員登錄;
(5)用新注冊的用戶登錄;
(6)使用字符長度等于臨界值的用戶名和密碼登錄;
(7)使用含有空格的用戶名登錄,檢驗系統截除空格,該用戶名仍可以正常登錄;
(8)注入式登錄:同管理員登錄;
(9)用已鎖定的用戶登錄,系統應該提示鎖定用戶無法登錄;
(10)Tab鍵驗證:同管理員登錄。
4. 修改注冊信息
(1)不修改直接按“保存”,檢查是否保存成功;
(2)將用戶名改為已存在的用戶名,檢查系統是否進行了重名檢驗;
(3)在修改的狀態下,將某個必填項置為空,檢查系統是否對必填項為空的情況做了處理;
(4)在修改的狀態下,將某個必填項中僅輸入空格,檢查系統是否能夠正確處理;
(5)輸入字符數等于域允許的最大字符數,檢查系統是否能正確保存該信息;
(6)輸入字符數大于域允許 的最大字符數,檢查系統是否對域輸入長度進行驗證,并對超過的字符做合理的處理;
(7)按[Tab]鍵,光標是否能夠按照從左到右,由上到下的順序在輸入域間切換;
(8)單擊[Enter],檢查是否相當于單擊了[修改]按鈕,將信息提交到系統中;
(9)檢查系統是否對用戶名中的空格做處理;
(10)輸入特殊字符,系統應該對特殊字符做合理的處理;
(11)輸入的確認密碼與設置密碼不一致,檢查系統是否做了密碼校驗;
(12)在“密碼”和“確認密碼”輸入域里輸入密碼,均未顯示明文;
(13)頁面切換校驗:在修改的狀態下,單擊瀏覽器工具欄上的[后退]按鈕,然后再單擊[前進]按鈕,系統進入到“用戶信息”頁面,密碼和確認密碼輸入域應該被清空,其它輸入域的信息仍然被保留;
(14)過期處理:在注冊頁面填寫所有的注冊信息,之后停留30分鐘,再單擊[注冊]按鈕,系統提示網頁已過期。
5. 一些屬于UI測試的測試點
(1)按鈕狀態是否正確:與正在進行的操作無關的按鈕應該加以屏蔽;
(2)按鈕的擺放位置是否合理:錯誤使用容易引起界面退出或關閉的按鈕不應該放在容易單擊的位置;
(3)重要按鈕的擺放位置是否合適:重要的命令按鈕與使用較頻繁的按鈕要放在界面上醒目的位置;
(4)關閉錯誤提示后的光標定位:關閉用戶輸入錯誤的提示信息后,光標應定位到對應的輸入框中;
(5)非法訪問:未登錄直接訪問(復制需要登錄后才可以訪問的頁面的URL)。
版權聲明:本文出自 jrjiarui 的51Testing軟件測試博客:http://www.51testing.com/?362432
在 AndroidManifest.xml 中加上以下文件 public class UserTestCase extends AndroidTestCase 繼承 AndroidTestCase
<instrumentation android:name="android.test.InstrumentationTestRunner" android:targetPackage="com.example.usersqllite" /> <uses-library android:name="android.test.runner" /> |
文件如下
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.usersqllite" android:versionCode="1" android:versionName="1.0" > <instrumentation android:name="android.test.InstrumentationTestRunner" android:targetPackage="com.example.usersqllite" /> <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="18" /> <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <uses-library android:name="android.test.runner" /> <activity android:name="com.example.usersqllite.MainActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest> |
一個客戶比較關心邏輯錯誤的恢復,我們給他推薦的方案是在容災庫上使用flashback技術,下面是一個簡單的linux的腳本。
#!/bin/bash export LOGIN_USER=test export LOGIN_PWD=test #########################################function############################################### flashscn() { echo -e "enter scn:\c" read SCNNUM STR1="flashback table $OWNER.$TABLE_NAME to scn $SCNNUM;" echo $STR1 T1=`sqlplus -silent $LOGIN_USER/$LOGIN_USER <<EOF set pagesize 0 feedback off verify off heading off echo off alter table $OWNER.$TABLE_NAME enable row movement; $STR1 alter table $OWNER.$TABLE_NAME disable row movement; EOF` if [ -z "$T1" ];then echo "######" echo "flashback table $TABLE_NAME OK!" else echo "######" echo "flashback tabel $TABLE_NAME error:" echo $T1 |awk -F "ORA-" '{print "ORA-" $NF}' fi } flashtime() { echo -e "enter time (example 2014-05-18 20:34:21):\c" read STIME STR2="flashback table $OWNER.$TABLE_NAME to timestamp to_timestamp('$STIME','yyyy-mm-dd hh24:mi:ss');" echo $STR2 T2=`sqlplus -silent $LOGIN_USER/$LOGIN_USER <<EOF set pagesize 0 feedback off verify off heading off echo off alter table $OWNER.$TABLE_NAME enable row movement; $STR2 alter table $OWNER.$TABLE_NAME disable row movement; EOF` if [ -z "$T2" ];then echo "######" echo "flashback table $TABLE_NAME OK!" else echo "######" echo "flashback tabel $TABLE_NAME error:" echo $T2 |awk -F "ORA-" '{print "ORA-" $NF}' fi } ############################################main start############################################## echo -e "enter flashback table owner:\c" read OWNER echo -e "enter flashbackup table name:\c" read TABLE_NAME echo -e "chose flashback type 1)time 2)scn 1\2 :\c" read STYPE case $STYPE in 1) flashtime ;; 2) flashscn ;; *) echo "your enter is error,please enter 1 or 2 !!!" exit ;; esac |
Android中在sqlite插入數據的時候默認一條語句就是一個事務,因此如果存在上萬條數據插入的話,那就需要執行上萬次插入操作,操作速度可想而知。因此在Android中插入數據時,使用批量插入的方式可以大大提高插入速度。
有時需要把一些數據內置到應用中,常用的有以下2種方式:其一直接拷貝制作好的SQLite數據庫文件,其二是使用系統提供的
數據庫,然后把數據批量插入。我更傾向于使用第二種方式:使用系統創建的數據庫,然后批量插入數據。批量插入數據也有很多方法,那么那種方法更快呢,下面通過一個demo比較一下各個方法的插入速度。
這里是把要插入的數據拼接成可執行的sql語句,然后調用db.execSQL(sql)方法執行插入。
public void inertOrUpdateDateBatch(List<String> sqls) { SQLiteDatabase db = getWritableDatabase(); db.beginTransaction(); try { for (String sql : sqls) { db.execSQL(sql); } // 設置事務標志為成功,當結束事務時就會提交事務 db.setTransactionSuccessful(); } catch (Exception e) { e.printStackTrace(); } finally { // 結束事務 db.endTransaction(); db.close(); } } |
2、使用db.insert("table_name", null, contentValues)
這里是把要插入的數據封裝到ContentValues類中,然后調用db.insert()方法執行插入。
db.beginTransaction(); // 手動設置開始事務 for (ContentValues v : list) { db.insert("bus_line_station", null, v); } db.setTransactionSuccessful(); // 設置事務處理成功,不設置會自動回滾不提交 db.endTransaction(); // 處理完成 db.close() |
3、使用InsertHelper類
這個類在API 17中已經被廢棄了
InsertHelper ih = new InsertHelper(db, "bus_line_station"); db.beginTransaction(); final int directColumnIndex = ih.getColumnIndex("direct"); final int lineNameColumnIndex = ih.getColumnIndex("line_name"); final int snoColumnIndex = ih.getColumnIndex("sno"); final int stationNameColumnIndex = ih.getColumnIndex("station_name"); try { for (Station s : busLines) { ih.prepareForInsert(); ih.bind(directColumnIndex, s.direct); ih.bind(lineNameColumnIndex, s.lineName); ih.bind(snoColumnIndex, s.sno); ih.bind(stationNameColumnIndex, s.stationName); ih.execute(); } db.setTransactionSuccessful(); } finally { ih.close(); db.endTransaction(); db.close(); } |
4、使用SQLiteStatement
查看InsertHelper時,官方文檔提示改類已經廢棄,請使用SQLiteStatement
String sql = "insert into bus_line_station(direct,line_name,sno,station_name) values(?,?,?,?)"; SQLiteStatement stat = db.compileStatement(sql); db.beginTransaction(); for (Station line : busLines) { stat.bindLong(1, line.direct); stat.bindString(2, line.lineName); stat.bindLong(3, line.sno); stat.bindString(4, line.stationName); stat.executeInsert(); } db.setTransactionSuccessful(); db.endTransaction(); db.close(); |
下圖是以上4中方法在批量插入1萬條數據消耗的時間
可以發現第三種方法需要的時間最短,鑒于該類已經在API17中廢棄,所以第四種方法應該是最優的方法。
線程池
推薦用ThreadPoolExecutor的工廠構造類Executors來管理線程池,線程復用線程池開銷較每次申請新線程小,具體看代碼以及注釋
public class TestThread { /** * 使用線程池的方式是復用線程的(推薦) * 而不使用線程池的方式是每次都要創建線程 * Executors.newCachedThreadPool(),該方法返回的線程池是沒有線程上限的,可能會導致過多的內存占用 * 建議使用Executors.newFixedThreadPool(n) * * 有興趣還可以看下定時線程池:SecheduledThreadPoolExecutor */ public static void main(String[] args) throws InterruptedException, ExecutionException { int nThreads = 5; /** * Executors是ThreadPoolExecutor的工廠構造方法 */ ExecutorService executor = Executors.newFixedThreadPool(nThreads); //submit有返回值,而execute沒有返回值,有返回值方便Exception的處理 Future res = executor.submit(new ConsumerThread()); //executor.execute(new ConsumerThread()); /** * shutdown調用后,不可以再submit新的task,已經submit的將繼續執行 * shutdownNow試圖停止當前正執行的task,并返回尚未執行的task的list */ executor.shutdown(); //配合shutdown使用,shutdown之后等待所有的已提交線程運行完,或者到超時。繼續執行后續代碼 executor.awaitTermination(1, TimeUnit.DAYS); //打印執行結果,出錯的話會拋出異常,如果是調用execute執行線程那異常會直接拋出,不好控制,submit提交線程,調用res.get()時才會拋出異常,方便控制異常 System.out.println("future result:"+res.get()); } static class ConsumerThread implements Runnable{ @Override public void run() { for(int i=0;i<5;i++) { System.out.println(i); } } } } |
輸出:
0
1
2
3
4
future result:null
線程同步
synchronized(this)和synchronized(MyClass.class)區別:前者與加synchronized的成員方法互斥,后者和加synchronized的靜態方法互斥
synchronized的一個應用場景是單例模式的,雙重檢查鎖
public class Singleton { private volatile static Singleton singleton; private Singleton (){} public static Singleton getSingleton() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; } } |
注意:不過雙重檢查鎖返回的實例可能是沒有構造完全的對象,高并發的時候直接使用有問題,不知道在新版的java里是否解決了
所以有了內部類方式的單例模式,這樣的單例模式有了延遲加載的功能(還有一種枚舉方式的單例模式,用的不多,有興趣的可以上網查)
//(推薦)延遲加載的單例模式 public class Singleton { private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } private Singleton (){} public static final Singleton getInstance() { return SingletonHolder.INSTANCE; } } |
若不要延遲加載,在類加載的時候實例化對象,那直接這么寫,如下:
public class Singleton { private static Singleton instance = new Singleton(); private Singleton (){} public static Singleton getInstance() { return instance; } } |
volatile保證同一變量在多線程中的可見性,所以它更多是用于修飾作為開關狀態的變量
用synchronized修飾變量的get和set方法,不但可以保證和volatile修飾變量一樣的效果(獲取最新值),因為synchronized不僅會把當前線程修改的變量的本地副本同步給主存,還會從主存中讀取數據更新本地副本。而且synchronized還有互斥的效果,可以有效控制并發修改一個值,因為synchronized保證代碼塊的串行執行。如果只要求獲取最新值的特性,用volatile就好,因為volatile比較輕量,性能較好
.
互斥鎖、讀寫鎖
ReentrantLock 和 ReentrantReadWriteLock
JDK5增加了ReentrantLock這個類因為兩點:
1.ReentrantLock提供了tryLock方法,tryLock調用的時候,如果鎖被其他線程(同一個線程兩次調用tryLock也都返回true)持有,那么tryLock會立即返回,返回結果是false。lock()方法會阻塞。
2.構造RenntrantLock對象可以接收一個boolean類型的參數,描述鎖公平與否的函數。公平鎖的好處是等待鎖的線程不會餓死,但是整體效率相對低一些;非公平鎖的好處是整體效率相對高一些。
注意:使用ReentrantLock后,需要顯式地進行unlock,所以建議在finally塊中釋放鎖,如下:
lock.lock(); try { //do something } finally { lock.unlock(); } |
ReentrantReadWriteLock與ReentrantLock的用法類似,差異是前者通過readLock()和writeLock()兩個方法獲得相關的讀鎖和寫鎖操作。
原子數
除了用互斥鎖控制變量的并發修改之外,jdk5中還增加了原子類,通過比較并交換(硬件CAS指令)來避免線程互斥等待的開銷,進而完成超輕量級的并發控制,一般用來高效的獲取遞增計數器。
AtomicInteger counter = new AtomicInteger();
counter.incrementAndGet();
counter.decrementAndGet();
可以簡單的理解為以下代碼,增加之后與原先值比較,如果發現增長不一致則循環這個過程。代碼如下
public class CasCounter { private SimulatedCAS value; public int getValue() { return value.getValue(); } public int increment() { int oldValue = value.getValue(); while (value.compareAndSwap(oldValue, oldValue + 1) != oldValue) oldValue = value.getValue(); return oldValue + 1; } } |
可以看IBM工程師的一篇文章 Java 理論與實踐: 流行的原子
喚醒、通知
wait,notify,notifyAll是java的Object對象上的三個方法,多線程中可以用這些方法完成線程間的狀態通知。
notify是喚醒一個等待線程,notifyAll會喚醒所有等待線程。
CountDownLatch主要提供的機制是當多個(具體數量等于初始化CountDownLatch時的count參數的值)線程都到達了預期狀態或完成預期工作時觸發事件,其他線程可以等待這個事件來觸發后續工作。
舉個例子,大數據分拆給多個線程進行排序,比如主線程
CountDownLatch latch = new CountDownLatch(5); for(int i=0;i<5;i++) { threadPool.execute(new MyRunnable(latch,datas)); } latch.await(); //do something 合并數據 |
MyRunnable的實現代碼如下
public void run() { //do something數據排序 latch.countDown(); //繼續自己線程的工作,與CyclicBarrier最大的不同,稍后馬上講 } |
CyclicBarrier循環屏障,協同多個線程,讓多個線程在這個屏障前等待,直到所有線程都到達了這個屏障時,再一起繼續執行后面的動作。
使用CyclicBarrier可以重寫上面的排序代碼
主線程如下
CyclicBarrier barrier = new CyclicBarrier(5+1); //主線程也要消耗一個await,所以+1 for(int i=0;i<5;i++) { threadPool.execute(new MyRunnable(barrier,datas));//如果線程池線程數過少,就會發生死鎖 } barrier.await(); //合并數據 |
MyRunnable代碼如下
public void run() {
//數據排序
barrier.await();
}
//全部 count+1 await之后(包括主線程),之后的代碼才會一起執行
信號量
Semaphore用于管理信號量,與鎖的最大區別是,可以通過令牌的數量,控制并發數量,當管理的信號量只有1個時,就退化到互斥鎖。
例如我們需要控制遠程方法的并發量,代碼如下
semaphore.acquire(count); try { //調用遠程方法 } finally { semaphore.release(count); } |
線程交換隊列
Exchanger用于在兩個線程之間進行數據交換,線程會阻塞在Exchanger的exchange方法上,直到另外一個線程也到了同一個Exchanger的exchanger方法時,二者進行交換,然后兩個線程繼續執行自身相關代碼。
public class TestExchanger { static Exchanger exchanger = new Exchanger(); public static void main(String[] args) { new Thread() { public void run() { int a = 1; try { a = (int) exchanger.exchange(a); } catch (Exception e) { e.printStackTrace(); } System.out.println("Thread1: "+a); } }.start(); new Thread() { public void run() { int a = 2; try { a = (int) exchanger.exchange(a); } catch (Exception e) { e.printStackTrace(); } System.out.println("Thread2: "+a); } }.start(); } } |
輸出結果:
Thread2: 1
Thread1: 2
并發容器
CopyOnWrite思路是在更改容器時,把容器寫一份進行修改,保證正在讀的線程不受影響,適合應用在讀多寫少的場景,因為寫的時候重建一次容器。
以Concurrent開頭的容器盡量保證讀不加鎖,并且修改時不影響讀,所以會達到比使用讀寫鎖更高的并發性能
基于.NET開發分布式系統,經常用到Remoting技術。在
測試驅動開發流行的今天,如果針對分布式系統中的每個Remoting接口的每個方法都要寫詳細的測試腳本,無疑非常浪費時間。所以,我想寫一個能自動測試remoting接口的小工具InterfaceTester。而且,當分布式系統中的某個remoting接口出現bug時,該小工具可以提交需要模擬的數據,以便在調試remoting服務的環境中,快速定位和解決bug。
InterfaceTester運行起來后的效果如下圖:
1.如何使用
(1)首先,填上要測試的并且是已經發布的Remoting服務的地址信息。
(2)選取要測試的remoting接口所在的程序集,一般是一個dll。選定程序集后,InterfaceTester會自動搜索該程序集中定義的所有接口,并將其綁定到“接口類型”的下拉列表。
(3)從 “接口類型”的下拉列表中選擇要測試的接口。選定接口后,InterfaceTester會自動搜索該接口中定義的所有方法,并將其綁定到“目標方法”的下拉列表。
(4)從 “目標方法”的下拉列表中選擇要測試的方法,InterfaceTester會根據該方法所要求的參數,自動生成參數錄入界面。
(5)在參數錄入界面上,輸入用于測試的參數的值,然后,點擊“調用”按鈕, InterfaceTester便會調用上述指定地址的remtoing服務的指定接口的指定方法,如果調用的方法有返回值,則會在“調用返回”的panel上顯示該值。如果返回的不是一個簡單類型,而是一個對象,則“調用返回”的panel上將會以xml的形式顯示這個對象的各個屬性值。
2.實現原理
就這個小工具的實現而言,主要用到的技術就是反射(reflection)。另外,需要注意的就是,根據參數的類型,生成錄入界面。具體細節大家可以參見源碼。目前,InterfaceTester支持的被測試方法的參數類型是有限制的:
(1)支持簡單的數據類型,像string、int、bool等。
(2)支持List<>、I List<>、IDictionary<,>、Dictionary<,>等集合類型。
(3)支持簡單的數據結構的class(如像Point、自定義的Entity等)。
3.源碼解決方案
下載源碼并用VS打開后,解決方案下有三個項目:InterfaceTester、DemoInterface、DemoService。
(1)InterfaceTester項目是我們本文的主角:用于remoting接口測試的小工具。
(2)DemoInterface和 DemoService則是為了試試小工具而構建的一個小demo。 DemoInterface定義了發布的remoting服務的接口, DemoService則是發布的remoting服務。
在試用時,先啟動 DemoService項目,再啟動InterfaceTester,就可以試試我們的小工具功能了。
MonkeyRunner是
Android系統自帶
測試工具。使用之前要安裝配置好android開發環境。
1、用eclipse打開android的模擬器或者在cmd用android命令打開模擬器。
例如:C:\Program Files\adt-bundle-windows-x86-20131030\sdk\tools>emulator -avd AVD3.2
備注:定位到android sdk路徑下的tools目錄,運行上面的命令。“AVD3.2”是模擬器名字。
2、模擬器啟動之后(如果是cmd窗口則不要關閉當前窗口),重新打開cmd窗口,還是定位到tools目錄,輸入命令“monkeyrunner”回城,將進入shell命令交換模式。 接下來導入monkeyrunner所要使用的模塊,使用"from...import..."
直接在shell命令中輸入:from com.android.monkeyrunner import MonkeyRunner,MonkeyDevice回車
現在可以與模擬器連接了,命令:device=MonkeyRunner.waitForConnection()
連接成功會返回true。如果未返回true,一般都是語法錯誤或者你傳入的相對路徑有問題。
連接成功之后安裝包HelloTest.apk
device.installPackage("../HelloTest.apk")
安裝之后就可以啟動任意的activity了,只要傳入package和activity名稱即可。
命令:device.startActivity(component="com.example.hellotest/com.example.hellotest.MainActivity")
此時模擬器會自動打開HelloTest這個應用的主頁。
正是因為業務需求推動應用軟件的創建,所以應用程序的設計必須萬無一失且通過質量保證認證。質量保證的一個重要方面是:設計出能確保所有設計場景已在測試中被抓取的測試用例。測試用例是一組條件或變量,在其中,測試員將決定被測系統是否滿足設計的要求和功能。開發測試用例的過程也有助于發現應用程序的要求或設計中的問題。一個測試用例與一些元素指示(如測試集ID ,測試用例ID,測試總結和測試描述)有關。 測試用例設計有兩個主要任務: ▪測試設計是所有邏輯測試用例的注意要求的草案。如果有效地設計,這就是一個能在測試執行時節省相當大精力及成本的關鍵部分。 ▪規格包含被轉化為將要進行的物理測試指令的完整描述的草稿。 我們使用一個基于元數據的方法來設計測試用例。這種方法對于將要跨多個應用程序進行統一測試時以可重復的方式設計測試用例來說是很有用的。示例場景是涉及數據遷移或企業數據屏蔽的項目。基于元數據的測試用例設計和通用測試用例設計的主要區別是:前者沒有在從需求去推導測試用例上花時間,因為通過元數據直接使用數據或前者沒有在從需求去推導測試用例上花時間,因為通過元數據直接使用數據或前期數據的數據或屬性是有可能的。
.jpg)
圖1.使用測試用例生成
工具設計測試用例
用基于元數據的方法,我們可以著手處理庫存要求;反過來,著手處理庫存要求也可以獲取元數據存儲庫中的數據屬性。基于庫存,就能準備高層次的場景,然后支持測試用例的開發。為了加快測試用例的準備過程,我們設計了可以用任意基本腳本語言(如VB腳本,UNIX或Perl)實現的方法,以可重復的方式高效地生成測試用例。 測試用例生成工具( TCGT )是一個基于在矩陣上的信息的基礎上生成測試用例的高度自動化工具。它生成的測試用例可以滿足驗收,確認,應用核實的目的。基于元數據的測試用例設計可以用于以下兩種場景,在這兩種場景中要求了基于工廠的測試用例設計和生成。 場景1:數據遷移 數據遷移項目需要大量的數據庫測試,以確保沒有數據泄漏,且遷移后數據的完整性和質量得以保留。遷移過程是由一組作為映射規則和轉換功能的規格決定的。例如,如果我們正在測試一個系統,把數據從SQL Server 2005遷移到SQL Server 2008中,我們就需要執行以下操作: ▪數據遷移的需求分析 ▪規范化要求 ▪元數據驗證 ▪數據驗證 場景2:數據屏蔽 基于元數據的測試用例的設計也可以在企業數據屏蔽中實現。數據屏蔽測試需要比較數據正確性和完整性的源頭數據和目標數據。沒有屏蔽或屏蔽后復制的表格應該測試其數據變化,屏蔽算法和業務規則。在大多數情況下,數據屏蔽場景需要可重復準備和執行的測試用例,這樣測試用例設計中就可以使用元數據方法了。