qileilove

blog已经转移至github,大家请访问 http://qaseven.github.io/

如何测试Nginx的高性能

简介
  Nginx ("engine x") 是一个高性能的HTTP和反向代理服务器,也是一个IMAP/POP3/SMTP代理服务器;
  作为一款轻量级的Web服务器,具有占有内存少,并发能力强等优势,是高连接并发场景下Apache的不错的替代品;
  本篇主要介绍Nginx作为Web服务器时,相对于Apache的性能优势;
  下一篇将会介绍Nginx作为方向代理服务器的实现;
  重要特点
  非阻塞:数据复制时,磁盘I/O的第一阶段是非阻塞的;
  事件驱动:通信机制采用epoll模型,支持更大的并发连接;
  master/worker结构:一个master进程,生成一个或多个worker进程;
  基础架构
  Nginx如何实现高并发:
  I/O模型采用异步非阻塞的事件驱动机制,由进程循环处理多个准备好的事件,如epoll机制;
  Nginx与Apache对高并发处理上的区别:
  对于Apache,每个请求都会独占一个工作线程,当并发量增大时,也会产生大量的工作线程,导致内存占用急剧上升,同时线程的上下文切换也会导致CPU开销增大,导致在高并发场景下性能下降严重;
  对于Nginx,一个worker进程只有一个主线程,通过事件驱动机制,实现循环处理多个准备好的事件,从而实现轻量级和高并发;
  部署配置
  安装
yum -y groupinstall “Development tools”
yum -y groupinstall “Server Platform Development”
yum install gcc openssl-devel pcre-devel zlib-devel
groupadd -r nginx
useradd -r -g nginx -s /sbin/nologin -M nginx
tar xf nginx-1.4.7.tar.gz
cd nginx-1.4.7
mkdir -pv /var/tmp/nginx
./configure \
--prefix=/usr \
--sbin-path=/usr/sbin/nginx \
--conf-path=/etc/nginx/nginx.conf \
--error-log-path=/var/log/nginx/error.log \
--http-log-path=/var/log/nginx/access.log \
--pid-path=/var/run/nginx/nginx.pid  \
--lock-path=/var/lock/nginx.lock \
--user=nginx \
--group=nginx \
--with-http_ssl_module \
--with-http_flv_module \
--with-http_stub_status_module \
--with-http_gzip_static_module \
--http-client-body-temp-path=/var/tmp/nginx/client/ \
--http-proxy-temp-path=/var/tmp/nginx/proxy/ \
--http-fastcgi-temp-path=/var/tmp/nginx/fcgi/ \
--http-uwsgi-temp-path=/var/tmp/nginx/uwsgi \
--http-scgi-temp-path=/var/tmp/nginx/scgi \
--with-pcre
make && make install
配置:
vi /etc/init.d/nginx # 配置服务脚本
#!/bin/sh
#
# nginx - this script starts and stops the nginx daemon
#
# chkconfig:   - 85 15
# description:  Nginx is an HTTP(S) server, HTTP(S) reverse \
#               proxy and IMAP/POP3 proxy server
# processname: nginx
# config:      /etc/nginx/nginx.conf
# config:      /etc/sysconfig/nginx
# pidfile:     /var/run/nginx.pid
# Source function library.
. /etc/rc.d/init.d/functions
# Source networking configuration.
. /etc/sysconfig/network
# Check that networking is up.
[ "$NETWORKING" = "no" ] && exit 0
nginx="/usr/sbin/nginx"
prog=$(basename $nginx)
NGINX_CONF_FILE="/etc/nginx/nginx.conf"
[ -f /etc/sysconfig/nginx ] && . /etc/sysconfig/nginx
lockfile=/var/lock/subsys/nginx
make_dirs() {
# make required directories
user=`nginx -V 2>&1 | grep "configure arguments:" | sed 's/[^*]*--user=\([^ ]*\).*/\1/g' -`
options=`$nginx -V 2>&1 | grep 'configure arguments:'`
for opt in $options; do
if [ `echo $opt | grep '.*-temp-path'` ]; then
value=`echo $opt | cut -d "=" -f 2`
if [ ! -d "$value" ]; then
# echo "creating" $value
mkdir -p $value && chown -R $user $value
fi
fi
done
}
start() {
[ -x $nginx ] || exit 5
[ -f $NGINX_CONF_FILE ] || exit 6
make_dirs
echo -n $"Starting $prog: "
daemon $nginx -c $NGINX_CONF_FILE
retval=$?
echo
[ $retval -eq 0 ] && touch $lockfile
return $retval
}
stop() {
echo -n $"Stopping $prog: "
killproc $prog -QUIT
retval=$?
echo
[ $retval -eq 0 ] && rm -f $lockfile
return $retval
}
restart() {
configtest || return $?
stop
sleep 1
start
}
reload() {
configtest || return $?
echo -n $"Reloading $prog: "
killproc $nginx -HUP
RETVAL=$?
echo
}
force_reload() {
restart
}
configtest() {
$nginx -t -c $NGINX_CONF_FILE
}
rh_status() {
status $prog
}
rh_status_q() {
rh_status >/dev/null 2>&1
}
case "$1" in
start)
rh_status_q && exit 0
$1
;;
stop)
rh_status_q || exit 0
$1
;;
restart|configtest)
$1
;;
reload)
rh_status_q || exit 7
$1
;;
force-reload)
force_reload
;;
status)
rh_status
;;
condrestart|try-restart)
rh_status_q || exit 0
;;
*)
echo $"Usage: $0 {start|stop|status|restart|condrestart|try-restart|reload|force-reload|configtest}"
exit 2
esac
chmod +x /etc/init.d/nginx # 复***务脚本执行权限
vi /etc/nginx/nginx.conf # 编辑主配置文件
worker_processes  2;
error_log  /var/log/nginx/nginx.error.log;
pid        /var/run/nginx.pid;
events {
worker_connections  1024;
}
http {
include       mime.types;
default_type  application/octet-stream;
log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
sendfile        on;
keepalive_timeout  65;
server {
listen       80;
server_name  xxrenzhe.lnmmp.com;
access_log  /var/log/nginx/nginx.access.log  main;
location / {
root   /www/lnmmp.com;
index  index.php index.html index.htm;
}
error_page  404              /404.html;
error_page  500 502 503 504  /50x.html;
location = /50x.html {
root   /www/lnmmp.com;
}
location ~ \.php$ {
root           /www/lnmmp.com;
fastcgi_pass   127.0.0.1:9000;
fastcgi_index  index.php;
fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
include        fastcgi_params;
}
}
}
vi /etc/nginx/fastcgi_params # 编辑fastcgi参数文件
fastcgi_param  GATEWAY_INTERFACE  CGI/1.1;
fastcgi_param  SERVER_SOFTWARE    nginx;
fastcgi_param  QUERY_STRING       $query_string;
fastcgi_param  REQUEST_METHOD     $request_method;
fastcgi_param  CONTENT_TYPE       $content_type;
fastcgi_param  CONTENT_LENGTH     $content_length;
fastcgi_param  SCRIPT_FILENAME    $document_root$fastcgi_script_name;
fastcgi_param  SCRIPT_NAME        $fastcgi_script_name;
fastcgi_param  REQUEST_URI        $request_uri;
fastcgi_param  DOCUMENT_URI       $document_uri;
fastcgi_param  DOCUMENT_ROOT      $document_root;
fastcgi_param  SERVER_PROTOCOL    $server_protocol;
fastcgi_param  REMOTE_ADDR        $remote_addr;
fastcgi_param  REMOTE_PORT        $remote_port;
fastcgi_param  SERVER_ADDR        $server_addr;
fastcgi_param  SERVER_PORT        $server_port;
fastcgi_param  SERVER_NAME        $server_name;
  启动服务:
  service nginx configtest # 服务启动前先验证配置文件是否正确
  service nginx start
  ps -ef |grep nginx # 检查nginx进程,尤其是worker进程是否与worker_processes值一致
  ss -antupl |grep 80 # 检查服务端口是否启动
  性能测试
  测试说明
  每次测试都进行3次,最后数据取平均值;
  对比测试中的Apache采用event的MPM机制,最大化提高Apache的并发性能;
  每次测试后,都需重新启动服务(httpd或nginx),以防止多次测试数据不准;
  测试工具:webbench
  优点:比ab能更好的模拟并发请求,最大支持模拟30000并发连接;
 测试方法
# 安装wenbench
wget http://blog.s135.com/soft/linux/webbench/webbench-1.5.tar.gz
tar xf webbench-1.5.tar.gz
cd webbench-1.5
make && make install
# 测试
webbench -c 100 -t 30 http://172.16.25.112/nginx.html # 测试静态文件访问
webbench -c 20 -t 30 http://172.16.25.112/test_mem.php # 测试动态文件访问
  测试数据
  分析趋势图
  静态文件访问趋势图
  动态文件访问趋势图
  
  总结
  综合上面测试得出的趋势图可以看出:
  静态文件测试时,低并发(200以下)情况下,Nginx和Apach的处理能力相当(2000pages/sec左右),当并发数超过200后,则 Apache的处理能力开始下降,而Nginx保持稳定;同时随着并发量的增大,Apache令人诟病的内存占用和负载开始急剧上升,与此同 时,Nginx在内存占用和负载方面的略微提升则可以忽略不计了;
  动态文件测试时,低并发 (100以下)情况下,Nginx和Apache的处理能力相当(650pages/sec左右),但Nginx的内存占用和负载峰值只有Apache的 50%左右;在高并发情况下(100以上),Apach的动态处理能力开始下滑,当并发达到500时,开始出现失败的请求,说明此时已达到的Apache 的处理上限了,而反观Nginx,虽然处理动态请求会消耗更多的内存,但其处理能力随着并发量的上升而上升,即使并发1000动态请求,也未达到其处理能 力上限;
  故不管是在静态文件请求还是动态文件请求方面,Nginx的性能都是强势优于Apache的;虽然可以通过系统调优的方式提高Apache的处理性能,但和Nginx相比,还是不足以打动技术狂热份子的吧,哈哈!

posted @ 2014-05-23 10:04 顺其自然EVO 阅读(2371) | 评论 (0)编辑 收藏

Linux下设置环境变量各配置文件的区别

 /etc/profile:此文件为系统的每个用户设置环境信息,当用户第一次登录时,该文件被执行.
  并从/etc/profile.d目录的配置文件中搜集shell的设置.
  /etc/bashrc:为每一个运行bash shell的用户执行此文件.当bash shell被打开时,该文件被读取.
  ~/.bash_profile:每个用户都可使用该文件输入专用于自己使用的shell信息,当用户登录时,该
  文件仅仅执行一次!默认情况下,他设置一些环境变量,执行用户的.bashrc文件.
  ~/.bashrc:该文件包含专用于你的bash shell的bash信息,当登录时以及每次打开新的shell时,该
  该文件被读取.
  ~/.bash_logout:当每次退出系统(退出bash shell)时,执行该文件.
  另外,/etc/profile中设定的变量(全局)的可以作用于任何用户,而~/.bashrc等中设定的变量(局部)只能继承/etc/profile中的变量,他们是"父子"关系.
  ~/.bash_profile 是交互式、login 方式进入 bash 运行的
  ~/.bashrc 是交互式 non-login 方式进入 bash 运行的
  通常二者设置大致相同,所以通常前者会调用后者。

posted @ 2014-05-22 10:21 顺其自然EVO 阅读(189) | 评论 (0)编辑 收藏

Derby数据库的安装配置及使用

Derby数据库是一个纯用Java实现的内存数据库,属于Apache的一个开源项目。由于是用Java实现的,所以可以在任何平台上运行;另外一个特点是体积小,免安装,只需要几个小jar包就可以运行了。下面说下其安装及配置
  安装
  1).从apache下载Derby数据库(如db-derby-10.10.1.1-bin.zip)并解压到任意目录(如:D:\Derby\db-derby-10.10.1.1-bin)。
  2).配置环境变量DERBY_HOME=D:\Derby\db-derby-10.10.1.1-bin
  并添加到path和classpath环境变量(%DERBY_HOME%\bin;%DERBY_HOME%\lib\derbyrun.jar)
  3).测试数据库安装 C:\>sysinfo
------------------ Java Information ------------------
Java Version:    1.7.0_40
Java Vendor:     Oracle Corporation
Java home:       C:\Program Files\Java\jdk1.7.0_40\jre
Java classpath:  D:\Derby\db-derby-10.10.1.1-bin\bin;D:\Derby\db-derby-10.10.1.1-bin\lib\derbyrun.jar;
OS name:         Windows 7
OS architecture: amd64
OS version:      6.1
Java user name:  qqqqq
Java user home:  D:\userdata\qqq
Java user dir:   C:\
java.specification.name: Java Platform API Specification
java.specification.version: 1.7
java.runtime.version: 1.7.0_40-b43
--------- Derby Information --------
[D:\Derby\db-derby-10.10.1.1-bin\lib\derby.jar] 10.10.1.1 - (1458268)
[D:\Derby\db-derby-10.10.1.1-bin\lib\derbytools.jar] 10.10.1.1 - (1458268)
[D:\Derby\db-derby-10.10.1.1-bin\lib\derbynet.jar] 10.10.1.1 - (1458268)
[D:\Derby\db-derby-10.10.1.1-bin\lib\derbyclient.jar] 10.10.1.1 - (1458268)
  连接
  C:\>ij
  ij 版本 10.10
  ij> CONNECT 'jdbc:derby:D:\Project\derbyDB\testdb;create=true';(如果数据库testdb不存在,则创建改数据库)
  ij> CONNECT 'jdbc:derby:D:\Project\derbyDB\testdb;';           (连接testdb数据库)
  ij(CONNECTION1)> CREATE TABLE FIRSTTABLE(ID INT PRIMARY KEY,NAME VARCHAR(12));(创建表)
  已插入/更新/删除 0 行
  ij(CONNECTION1)> INSERT INTO FIRSTTABLE VALUES(10,'TEN'),(20,'TWENTY'),(30,'THIRTY');(插入数据)已插入/更新/删除 3 行
  ij(CONNECTION1)> SELECT * FROM FIRSTTABLE;
  ID |NAME
  ------------------------
  10 |TEN
  20 |TWENTY
  30 |THIRTY
  已选择 3 行
  ij(CONNECTION1)>exit;(退出)
  说明
  1. sysinfo工具用于显示Java环境信息和Derby的版本信息。
  2. ij工具来进行数据库交互,执行SQL脚本,如查询、增删改、创建表等
  例子
  下面是个完整的例子,如何程序中操作JavaDB
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Properties;
public class TestDerby {
public static void main(String[] args) {
try {
Class.forName("org.apache.derby.jdbc.EmbeddedDriver").newInstance();
System.out.println("Load the embedded driver");
Connection conn = null;
Properties props = new Properties();
props.put("user", "user1"); props.put("password", "user1");
conn=DriverManager.getConnection("jdbc:derby:C:\\Project\\derbyDB\\testdb;");
System.out.println("create and connect to testdb");
Statement s = conn.createStatement();
ResultSet rs = s.executeQuery("SELECT * FROM FIRSTTABLE");
System.out.println("name\t\tscore");
while(rs.next()) {
StringBuilder builder = new StringBuilder(rs.getString(1));
builder.append("\t");
builder.append(rs.getInt(1));
System.out.println(builder.toString());
}
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}catch (Exception e) {
e.printStackTrace();
}
}
}

posted @ 2014-05-22 10:18 顺其自然EVO 阅读(2635) | 评论 (0)编辑 收藏

Ant构建Java项目之第2篇

 实例1:实现自定义Ant任务
  其中FileSorter文件的源码如下:
package com.ant.test02;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.ListIterator;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Task;
public class FileSorter extends Task {
private File srcFile;
private File destFile;
public File getSrcFile() {
return srcFile;
}
public void setSrcFile(File srcFile) {
this.srcFile = srcFile;
}
public File getDestFile() {
return destFile;
}
public void setDestFile(File destFile) {
this.destFile = destFile;
}
@Override
public void execute() throws BuildException {
try {
BufferedReader fromFile = new BufferedReader(
new FileReader(srcFile));
BufferedWriter toFile = new BufferedWriter(new FileWriter(destFile));
List<String> list = new ArrayList<String>();
String line = fromFile.readLine();
while (line != null) {
list.add(line);
line = fromFile.readLine();
}
Collections.sort(list);
for (ListIterator<String> li = list.listIterator(); li.hasNext();) {
String str = li.next();
toFile.write(str);
toFile.newLine();
}
fromFile.close();
toFile.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
实现一个文件排序的功能。
  build.xml文件源码如下:
<?xml  version="1.0"?>
<project name="tasks" default="main">
<property name="build.dir" location="build" />
<target name="init">
<mkdir dir="${build.dir}" />
</target>
<target name="compile" depends="init">
<javac srcdir="src" destdir="${build.dir}" />
</target>
<target name="simpletask" depends="compile">
<taskdef name="simpletask" classname="com.ant.test02.FileSorter" classpath="${build.dir}" />
<simpletask srcFile="input.txt" destFile="output.txt"/>
</target>
<target name="clean">
<delete dir="${build.dir}" />
</target>
<target name="main" depends="simpletask" />
</project>
  input.txt中输入需要排序的内容后,运行build.xml。查看output.txt,发现内容进行了排序。
  实例2:模式匹配
  可以对目录执行模式匹配。例如,模式src*/*.java将匹配带src前缀的任何目录中的所有Java文件。
  还有另一种模式结构:**,它匹配任意数量的目录。例如,模式**/*.java将匹配当前目录结构下的所有Java文件。
<copy todir=”archive”>
<fileset dir=”src”>
<include name=”*.java”/>
</fileset>
</copy>
  fileset默认情况下包含指定src目录下的所有文件,因此为了仅选择Java文件,我们对模式使用一个include元素。类似地,我们可以对另一个模式添加一个exclude元素,从而潜在地排除include指定的匹配项。甚至可以指定多个include和exclude元素;这样将得到一组文件和目录,它们包含include模式的所有匹配项的并集,但排除了exclude模式的所有匹配项。
<?xml  version="1.0"?>
<project name="tasks" default="main">
<property name="build.dir" location="build" />
<target name="main" >
<copy todir="${build.dir}/dest_dir">
<!-- 只拷贝build目录下的文件,其子目录下的文件不做拷贝 -->
<fileset dir="${build.dir}">
<include name="*.class"/>
<exclude name="*.war"/>
</fileset>
</copy>
</target>
</project>
 实例3:可执行Jar文件的生成
  在com.ant.test03包下新建一个Testjar.java文件,源码如下:
package com.ant.test03;
import java.awt.Color;
import java.awt.Dimension;
import javax.swing.JButton;
import javax.swing.JFrame;
public class TestJar {
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setSize(new Dimension(200, 300));
frame.setBackground(new Color(200, 200, 200));
frame.setAlwaysOnTop(true);
frame.getContentPane().add(new JButton("Test Ant"));
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
  build.xml文件源码如下:
<?xml version="1.0" encoding="utf-8"?>
<project name="myAntProject" default="dist" basedir=".">
<property name="compile" value="compile" />
<property name="dist" value="dist" />
<target name="preprocess" >
<mkdir dir="${compile}" />
<mkdir dir="${dist}" />
</target>
<target name="myCompile" depends="preprocess">
<javac srcdir="src" destdir="${compile}">
</javac>
</target>
<target name="dist" depends="myCompile">
<jar destfile="${dist}/package.jar" basedir="${compile}">
<manifest>
<attribute name="Built-By" value="${user.name}" />
<attribute name="Main-Class" value="com.ant.test03.TestJar" />
</manifest>
</jar>
</target>
</project>
  运行后刷新MyEclipse项目,就可以看到在dist文件夹下生成的package.jar文件,双击可以运行。
相关文章:

posted @ 2014-05-22 10:16 顺其自然EVO 阅读(179) | 评论 (0)编辑 收藏

使用Jmeter录制web脚本

  1。web性能测试以及web http请求基本原理。
  再介绍录制jmeter脚本之前,我们先谈一下web性能测试。web就是调用http/https接口, 其实没有是什么复杂度可言。只是我们必须清楚,对于一个网站说,一个页面并通常不是只有一个http请求。如果需要测试一个rich web page,必须了解到这一点(测试http接口不在此类,虽然也是http协议,但是以http接口对外服务)。
  例如这样一个页面(下面是html代码)
  如果向服务器请求上面的一个页面,则除了本身页面的请求本身,还有一个css和img的资源。这样打开这样一个页面,本需要有三个http请求。
  1. http1 -> get the web page
  2. the web page contain two resources
  3. then request  http2 and http3 to  get the css file and image respecitively.
  所以这样就是发起了3个http请求,才完整的打开了一个页面。浏览器实际是这么做的,但浏览器作了很多优化。 (比如多线程下载资源,缓存图片,css等资源)。说道这里,不得不佩服loadrunner的强大。他可以尽量模拟浏览器的这些行为,来保证测试的结果准确性。但这些参数都可以动态调整的。
  2。实际录制脚本
  好,那么我们现在就来实际录制脚本吧,并且体验一下真正一个页面的实际http请求过程。这里我们只谈http,https不在本文所讲范围内。
  录制脚本目前有2种方法,我们先介绍jmeter proxy方法。另一个是badboy录制的脚本转化为jmeter脚本,直接想了解badboy,请直接访问
  http://www.badboysoftware.biz/docs/jmeter.htm
  jmeterproxy 来了。
  1)启动jmeter,这个不多说了。
  2)选择测试计划,右键添加线程组(thread group)
  3)选择这个线程组,右键添加config element -〉 http默认请求
  4)在http默认请求单元,填入server name 为jakarta.apache.org. 这个是我们要录制的页面。其他地方不填
  5)然后选择刚才那个县城组,右键加入一个录制控制器。位于Add>Logic Controllers -> Recording Controller
  6) 选择WorkBench,右键加入 Non-Test Elements -> Http proxy server
  7)在http proxy server里, 的patterns to include 里,写入.*\.html  这个是正则表达式,意思是录制所有的html为后缀名的页面。 那么如果你要录制后缀名为jsp或者do的,则写入.*\.jsp  和 .*\.do 分别。
  8)对于url怕tterns to exclude的地方,是写入不想被录制的一些资源文件url。比如图片等。 这些配置,视测试的具体场景而定。比如是否要测试静态图片等。
  9)为了调试录制的情况,我们选择http proxy server ,添加一个察看结果树监听器。这个以前我们曾经用过。
10)回到http proxy server然后,选择开始启动。这样proxy server 就启动了。
  11)这样我们打开一个浏览器,将浏览器的代理设置为jmeter proxy server 的ip和端口号。 因为proxy server 就在本地,所以就写localhost 和端口即可。
  12)设置好代理后,用浏览器访问http://jakarta.apache.org/jmeter/index.html
  13)随便点击一些连接,然后回到jmeter 的窗口,你查看结果树就看到了录制的http请求了。
  14)这样的脚本,我们就可以直接用来运行了。

posted @ 2014-05-22 10:12 顺其自然EVO 阅读(3323) | 评论 (0)编辑 收藏

验证集成数据的策略

在一个数据不断增长的世界,软件测试涉及不同的数据验证程序,这些程序是传统质量保证方法的一个组成部分。本文旨在为非数据人员提供数据验证工具,同时也为回归测试和功能测试提供更多自动且迅速的工具和测试策略。
  本文中,我将详细介绍数据验证的两种方法:自下而上法和自上而下法。这两种方法在不同的应用程序和测试策略中已实现的数据中所起的作用是不一样的。
  自下而上方法中,应用程序是代理器,而数据是反应器。我们通常使用此方法对新功能进行功能测试或把它与自上而下结合方法以覆盖大量代码。
  另一方面自上而下法中,数据只在画面里,我们根据外部知识判断关于数据模式内关系的猜想是真还是假。
  后一种方法使我们能够设计出成本低但功能强大的回归测试集以及根本原因分析法。

  介绍
  数据无所不在。它已不再是某些较高等级的应用程序的附带品,而被视为是有价值的、脆弱的。在信息论中,数据是信息抽象化即知识的最低水平。这意味着我们要有一个专家把行业知识原则又称产品需求和产品规格转化为一个一致的数据模式。
  这实际上解决了这一行中一直以来的一个困境:产品经理和产品架构师对产品规格的见解不一。
  事实上,数据的既定逻辑结构及评估它的明确结果可以把一个MRD / PRD转化成一个比以往更容易的测试计划
  但是,数据质量保证技术通常很昂贵且需要分析、BI工具和专业知识。
  本文不看数据验证本身,而是建议用一个简单的、可重复的、不怎么需要资源和外部或技术支持的、且基于数据验证的测试策略去替代标准质量保证程序。
  让我们考虑考虑一个在线购物市场的简单案例研究:实现B2C交易并对测试这样一个应用程序的设计和选项进行检查。

  自下而上的数据验证法
  直接设计包括应用程序和DB。
  此方法中,我们有一个传统的STP ,当自动使用简单的宏命令时,可用作一个自定义的和高度有效的低成本测试工具。
  为简单起见,我们假设在我们的例子中,基本的测试用例:



  

  需要注意的是,我们还没有对这个流程进行任何验证。
  我们不会把这个简单直接的流程扩展为一个完整的测试过程:“生成用户> 0 $交易”。
  这个程序将使用简单的Web宏命令调整发送给应用程序的涵盖购物车、用户资料等所有可能方案的需求。验证在最后进行以防止断裂或使用昂贵的基础设施。
  这个例子演示了数据流的另一个强大的优点。数据是事件流的结果。这意味着:例如,如果用户还没有注册账户(根据这个具体的例子),那我们就不指望在DB中找到交易。这可以被用于负面测试以扩大代码覆盖。
  回到我们的例子中,另一个过程可能是“用户退款”(在这里,例如,我们想用负数金额重复(a))等等。 验证应该在最后用任一电子表格(Excel,Zoho)或是像MySQL Workbench的免费SQL工具通过点击完成。从这个意义上来说Excel非常方便,它不需要专业知识,并具有不受规范限制的比较工具。关于这个我将在下一节做简要探讨。

  总结一下这种方法:
  1. 做小测试用例。
  2. 把它们一起放入10 TC过程。
  3. 把过程一起放入一个测试集。
  4. 最后进行验证。

  自上而下的数据验证法
  这种方法与我们所知道的经典的STP设计完全不同。它对功能测试和根本原因分析都有用。它可以被视作是我们所知道的黑盒测试,并在两个重要概念上不同于以往的设计:
  1. 数据只在图像中。因此,我们通常会把这个用作测试周期的第二层,用于bug和问题的根本原因分析。
  2. 如果先前的方法里我们试图把测试计划打破成一个个小流程,那么在这里,我们依靠产品规格、领域知识和很多常识,创造性地创建系统的不同用例之间的依赖关系。例如,对比之前的设计中的退款模块,让我们回忆一下我们的交易模块,。
  在那种设计中,我们不得不考虑一个交易,一个我们不得不事先用我们应用于退款和验证的宏命令建立的交易。
  现在的方法中,我们将使用电子表格/ SQL来获取DB中的所有退款行,将它们连到它们的父事务,并根据产品规格(金额,原因等)验证不同的数据字段。这种方法是很强大的,往往能揭示产品需求和架构中的问题。
  对于负面测试,我们有一个稳赢策略。例如,发现一笔交易里用户没有注册网站(例如,用户的电子邮件未被记录)。根本原因分析中,我们能够直接从DB生成一个执行流–即什么导致失败,并使用     简单的排序、筛选和条件格式作为Excel不受规范限制的分析工具去评估他们的IF-AND-OR-NOT依赖关系。
  这种方法对于使用Excel数据验证功能来执行简单的验证(如无前/后间隔,减少/增加/恰好n位场/十进制值溢出,日期范围限制等)的简单现场验证来说也是很经典的。

  自动化与回归
  正如前面提到的,数据验证对自动化而言很容易。
  对于迅速回归,我们会选择第二种方法,即根据一个预定义的基线验证DB数据。
  例如对于我们的购物网站,我们将有一组预定义的会产生一个数据集的一系列操作。
  我们可以输出DB到电子表格或上传基线到MySQL并点击/查询以便比较两个表格。
  Excel的VLOOKUP在带来两个表间的差异上表现出色,SQL通过采用两表间简单的左连接点带来差异。

  总结
  如前所述,数据验证在取代传统的功能测试和根本原因分析中非常有效。可以用简单,低成本的方法通过把它的继承逻辑结构扩展到被明确标示为通过/不通过的功能测试用例来进行数据验证。使用数据验证而非传统功能测试可以通过把产品规格转化为明确清晰的数据架构内的逻辑关系使编译出更好的测试用例更容易。

posted @ 2014-05-22 10:12 顺其自然EVO 阅读(182) | 评论 (0)编辑 收藏

QTP脚本—测试参数限制

 以前一直觉得自己没有写代码的资质,太急于求成,以为一天就能写好几个功能,几千行代码,于是就没耐心了,没心情学下去了....但是最近发现其实写代码是一个漫长的过程,都是在修修改改中成长起来的。于是今天试着慢慢用QTP测下参数限制,虽然代码量不多,其实也算不上编程,O(∩_∩)O哈哈~但也是个慢慢积累的过程。
  首先,我有一段登陆系统的测试模块,可以把它设为可重用的,并且参数化必要的信息,比如登陆用户名密码等等,这些就不细说了。可以参见《QTP自动化测试实践》8.3节 Action测试输入的参数化,调用过程见上一篇关于action的文章
  现在我要测试参数的限制:
  第一步,必须要在当前项目下新建一个action,步骤如下:
  选择Insert|Call to New Action:
  然后在弹出框中填写新建action的name和description,可不可重用,以及新action的位置,这里我的参数检查功能是在登陆模块之后,所以选择第一个At the end of the test
  第二步,既然2个action都是测试同一个软件,可以重用它们的对象库respositories,操作步骤是:
  先保存login这个action的对象库,后缀是“.tsr",然后选择Resources|Associate Respositories,选定刚刚保存的对象库文件,然后下面的Available Action选择login模块,右边的Associated Action选择Test_Parameters模块:
  于是,在测试参数的模块中就可以直接使用login的对象库了。 第三步,因为测试参数是一个繁杂的过程,有很多种组合方式,而且每个参数输入框都要求输入一遍,但是好在参数输入框的规则都是一样的,比如不能输入符号,字母,负数,小数,空格等等,除此之外还有范围限制,于是我就采用数据驱动测试的方法来做这个脚本。先写好输入参数的过程:
Dialog("App(1.0.1.0)").WinEdit("MINS").Set ”1“
Dialog("App(1.0.1.0)").WinEdit("MINX").Set ”1“
Dialog("App(1.0.1.0)").WinEdit("MAXS").Set ”2“
Dialog("App(1.0.1.0)").WinEdit("MAXX").Set ”2“
Dialog("App(1.0.1.0)").WinEdit("IR").Set ”1“
Dialog("App(1.0.1.0)").WinEdit("OR").Set ”2“
  然后根据数据驱动测试的步骤设置参数根据table中的值来输入,详情参见我博客《QTP:数据驱动测试》,Expert View的显示如下:
Dialog("App(1.0.1.0)").WinEdit("MINS").Set DataTable("minS", dtLocalSheet)
Dialog("App(1.0.1.0)").WinEdit("MINX").Set DataTable("minX", dtLocalSheet)
Dialog("App(1.0.1.0)").WinEdit("MAXS").Set DataTable("maxS", dtLocalSheet)
Dialog("App(1.0.1.0)").WinEdit("MAXX").Set DataTable("maxX", dtLocalSheet)
Dialog("App(1.0.1.0)").WinEdit("IR").Set DataTable("IR", dtLocalSheet)
Dialog("App(1.0.1.0)").WinEdit("OR").Set DataTable("OR", dtLocalSheet)<br>Dialog("App(1.0.1.0)").WinButton("应用参数").Click    ' 点击开始应用参数
  我设置了2种参数范围边界的数据,9种服务端会拒绝应用的参数组合,30种客户端限制的参数类型组合。
  第四步,对测试结果进行判断并显示在QTP生成的测试报告中:
  开始我是这么写的:
If   Dialog("App(1.0.1.0)").Dialog("提示").Exist(3) Then
Dialog("App(1.0.1.0)").Dialog("提示").WinButton("应用参数成功-确定").Click    ' 应用成功
reporter.ReportEvent micDone,"yes","前2个:可以应用成功"
else
reporter.ReportEvent micFail, "yes"," 前2个:服务端拒绝应用"
End If
If Dialog("App(1.0.1.0)").Dialog("错误").Exist(3) Then
Dialog("App(1.0.1.0)").Dialog("错误").WinButton("服务器不支持该参数-确定").Click
reporter.ReportEvent micDone,"server no","中间9个:服务端拒绝应用"
else
reporter.ReportEvent micFail,"server no", "中间9个:服务端居然应用了"
End If
If  Dialog("App(1.0.1.0)").Dialog("警告").Exist(3) Then
Dialog("App(1.0.1.0)").Dialog("警告").WinButton("客户端不支持该参数-确定").Click
reporter.ReportEvent micDone,"client no","后30个:DTC拒绝应用"
else
reporter.ReportEvent micFail,"client no", "后30个:DTC居然应用了"
End If
  运行之后发现,每一行参数的结果都有2个fail,因为我设置的三个主if是并列关系!符合其中一种情况之后,另外2种情况都会失败。
  于是,我再写成这样的:
If   Dialog("App(1.0.1.0)").Dialog("提示").Exist(3) Then
Dialog("App(1.0.1.0)").Dialog("提示").WinButton("应用参数成功-确定").Click    ' 应用成功
reporter.ReportEvent micDone,"yes","前2个:可以应用成功"
elseif Dialog("App(1.0.1.0)").Dialog("错误").Exist(3) Then
Dialog("App(1.0.1.0)").Dialog("错误").WinButton("服务器不支持该参数-确定").Click
reporter.ReportEvent micDone,"server no","中间9个:服务端拒绝应用"
elseif Dialog("App(1.0.1.0)").Dialog("警告").Exist(3) Then
Dialog("App(1.0.1.0)").Dialog("警告").WinButton("客户端不支持该参数-确定").Click
reporter.ReportEvent micDone,"client no","后30个:客户端拒绝应用"
else
reporter.ReportEvent micFail, "fail"," 结果跟预期不一致"
End If
  现在如果全部测试通过,不会出现fail的情况,但是需要在测试报告中一层层点开,查看对于每个测试数据行的测试结果是不是符合以下描述:
  1.前2个:可以应用成功
  2.中间9个:服务端拒绝应用
  3.后30个:客户端拒绝应用
  虽然上面的脚本避免了重复输入41种数据,但是后期的结果查看还是一个艰辛的过程,不知道QTP有没有把测试人员要求的结果描述统一到一个页面来的功能呢,有待挖掘。

posted @ 2014-05-22 10:11 顺其自然EVO 阅读(369) | 评论 (0)编辑 收藏

对redis中单元测试框架的简单修改

 最近在看redis的sds,发现原来redis提供了一个简单的测试框架,觉得挺有新意的。之前写代码,都是单独写所有的单元测试代码,感觉很麻烦,这个还不错,感觉修改一下会更加方便使用。
  测试宏定义如下:
/* This is a really minimal testing framework for C.
*
* Example:
*
* test_cond("Check if 1 == 1", 1==1)
* test_cond("Check if 5 > 10", 5 > 10)
* test_report()
*
* ----------------------------------------------------------------------------
*
* Copyright (c) 2010-2012, Salvatore Sanfilippo <antirez at gmail dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
*   * Redistributions of source code must retain the above copyright notice,
*     this list of conditions and the following disclaimer.
*   * Redistributions in binary form must reproduce the above copyright
*     notice, this list of conditions and the following disclaimer in the
*     documentation and/or other materials provided with the distribution.
*   * Neither the name of Redis nor the names of its contributors may be used
*     to endorse or promote products derived from this software without
*     specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef __TESTHELP_H
#define __TESTHELP_H
int __failed_tests = 0;
int __test_num = 0;
#define test_cond(descr,_c) do { \
__test_num++; printf("%d - %s: ", __test_num, descr); \
if(_c) printf("PASSED\n"); \
else {printf("FAILED\n"); __failed_tests++;\
printf("************************ WARNING ***************************\n\
[*failed*] We have failed tests here : %s :%d\n",__FILE__, __LINE__);\
printf("are you continue[y]:");\
if(tolower(getchar())=='n'){\
test_report();\
printf("************************************************************\n");\
exit(1);}\
else{\
printf("************************************************************\n");\
}} \
} while(0);
#define test_report() do { \
printf("------------------------------------------------------------\n");\
printf("%d tests, %d passed, %d failed\n", __test_num, \
__test_num-__failed_tests, __failed_tests);\
printf("------------------------------------------------------------\n");\
} while(0);
#endif


  写一个简单的测试来展现一下:
#include<stdio.h>
#include<stdlib.h>
#include"testhelp.h"
int fun1(int a,int b){
return a+b;
}
int fun2(int a,int b){
return a-b;
}
int fun3(int a,int b){
return a*b;
}
int fun4(int a,int b){
return a/b;
}
int main(){
test_cond("jiafa", fun1(1,2)==3);
test_cond("jianfa",fun2(2,1)==2);
test_cond("chengfa",fun3(2,1)==2);
test_cond("chufa",fun4(2,1)==2);
test_report();
}
  可以得到如下的测试结果:
  哈哈,其实也没有加进去什么,只是在出现错误的时候,出现错误出现在哪里的提示,以及选择是否要继续测试。这样的话更有利于跟踪错误的位置,及时修改错误。

posted @ 2014-05-22 10:09 顺其自然EVO 阅读(269) | 评论 (0)编辑 收藏

数据库关系模式规范化

  在教学中,大多实例都是主键由一列构成,所以也可以简单地说主属性与主键没有什么区别。
  第三范式的定义:如果关系模式R中的所有非主属性对任何候选关键字都不存在传递依赖,则称关系R是属于第三范式的。记作R 3NF。
  如:学生关系模式S1(学号,姓名,系号,系名,系地址)
  (学号)为关键字,因是单属性关键字,不存在部份依赖问题,应属于第二范式。但因为:学号—>系号,系号—\>学号,系号—>系地址,因此:学号—>系地址 是通过传递依赖实现的。即候选关键字“学号”不直接函数决定于非主属性“系地址”。所以此关系不属于第三范式。应将其分解为:
  SC(学号,姓名,系号)
  D(系号,系名,系地址)
  C(课程号,课程名,学分)
  S(学号,课程号,成绩)
  设计原则:“一事一地”,即一个关系反映一个实体或一个联系,不应把几样东西混合放在一起。基本关系模式切忌“大而全”,在若干个基本关系模式组成的关系模型上,根据需要可以通过自然联接导出所需要的关系。
  BCNF的定义:如果一个关系R中的所有属性都不传递依赖于R的任何候选关键字,或者说关系R中的每个决定因数都是候选关键字时,则称关系R属于BCNF范式,记作R BCNF。
  一个满足BCNF的关系模式有
  1.所有非主属性对每一个码都是完全函数依赖。
  2.所有的主属性对每一个不包含它的码,也是完全函数依赖。
  3.没有任何属性完全函数依赖于非码的任何一组属性。
  由于RBCNF,按定义排除了任何属性对码的传递依赖与部分依赖,所以R3NF。但是若R3NF,则R未必属于BCNF。
  下面用几个例子说明属于3NF的关系模式有的属于BCNF,但有的不属于BCNF。
  详细信息...
  例l 关系模式SJP(S,J,P)中,S是学生,J表示课程,P表示名次。每一个学生选修每门课程的成绩有一定的名次,每门课程中每一名次只有一个学生(即没有并列名次)。由语义可得到下面的函数依赖:
  (S,J)→P ,(J,P)→S
  所以(S,J)与(J,P)都可以作为候选码。这两个码各由两个属性组成,而且它们是相交的。这个关系模式中显然没有属性对码传递依赖或部分依赖。所以SJP3NF,而且除(S,J)与(J,P)以外没有其它决定因素,所以SJPBCNF。
  例2 关系模式STJ(S,T,J)中,S表示学生,T表示教师,J表示课程。每一教师只教一门课。每门课有若干教师,某一学生选定某门课,就对应一个固定的教师。由语义可得到如下的函数依赖。
  (S,J)→T;(S,T)→J;T→J。
  这里(S,J),(S,T)都是候选码。
  STJ是3NF,因为没有任何非主属性对码传递依赖或部分依赖。但STJ不是BCNF关系,因为T是决定因素,而T不包含码。
  3NF的“不彻底”性表现在可能存在主属性对码的部分依赖和传递依赖。非BCNF的关系模式也可以通过分解成为BCNF。例如STJ可分解为ST(S,T)与TJ(T,J),它们都是BCNF。
  一个模式中的关系模式如果都属于BCNF,那么在函数依赖范畴内,它已实现了彻底的分离,已消除了插入和删除的异常。
  关系模式规范化小结
  目的:规范化的目的是使结构合理,消除存储异常,使数据冗余尽量小,便于插入、删除和更新。
  原则:遵从概念单一化“一事一地”的原则,即一个关系模式描述一个实体或实体间的一种联系。规范的实质就是概念单一化。
  方法:将关系模式投影分解成两个或两个以上的关系模式。
  要求:分解后的关系模式集合应当与原关系模式“等价”,即经过自然联接可以恢复原关系而不丢失信息,并保持属性间合理的联系。

posted @ 2014-05-21 10:10 顺其自然EVO 阅读(519) | 评论 (0)编辑 收藏

Win7 64位下安装Selenium

 例子是基于python的,selenium是做啥的自己看吧。
  网上找了下,貌似有些已经过时了,重新弄了下,记录过程。
  0.安装python
  (略)
  我的python版本是2.7.5
  1.安装easy_install
  只能使用http://peak.telecommunity.com/dist/ez_setup.py进行安装,将文件下载下来后直接python运行即可;
  2.安装pip
  在c:\Python27\Scripts目录(目录可能不是这个,取决于你的python安装目录)下可以找到安装的easy_install,执行easy_install.exe pip即可,自动下载安装。
  3.安装selenium
  在c:\Python27\Scripts目录(目录可能不是这个,取决于你的python安装目录)下可以找到安装的pip,执行pip install selenium即可,自动下载安装。
  4.下载chrome的webdriver
  对应的下载目录都在这里了 http://chromedriver.storage.googleapis.com/index.html ,自己寻找合适的版本下载即可,然后32位的也可以在64位的系统上跑,但是需要注意的是对应的webdriver会需要chrome的版本,所以如果下载的比较新的webdriver版本,请更新自己的chrome版本。
  下载后将对应的exe的目录加入到环境变量,我是直接拷贝到c:\Python27目录下了,这样最方便。。。
  5.enjoy your time
  这里仍然来一个简单的例子。
# -*- coding:utf-8 -*-
from selenium import webdriver
browser = webdriver.Chrome()
browser.get("http://www.soso.com")
print browser.title
browser.close()
  打开chrome,打开soso的主页(不要问我为啥选择这个页面,缅怀下过去),输出页面的title,关闭浏览器。
  OK,搞定收工,后续再继续研究api。

posted @ 2014-05-21 10:09 顺其自然EVO 阅读(2871) | 评论 (0)编辑 收藏

仅列出标题
共394页: First 上一页 108 109 110 111 112 113 114 115 116 下一页 Last 
<2024年11月>
272829303112
3456789
10111213141516
17181920212223
24252627282930
1234567

导航

统计

常用链接

留言簿(55)

随笔分类

随笔档案

文章分类

文章档案

搜索

最新评论

阅读排行榜

评论排行榜