一、性能測(cè)試的概念
性能測(cè)試是通過自動(dòng)化的測(cè)試工具模擬多種正常峰值及異常負(fù)載條件來對(duì)系統(tǒng)的各項(xiàng)性能指標(biāo)進(jìn)行測(cè)試。負(fù)載測(cè)試和壓力測(cè)試都屬于性能測(cè)試,兩者可以結(jié)合進(jìn)行。
通過負(fù)載測(cè)試,確定在各種工作負(fù)載下系統(tǒng)的性能,目標(biāo)是當(dāng)負(fù)載逐漸增加時(shí),測(cè)試系統(tǒng)各項(xiàng)性能指標(biāo)的變化情況。壓力測(cè)試時(shí)通過確定一個(gè)系統(tǒng)的瓶頸或者不能接受的
性能點(diǎn),來獲取系統(tǒng)能提供的大服務(wù)級(jí)別的測(cè)試。性能測(cè)試主要包括負(fù)載測(cè)試、強(qiáng)度測(cè)試、容量測(cè)試。
二、性能測(cè)試的指標(biāo)
web服務(wù)器:
Avg Rps: 平均每秒的響應(yīng)次數(shù) = 總請(qǐng)求數(shù) /秒數(shù);
Avg time to last byte per terstion(mstes): 平均每秒業(yè)務(wù)腳本的迭代次數(shù);
Successful Rounds: 成功的請(qǐng)求;
Failed Rounds: 失敗的請(qǐng)求;
Successful Hits: 成功的點(diǎn)擊次數(shù);
Failed Hits: 失敗的點(diǎn)擊次數(shù);
Hits Per Second: 每秒點(diǎn)擊次數(shù);
Successful Hits Per Second:每秒成功的點(diǎn)擊次數(shù);
Failed Hits Per Second: 每秒失敗的點(diǎn)擊次數(shù);
Attempted Connections: 嘗試連接數(shù);
Throughput: 吞吐率;
數(shù)據(jù)庫服務(wù)器:
User Connections: 用戶連接數(shù),也是數(shù)據(jù)庫的連接數(shù)量;
Number of Deadlocks: 數(shù)據(jù)庫死鎖;
Butter Cache Hit: 數(shù)據(jù)庫Cache 的命中情況;
三、性能測(cè)試的流程
1.明確性能測(cè)試需求;
2.制定性能測(cè)試方案;
2.1.測(cè)試范圍
2.2.入口標(biāo)準(zhǔn)
2.3.出口標(biāo)準(zhǔn)
2.4.測(cè)試策略(測(cè)試環(huán)境指標(biāo)、存量數(shù)據(jù)、業(yè)務(wù)場(chǎng)景、測(cè)試通過標(biāo)準(zhǔn)等)
2.5.測(cè)試風(fēng)險(xiǎn)
2.6.測(cè)試資源
3.設(shè)計(jì)性能測(cè)試用例;
4.執(zhí)行性能測(cè)試用例;
5.分析性能測(cè)試結(jié)果;
6.生成性能測(cè)試報(bào)告;
四、性能測(cè)試的工具--JMeter
為什么是JMeter而不是LoadRunner呢 1.更少的投入,針對(duì)有限的測(cè)試成本; 2.開源工具的可定制性無可比擬; 3.通過社區(qū)得到大程度的支持。
JMeter是Apache組織開發(fā)的基于Java的壓力測(cè)試工具。初被設(shè)計(jì)用于web應(yīng)用的測(cè)試,后來擴(kuò)展到其他測(cè)試領(lǐng)域?捎糜跍y(cè)試靜態(tài)和動(dòng)態(tài)資源,如文件、Java服務(wù)
程序、Java對(duì)象、數(shù)據(jù)庫等。JMeter能夠?qū)?yīng)用程序做功能/回歸測(cè)試,通過創(chuàng)建帶有斷言的腳本來驗(yàn)證被測(cè)程序返回了期望的結(jié)果。而且為了保證大限度的靈活性,
JMeter允許使用正則表達(dá)式創(chuàng)建斷言。
五、JMeter的特性
1.支持對(duì)多種服務(wù)類型進(jìn)行測(cè)試;
2.支持通過錄制/回訪方式獲取測(cè)試腳本;
3.具備高可移植性,是純Java 程序;
4.采用多線程框架,允許通過多個(gè)線程并發(fā)取樣及通過獨(dú)立的線程組對(duì)不同的功能同時(shí)取樣;
5.精心設(shè)計(jì)的GUI支持高速用戶操作和精確計(jì)時(shí);
6.支持緩存和離線的方式分析/回放測(cè)試結(jié)果;
7.高擴(kuò)展性;
六、JMeter常用測(cè)試元件
1.線程組
用來管理執(zhí)行性能測(cè)試所需的JMeter線程。
a.可以設(shè)置線程數(shù)量
b.設(shè)置線程啟動(dòng)周期
c.設(shè)置執(zhí)行測(cè)試腳本的循環(huán)次數(shù)
2.控制器
JMeter有兩種類型的控制器:采樣器和邏輯控制器。
采樣器被用來向服務(wù)器發(fā)送請(qǐng)求。JMeter采樣器包含:FTP Request、HTTP Request、JDBC Request等。
邏輯控制器用來控制JMeter的測(cè)試邏輯,特別是何時(shí)發(fā)送請(qǐng)求。
3.監(jiān)聽器
監(jiān)聽器提供了對(duì)JMeter在測(cè)試期間收集到的信息的訪問方法。
4.定時(shí)器
JMeter線程在發(fā)送請(qǐng)求之間沒有間歇,通過添加定時(shí)器,設(shè)定請(qǐng)求之間應(yīng)該間隔的時(shí)間。
5.斷言
可以使用斷言來檢查從服務(wù)器獲得的響應(yīng)內(nèi)容。
6.配置元件
配置元件與采樣器緊密關(guān)聯(lián)。雖然配置元件并不發(fā)送請(qǐng)求,但可添加或修改請(qǐng)求。
7.前置處理器
會(huì)在采樣器發(fā)出請(qǐng)求之前做一些操作。
8.后置處理器
會(huì)在采樣器發(fā)出請(qǐng)求之后做一些操作。
JMeter執(zhí)行順序:配置元件=》前置處理器=》定時(shí)器=》采樣器=》后置處理器=》斷言=》監(jiān)聽器
七、輔助測(cè)試工具開發(fā)
下面的代碼(工具:sqlexec)是一個(gè)用來向數(shù)據(jù)庫(目前支持Oracle、MySQL)插入測(cè)試數(shù)據(jù)的工具。支持多線程,可插入千萬級(jí)別測(cè)試數(shù)據(jù)。在后續(xù)壓測(cè)中會(huì)用到該
工具,工具開發(fā)盡量簡單,一個(gè)工具只完成一個(gè)任務(wù),同時(shí)不要重復(fù)制造輪子。
[java] view plain copy
package d706;
/*
* sql處理
*/
public class Test_DB_Insert extends Thread{
public static String SQLTEXT = null; // 待處理的sql語句
private InputStream ins = null; // 用于讀取配置文件
private Properties property = new Properties(); // 讀取數(shù)據(jù)庫配置文件
private String databaseType = null; // 數(shù)據(jù)庫連接類型
private String driver = null; // 數(shù)據(jù)庫驅(qū)動(dòng)
private String url = null; // 數(shù)據(jù)庫連接
private String uName = null; // 數(shù)據(jù)庫登錄用戶名
private String pwd = null; // 數(shù)據(jù)庫登錄用戶密碼
private int numOfTestRecords; // 插入數(shù)據(jù)條數(shù)
private Connection con = null; // 連接數(shù)據(jù)庫
private PreparedStatement statement = null; // 獲取數(shù)據(jù)庫操作對(duì)象
public Test_DB_Insert(String sql){
SQLTEXT = sql; // sql語句以參數(shù)的形式,在構(gòu)造實(shí)例的時(shí)候傳入
}
private void init(){ // 初始化配置文件
try{
ins = new FileInputStream("./d706/dbconf.properties");
}catch(FileNotFoundException ffe){
ffe.printStackTrace();
}
try{
property.load(ins); //
}catch(IOException ie){
ie.printStackTrace();
}
databaseType = property.getProperty("databasetype"); // 獲取配置文件中設(shè)置的連接數(shù)據(jù)庫類型
if(databaseType.toUpperCase().equals("MYSQL")){ // 判斷連接數(shù)據(jù)庫類型
driver = property.getProperty("driver_mysql");
url = property.getProperty("url_mysql");
uName = property.getProperty("db_userName_mysql"); // 連接數(shù)據(jù)庫的用戶信息;
pwd = property.getProperty("db_pwd_mysql");
}else if(databaseType.toLowerCase().equals("oracle")){ //
driver = property.getProperty("driver_oracle");
url = property.getProperty("url_oracle");
uName = property.getProperty("db_userName_oracle");
pwd = property.getProperty("db_pwd_oracle");
}
}
private synchronized void Insert_DB(){
try {
try {
Class.forName( driver ); // 注冊(cè)驅(qū)動(dòng);
}catch(ClassNotFoundException cf){
cf.printStackTrace();
}
con = DriverManager.getConnection(url,uName, pwd); // 獲取數(shù)據(jù)庫連接
con.setAutoCommit(false); // 關(guān)閉事務(wù)自動(dòng)提交
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss:SS"); // 記錄執(zhí)行時(shí)間
TimeZone t = sdf.getTimeZone();
t.setRawOffset(0);
sdf.setTimeZone(t);
Long startTime = System.currentTimeMillis();
System.out.println("插入數(shù)據(jù)操作開始...");
statement = con.prepareStatement(SQLTEXT); //創(chuàng)建數(shù)據(jù)庫操作對(duì)象
/*
* "INSERT INTO TEST_DB(name,sex,nickname,test1,test2,test3,test4," +
"test5,test6,test7,test8,test9,test10,test11,test12,test13,test14," +
"test15,test16,test17,test18,test19,test20,test21,test22,test23," +
"test24,test25,test26,test27,test28,test29,test30,test31,test32," +
"test33,test34,test35,test36,test37,test38,test39,test40,test41," +
"test42) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?," +
"?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"
*/
numOfTestRecords = 1000; //插入的測(cè)試數(shù)據(jù)量;
for(int i = 0; i<numOfTestRecords; i++) { //循環(huán)
statement.setString(i + 1, "DBTest-" + i);
//statement.setString(2, "" + i%2); //0表示男 1表示女
statement.addBatch(); // 把一個(gè)SQL命令加入命令列表
//statement.executeUpdate(); //執(zhí)行SQL;
}
statement.executeBatch(); //執(zhí)行批量更新
con.commit();//語句執(zhí)行完畢,提交事務(wù)
//int[] ref = statement.executeBatch();
//if(ref[numOfTestRecords-1] == 0){System.out.println("插入數(shù)據(jù)操作完成");} //
System.out.println("插入數(shù)據(jù)操作完成");
Long endTime = System.currentTimeMillis();
System.out.println("插入"+numOfTestRecords+"條數(shù)據(jù),"+"用時(shí)(時(shí):分:秒:毫秒)" +
sdf.format(new Date(endTime - startTime))); //
}catch(Exception e) {
System.out.println("異常: " + e.toString());
e.printStackTrace();
}finally{
if(statement != null){ // 關(guān)閉數(shù)據(jù)庫操作對(duì)象
try{
statement.close();
}catch(SQLException se){
se.printStackTrace();
}
}
if(con != null){ // 關(guān)閉數(shù)據(jù)庫連接
try{
if(con.isClosed()){con.close();}
}catch(SQLException se){
se.printStackTrace();
}
}
}
}
@Override
public void run() { // 類外調(diào)用
Test_DB_Insert ti = new Test_DB_Insert(SQLTEXT); // 構(gòu)造實(shí)例
ti.init(); // 初始化
ti.Insert_DB(); // 執(zhí)行插入數(shù)據(jù)
}
// public static void main(String[] args){
//
// Test_DB_Insert ti = new Test_DB_Insert(SQLTEXT);
// ti.init(); //初始化
// ti.Insert_DB(); //執(zhí)行插入數(shù)據(jù)
// }
}
// 針對(duì)增刪查改,可放到一個(gè)SQL處理類(Test_DB_crud)中,判斷傳入的SQL字符串,然后交給對(duì)應(yīng)的方法去執(zhí)行并在控制臺(tái)輸出結(jié)果。 在Test_DB_Control類