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,测试总结和测试描述)有关。 测试用例设计有两个主要任务: ▪测试设计是所有逻辑测试用例的注意要求的草案。如果有效地设计,这就是一个能在测试执行时节省相当大精力及成本的关键部分。 ▪规格包含被转化为将要进行的物理测试指令的完整描述的草稿。 我们使用一个基于元数据的方法来设计测试用例。这种方法对于将要跨多个应用程序进行统一测试时以可重复的方式设计测试用例来说是很有用的。示例场景是涉及数据迁移或企业数据屏蔽的项目。基于元数据的测试用例设计和通用测试用例设计的主要区别是:前者没有在从需求去推导测试用例上花时间,因为通过元数据直接使用数据或前者没有在从需求去推导测试用例上花时间,因为通过元数据直接使用数据或前期数据的数据或属性是有可能的。
图1.使用测试用例生成
工具设计测试用例
用基于元数据的方法,我们可以着手处理库存要求;反过来,着手处理库存要求也可以获取元数据存储库中的数据属性。基于库存,就能准备高层次的场景,然后支持测试用例的开发。为了加快测试用例的准备过程,我们设计了可以用任意基本脚本语言(如VB脚本,UNIX或Perl)实现的方法,以可重复的方式高效地生成测试用例。 测试用例生成工具( TCGT )是一个基于在矩阵上的信息的基础上生成测试用例的高度自动化工具。它生成的测试用例可以满足验收,确认,应用核实的目的。基于元数据的测试用例设计可以用于以下两种场景,在这两种场景中要求了基于工厂的测试用例设计和生成。 场景1:数据迁移 数据迁移项目需要大量的数据库测试,以确保没有数据泄漏,且迁移后数据的完整性和质量得以保留。迁移过程是由一组作为映射规则和转换功能的规格决定的。例如,如果我们正在测试一个系统,把数据从SQL Server 2005迁移到SQL Server 2008中,我们就需要执行以下操作: ▪数据迁移的需求分析 ▪规范化要求 ▪元数据验证 ▪数据验证 场景2:数据屏蔽 基于元数据的测试用例的设计也可以在企业数据屏蔽中实现。数据屏蔽测试需要比较数据正确性和完整性的源头数据和目标数据。没有屏蔽或屏蔽后复制的表格应该测试其数据变化,屏蔽算法和业务规则。在大多数情况下,数据屏蔽场景需要可重复准备和执行的测试用例,这样测试用例设计中就可以使用元数据方法了。