一、性能測试的概念
性能測试是通过自己主动化的測试工具模拟多种正常峰值及异常负载条件来对系统的各项性能指标进行測试。负载測试和压力測试都属于性能測试,两者能够结合进行。
通过负载測试,确定在各种工作负载下系统的性能,目标是当负载逐渐添加时,測试系统各项性能指标的变化情况。压力測试时通过确定一个系统的瓶颈或者不能接受的
性能点,来获取系统能提供的最大服务级别的測试。
性能測试主要包括负载測试、强度測试、容量測试。
二、性能測试的指标
webserver:
Avg Rps: 平均每秒的响应次数 = 总请求数 /秒数;
Avg time to last byte per terstion(mstes): 平均每秒业务脚本的迭代次数;
Successful Rounds: 成功的请求;
Failed Rounds: 失败的请求;
Successful Hits: 成功的点击次数;
Failed Hits: 失败的点击次数;
Hits Per Second: 每秒点击次数;
Successful Hits Per Second:每秒成功的点击次数;
Failed Hits Per Second: 每秒失败的点击次数;
Attempted Connections: 尝试连接数;
Throughput: 吞吐率;
数据库server:
User Connections: 用户连接数,也就是数据库的连接数量;
Number of Deadlocks: 数据库死锁;
Butter Cache Hit: 数据库Cache 的命中情况;
三、性能測试的流程
1.明白性能測试需求;
2.制定性能測试方案;
2.1.測试范围
2.2.入口标准
2.3.出口标准
2.4.測试策略(測试环境指标、存量数据、业务场景、測试通过标准等)
2.5.測试风险
2.6.測试资源
3.设计性能測试用例;
4.运行性能測试用例;
5.分析性能測试结果;
6.生成性能測试报告;
四、性能測试的工具--JMeter
为什么是JMeter而不是LoadRunner呢 1.更少的投入,针对有限的測试成本; 2.开源工具的可定制性无可比拟; 3.通过社区得到最大程度的支持。
JMeter是Apache组织开发的基于Java的压力測试工具。最初被设计用于web应用的測试,后来扩展到其它測试领域。
可用于測试静态和动态资源,如文件、Java服务
程序、Java对象、数据库等。JMeter能够相应用程序做功能/回归測试,通过创建带有断言的脚本来验证被測程序返回了期望的结果。并且为了保证最大限度的灵活性。
JMeter同意使用正則表達式创建断言。
五、JMeter的特性
1.支持对多种服务类型进行測试;
2.支持通过录制/回訪方式获取測试脚本;
3.具备高可移植性,是纯Java 程序;
4.採用多线程框架。同意通过多个线程并发取样及通过独立的线程组对不同的功能同一时候取样;
5.精心设计的GUI支持快速用户操作和精确计时;
6.支持缓存和离线的方式分析/回放測试结果;
7.高扩展性;
六、JMeter经常使用測试元件
1.线程组
用来管理运行性能測试所需的JMeter线程。
a.能够设置线程数量
b.设置线程启动周期
c.设置运行測试脚本的循环次数
2.控制器
JMeter有两种类型的控制器:採样器和逻辑控制器。
採样器被用来向server发送请求。
JMeter採样器包括:FTP Request、HTTP Request、JDBC Request等。
逻辑控制器用来控制JMeter的測试逻辑,特别是何时发送请求。
3.监听器
监听器提供了对JMeter在測试期间收集到的信息的訪问方法。
4.定时器
JMeter线程在发送请求之间没有间歇,通过加入定时器,设定请求之间应该间隔的时间。
5.断言
能够使用断言来检查从server获得的响应内容。
6.配置元件
配置元件与採样器紧密关联。尽管配置元件并不发送请求,但可加入或改动请求。
7.前置处理器
会在採样器发出请求之前做一些操作。
8.后置处理器
会在採样器发出请求之后做一些操作。
JMeter运行顺序:配置元件=》前置处理器=》定时器=》採样器=》后置处理器=》断言=》监听器
七、辅助測试工具开发
以下的代码(工具:sqlexec)是一个用来向数据库(眼下支持Oracle、Mysql)插入測试数据的工具。支持多线程,可插入千万级别測试数据。
在兴许压測中会用到该
工具,工具开发尽量简单,一个工具仅仅完毕一个任务。同一时候不要反复制造轮子。
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(); // 读取数据库配置文件 private String databaseType = null; // 数据库连接类型 private String driver = null; // 数据库驱动 private String url = null; // 数据库连接 private String uName = null; // 数据库登录username private String pwd = null; // 数据库登录用户password private int numOfTestRecords; // 插入数据条数 private Connection con = null; // 连接数据库 private PreparedStatement statement = null; // 获取数据库操作对象 public Test_DB_Insert(String sql){ SQLTEXT = sql; // sql语句以參数的形式,在构造实例的时候传入 } private void init(){ // 初始化配置文件 try{ ins = new FileInputStream("./d706/dbconf.properties"); }catch(FileNotFoundException ffe){ ffe.printStackTrace(); } try{ property.load(ins); // }catch(IOException ie){ ie.printStackTrace(); }finally{ try{ if(ins != null) ins.close(); }catch(IOException ie){ ie.printStackTrace(); } } databaseType = property.getProperty("databasetype"); // 获取配置文件里设置的连接数据库类型 if("MYSQL".equals( databaseType.toUpperCase() )){ // 推断连接数据库类型 driver = property.getProperty("driver_mysql"); url = property.getProperty("url_mysql"); uName = property.getProperty("db_userName_mysql"); // 连接数据库的用户信息; pwd = property.getProperty("db_pwd_mysql"); }else if("ORACLE".equals( databaseType.toUpperCase() )){ 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 ); // 注冊驱动; }catch(ClassNotFoundException cf){ cf.printStackTrace(); } con = DriverManager.getConnection(url,uName, pwd); // 获取数据库连接 con.setAutoCommit(false); // 关闭事务自己主动提交 SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss:SS"); // 记录运行时间 TimeZone t = sdf.getTimeZone(); t.setRawOffset(0); sdf.setTimeZone(t); Long startTime = System.currentTimeMillis(); System.out.println("插入数据操作開始..."); statement = con.prepareStatement(SQLTEXT); //创建数据库操作对象 /* * "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; //插入的測试数据量; for(int i = 0; i<numOfTestRecords; i++) { //循环 statement.setString(i + 1, "DBTest-" + i); //statement.setString(2, "" + i%2); //0表示男 1表示女 statement.addBatch(); // 把一个SQL命令加入命令列表 //statement.executeUpdate(); //运行SQL; } statement.executeBatch(); //运行批量更新 con.commit();//语句运行完毕,提交事务 //int[] ref = statement.executeBatch(); //if(ref[numOfTestRecords-1] == 0){System.out.println("插入数据操作完毕");} // System.out.println("插入数据操作完毕"); Long endTime = System.currentTimeMillis(); System.out.println("插入"+numOfTestRecords+"条数据,"+"用时(时:分:秒:毫秒)" + sdf.format(new Date(endTime - startTime))); // }catch(Exception e) { System.out.println("异常: " + e.toString()); e.printStackTrace(); }finally{ if(statement != null){ // 关闭数据库操作对象 try{ statement.close(); }catch(SQLException se){ se.printStackTrace(); } } if(con != null){ // 关闭数据库连接 try{ if(con.isClosed()){con.close();} }catch(SQLException se){ se.printStackTrace(); } } } } @Override public void run() { // 类外调用 Test_DB_Insert ti = new Test_DB_Insert(SQLTEXT); // 构造实例 ti.init(); // 初始化 ti.Insert_DB(); // 运行插入数据 } // public static void main(String[] args){ // // Test_DB_Insert ti = new Test_DB_Insert(SQLTEXT); // ti.init(); //初始化 // ti.Insert_DB(); //运行插入数据 // } }
// 针对增删查改,可放到一个SQL处理类(Test_DB_crud)中,推断传入的SQL字符串,然后交给相应的方法去运行并在控制台输出结果。
在Test_DB_Control类
中仅仅是new一个Test_DB_crud类,形成了Test_DB_crud对Test_DB_Control的依赖关系。
package d706; /* *程序界面 *button事件 */ public class Test_DB_gui extends JFrame implements ActionListener{ private static final long serialVersionUID = 1L; public static String SQLTEXT = null; // 界面输入的sql文本 private JScrollPane js = null; private JPanel jp1 = null; private JTextArea ta = null; private JButton jb = new JButton(); public Test_DB_gui(){ this.setTitle("sqlExecV1.0"); ta = new JTextArea(); ta.setText(""); js = new JScrollPane(ta); jp1 = new JPanel(); jp1.setLayout(new BorderLayout()); jp1.add(js,BorderLayout.CENTER); jb = new JButton(); jb.setText("运行"); jb.addActionListener(this); // 加入监听 jp1.add(jb,BorderLayout.SOUTH); this.getContentPane().add(jp1); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this.setBounds(400,200,700,500); this.setVisible(true); jb.addActionListener(new ActionListener(){ // button事件 public void actionPerformed(ActionEvent e) { // 匿名内部类的形式实现button事件 SQLTEXT = ta.getText(); // 将要运行的SQL设置成静态的 Multi_process mp = new Multi_process(SQLTEXT); // 在构造实例时,传入sql mp.run(); // 运行程序 //System.out.println( mp.SQLTEXT +"***"); // 控制台输出 } }); } public void actionPerformed(ActionEvent e) { // 实现 ActionListener 接口,须要实现的方法 } public void run() { // 运行方法 Test_DB_gui np = new Test_DB_gui(); System.out.println( np.getTitle() ); } }
public class Multi_process { /* *多线程 */ public static String SQLTEXT = null; // 待处理的sql语句 public Multi_process(String sql){ SQLTEXT = sql; // sql语句以參数的形式,在构造实例的时候传入 } public void run(){ Test_DB_Insert td1 = new Test_DB_Insert(SQLTEXT); // 创建实例 Test_DB_Insert td2 = new Test_DB_Insert(SQLTEXT); Test_DB_Insert td3 = new Test_DB_Insert(SQLTEXT); Test_DB_Insert td4 = new Test_DB_Insert(SQLTEXT); Thread t1 = td1; // 创建线程 Thread t2 = td2; Thread t3 = td3; Thread t4 = td4; t1.start(); // 启动线程 t2.start(); t3.start(); t4.start(); } }
public class Test_Exec { /* * 程序入口 */ public static void main(String[] args){ Test_DB_gui np = new Test_DB_gui(); np.run(); } }
在Linux下运行,需打成jar包。通过shell脚本运行。以下是sqlexec的startup.sh运行脚本.
#!/bin/sh # #Author: bruce #Version: sqlExecv1.0 #Date:2013-11-20 # read -p "please input jar file path:" jarpath java -jar "$jarpath" echo "running sqlExec."
注:
今天突然想到測试数据的一个问题。即造出来的数据应该更接近真实。而不是都一样。所以想利用配置文件的方式,在配置文件里写好SQL,程序读取运行配置文件
并动态生成SQL。完毕插入測试数据。
这样就攻克了測试数据不够真实。可能影响測试结果的问题。---------2014年11月27日