本文介绍6款开源的工具,可以将Java项目反向工程到UML的类图。
1.Eclipse MDT
模型开发工具(MDT)提供了一些示范性工具,在元模型的基础上的发展模式。它支持创建UML图,从代码的逆向工程到UML图等等。
2.ArgoUML
ArgoUML是一个领先的开源UML模型工具,它支持UML 1.4的所有标准,可以运行于任何Java平台上。
3.ModelGoon
ModelGoon是一个Java包的依赖分析的Eclipse插件,用来显示项目中Java包与包之间的依赖关系,如下图所示:
sqlite3数据库是关系型
数据库,体积小,支持ACID事物。
(ACID,指数据库事务正确执行的四个基本要素的缩写。包含:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)。一个支持事务(Transaction)的数据库系统,必需要具有这四种特性,否则在事务过程(Transaction processing)当中无法保证数据的正确性,交易过程极可能达不到交易方的要求。)
/********************************************/
sqlite是一个轻量级的嵌入式数据库。
特征:
1.零配置,无需安装和管理配置;
2.储存在单一磁盘文件中的一个完整的数据库;
3.数据库文件可以在不同字节顺序的机器间自由共享;
4.支持数据库大小至2TB;
5.足够小,全部源代码大致3万行c代码,250KB;
6.比以前流行的大多数数据库对数据的操作要快;
/*******************************************/
SQLite数据库采用模块化设计,由8个独立的模块构成,这些独立模块又构成了三个主要的子系统,模块将复杂的查询过程分解为细小的
工作进行处理。
/*******************************************/
手工建数据库:
linux@ubuntu:~$ sqlite3 my.db
查看帮助:
sqlite> .help
文件存放位置:
sqlite> .database
退出:
sqlite> .quit
查看表:
sqlite> .tables
显示表的结构:
sqlite> .schema
1.建表:
sqlite> create table usr(id integer primary key, name text,age integer null, gender text, salary real not null);
2.删除表
sqlite> drop table usr;
3.增:
sqlite> insert into usr(id, name, age, salary) values(2, 'liu', 20, 6000);
4.删
sqlite> delete from usr where id = 2;
5.改:
sqlite> update usr set gender = 'man' where id = 3;
6.查:
sqlite> select * from usr where id = 2;
7.在表中添加字段
sqlite>alter table usr add column country text;
/******************************************/
代码创建
sqlite编程接口
1.打开sqlite数据库
int sqlite3_open(char *path, sqlite3 **db);
path: 数据库文件的路径
db: 指向sqlite句柄的指针
返回值: 成功返回0,失败返回错误码(非零值)
2.关闭sqlite数据库
int sqlite3_close(sqlite3 *db);
返回值: 成功返回0,失败返回错误码
3.
const char *sqlite3_errmsg(sqlite3 *db);
返回值: 返回错误信息
程序的编译方法:
gcc -o test test.c -lsqlite3
4.执行SQL操作
typedef int (*sqlite3_callback)(void *, int, char **, char **); int sqlite3_exec(sqlite3 *db, const char *sql, sqlite3_callback callback, void *, char **errmsg); db: 函数库句柄 sql: SQL语句 callback:回调函数 errmsg: 错误信息指针的地址 |
返回值: 成功返回0,失败返回错误码
5.每次找到一条记录自动执行一次回调函数
typedef int (*sqlite3_callback)(void *para, int f_num, char **f_value, char **f_name); para: 传递回调函数的参数 f_num: 记录包含的字段数目 f_value: 包含每个字段值的指针数组 f_name: 包含每个字段名称的指针数组 |
返回值: 成功返回0,失败返回-1
6.不使用回调函数执行SQL操作
int sqlite3_get_table(sqlite3 *db, const char *sql, char ***resultp, int *nrow, int *ncolumn, char **errmsg);
db: 数据库句柄
sql: SQL语句
resultp: 用来指向sql执行结果的指针
nrow: 满足条件的记录的数目
ncolumn: 每条记录包含的字段数目
errmsg: 错误信息指针的地址
返回值: 成功返回0,失败返回错误码
涉及到多个
数据库之间的同步,由于某些原因(某些数据库只需要表内部分数据;数据库类型目前为
MySQL,但可能后期部分数据库采用
Oracle),不能采用MySQL的主从同步机制。由于对同步的实时性要求不高,记录个数也不是太多,另外做了一种简易的同步方案。
一、 通过触发器生成数据表版本号
将数据表的每次更新时间记录到另一张版本表中。
drop table if exists Tab_Version_Tab; create table Tab_Version_Tab ( Tab_Name char(40) not null, #表名 # TrigVersion int null, #触发版本号# SourceVersion int null, #源版本号# LocalVersion int null, #本地版本号# constraint pk_Tab_Version_Tab primary key(Tab_Name) ); delete from Tab_Version_Tab; insert into Tab_Version_Tab values("Demo1_Tab", 0, 0, 0); |
触发器脚本如下:
drop trigger if exists trig_insert_Demo1_Tab; delimiter | create trigger trig_insert_Demo1_Tab after insert on Demo_Tab for each row begin update Tab_Version_Tab set TrigVersion=UNIX_TIMESTAMP() where Tab_Name = 'Demo1_Tab'; end; | delimiter ; |
同样可以建立update,delete的触发器。郁闷的是MySQL的触发器只支持for each row,效率会较低。
在实际应用中,触发器脚本是用LUA脚本生成的。
local file_in = "d:\\sync_tables.sql" local file_out = "d:\\sync_trigs.sql" local tbl_prop = {} local fp = io.open(file_in, "r") if fp then while true do s1 = fp:read("*l") if not s1 then break end _,_,s2 = string.find(s1, "\"([%w%_]+)\"") if s2 then tbl_prop[s2] = 1 end end end local sql_prep = [[ drop trigger if exists trig_$OP_$TAB; delimiter | create trigger trig_$OP_$TAB after $OP on $TAB for each row begin update Tab_Version_Tab set TrigVersion=UNIX_TIMESTAMP() where Tab_Name = '$TAB'; end; | delimiter ; ]] local tbl_rep = {"insert", "update", "delete"} local fp_out = io.open(file_out, "w") if fp_out then fp_out:write("use xopensdb\n") for k,v in pairs(tbl_prop) do fp_out:write("-- triggers of " .. k .. "\n") for k1,v1 in pairs(tbl_rep) do local sql_out = sql_prep sql_out = string.gsub( sql_out, "$TAB", k); sql_out = string.gsub( sql_out, "$OP", v1); fp_out:write(sql_out) fp_out:write("\n") end fp_out:write("\n") end end |
二、 维护表记录一致
从数据库定时访问Tab_Version_Tab,检查是否发生变化,如果发生变化,则将主数据库的数据表按条件导入到从数据库的临时表中,然后比较临时表和本地表的记录是否相同:
1. 比较两者记录个数是否相同
2. 用natural join取出的记录个数和本地表记录是否相同
SELECT COUNT(*) FROM table_local NATURAL JOIN table_tmp;
如果不同,则清空本地表,从临时表插入。
MySQL无DBLINK,否则访问远端数据库会简单些。
1.测试类要继承AndroidTestCase,然后写
测试方法。
2.在AndroidManifest.xml清单文件中配置Junit测试的配置信息
<instrumentation android:name="android.test.InstrumentationTestRunner" android:targetPackage="com.xxc.junit" /><!-- 写测试的包名 --> <uses-library android:name="android.test.runner" /><!-- junit所需配置,这句话写在application标签的自标签中 --> |
也可以直接新建一个android测试工程
选择要测试的项目
最近在做webservices,得到的数据是从德国那边的服务器。要将这些数据整合到现在网站中去。不知道性能如何。就做个
压力测试。现在有些
压力测试工具都是收费的。在开源的apache中自带个ab工具。就在C:\Apache2. ...
最近在做webservices,得到的数据是从德国那边的服务器。要将这些数据整合到现在网站中去。不知道性能如何。就做个压力测试。现在有些压力测试工具都是收费的。在开源的apache中自带个ab工具。就在C:\Apache2.2\bin\ab.exe。用它可以检测自己的程序性能如何。
用下吧!,可以提高自己代码质量。
APACHE的bin目录下。
格式.ab [options] [http://]hostname[:port]/path
参数
-n requests Number of requests to perform //在测试会话中所执行的请求个数。默认时,仅执行一个请求 -c concurrency Number of multiple requests to make //一次产生的请求个数。默认是一次一个。 -t timelimit Seconds to max. wait for responses //测试所进行的最大秒数。其内部隐含值是-n 50000。它可以使对服务器的测试限制在一个固定的总时间以内。默认时,没有时间限制。 -p postfile File containing data to POST //包含了需要POST的数据的文件. -T content-type Content-type header for POSTing //POST数据所使用的Content-type头信息。 -v verbosity How much troubleshooting info to print //设置显示信息的详细程度 - 4或更大值会显示头信息, 3或更大值可以显示响应代码(404, 200等), 2或更大值可以显示警告和其他信息。 -V 显示版本号并退出。 -w Print out results in HTML tables //以HTML表的格式输出结果。默认时,它是白色背景的两列宽度的一张表。 -i Use HEAD instead of GET // 执行HEAD请求,而不是GET。 -x attributes String to insert as table attributes // -y attributes String to insert as tr attributes // -z attributes String to insert as td or th attributes // -C attribute Add cookie, eg. ‘Apache=1234. (repeatable) //-C cookie-name=value 对请求附加一个Cookie:行。 其典型形式是name=value的一个参数对。此参数可以重复。 -H attribute Add Arbitrary header line, eg. ‘Accept-Encoding: gzip’ Inserted after all normal header lines. (repeatable) -A attribute Add Basic WWW Authentication, the attributes are a colon separated username and password. -P attribute Add Basic Proxy Authentication, the attributes are a colon separated username and password. //-P proxy-auth-username:password 对一个中转代理提供BASIC认证信任。用户名和密码由一个:隔开,并以base64编码形式发送。无论服务器是否需要(即, 是否发送了401认证需求代码),此字符串都会被发送。 -X proxy:port Proxyserver and port number to use -V Print version number and exit -k Use HTTP KeepAlive feature -d Do not show percentiles served table. -S Do not show confidence estimators and warnings. -g filename Output collected data to gnuplot format file. -e filename Output CSV file with percentages served -h Display usage information (this message) |
//-attributes 设置 属性的字符串. 缺陷程序中有各种静态声明的固定长度的缓冲区。另外,对命令行参数、服务器的响应头和其他外部输入的解析也很简单,这可能会有不良后果。它没有完整地实现 HTTP/1.x; 仅接受某些’预想’的响应格式。 strstr(3)的频繁使用可能会带来性能问题,即, 你可能是在测试ab而不是服务器的性能。
参数很多,一般我们用 -c 和 -n 参数就可以了. 例如:
打开cmd,输入 以下代码。
cd C:\Apache2.2\bin
ab -n 1000 -c 100 http://zf.guqin.com/index/index
这个表示同时处理100个请求并运行1000次index.php文件。以下是打印出来的内容。
This is ApacheBench, Version 2.0.41-dev <$Revision: 1.121.2.12 $> apache-2.0 Copyright (c) 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ Copyright (c) 1998-2002 The Apache Software Foundation, http://www.apache.org/ Benchmarking 127.0.0.1 (be patient) Completed 100 requests Completed 200 requests Completed 300 requests Completed 400 requests Completed 500 requests Completed 600 requests Completed 700 requests Completed 800 requests Completed 900 requests Finished 1000 requests Server Software: Apache/2.2.8 //平台apache 版本2.2.8 Server Hostname: zf.guqin.com //服务器主机名 Server Port: 80 //服务器端口 Document Path: /index.php //测试的页面文档 Document Length: 1018 bytes //文档大小 Concurrency Level: 1000 //并发数 Time taken for tests: 8.188731 seconds //整个测试持续的时间 Complete requests: 1000 //完成的请求数量 Failed requests: 0 //失败的请求数量 Write errors: 0 Total transferred: 1361581 bytes //整个场景中的网络传输量 HTML transferred: 1055666 bytes //整个场景中的HTML内容传输量 Requests per second: 122.12 [#/sec] (mean) //大家最关心的指标之一,相当于 LR中的每秒事务数,后面括号中的 mean表示这是一个平均值 Time per request: 8188.731 [ms] (mean) //大家最关心的指标之二,相当于 LR中的平均事务响应时间,后面括号中的 mean表示这是一个平均值 Time per request: 8.189 [ms] (mean, across all concurrent requests) //每个请求实际运行时间的平均值 Transfer rate: 162.30 [Kbytes/sec] received //平均每秒网络上的流量,可以帮助排除是否存在网络流量过大导致响应时间延长的问题 Connection Times (ms) min mean[+/-sd] median max Connect: 4 646 1078.7 89 3291 Processing: 165 992 493.1 938 4712 Waiting: 118 934 480.6 882 4554 Total: 813 1638 1338.9 1093 7785 //网络上消耗的时间的分解,各项数据的具体算法还不是很清楚 Percentage of the requests served within a certain time (ms) 50% 1093 66% 1247 75% 1373 80% 1493 90% 4061 95% 4398 98% 5608 99% 7368 100% 7785 (longest request) |
//整个场景中所有请求的响应情况。在场景中每个请求都有一个响应时间,其中50%的用户响应时间小于1093 毫秒,60% 的用户响应时间小于1247 毫秒,最大的响应时间小于7785 毫秒
由于对于并发请求,cpu实际上并不是同时处理的,而是按照每个请求获得的时间片逐个轮转处理的,所以基本上第一个Time per request时间约等于第二个Time per request时间乘以并发请求数。
window下面测试 p参数的使用
ab.exe -n 100 -c 10 -p "D:/post.txt" -T "application/x-www-form-urlencoded" "http://localhost/test/index.php"
post.txt文件内容
myname=xiaoming&sex=1&age=10
index.php内容
<?php
file_put_contents('request.txt',implode('####',$_REQUEST));
测试结果
request.txt文件内容 提示 request.txt和index.php同目录
xiaoming####1####10
window下面测试 A参数的使用
ab.exe -A myname:mypassword -n 100 -c 10 "http://localhost/test/index.php"
PHP认证学习地址
http://www.php100.com/manual/php/features.http-auth.html
index.php内容
<?php if (!isset($_SERVER['PHP_AUTH_USER'])) { header('WWW-Authenticate: Basic realm="My Realm"'); header('HTTP/1.0 401 Unauthorized'); echo 'Text to send if user hits Cancel button'; exit; } else { $str = "<p>Hello {$_SERVER['PHP_AUTH_USER']}.</p>"; $str .="<p>You entered {$_SERVER['PHP_AUTH_PW']} as your password.</p>"; file_put_contents('request.txt',$str); } |
request.txt测试结果 提示和index.php一个文件
<p>Hello myname.</p><p>You entered mypassword as your password.</p>
1、VuGen发生器:捕捉用户的业务流,并最终将其录制成一个脚本:
(1)选择相应的一种协议;
(2)在客户端模拟用户使用过程中的业务流程,并录制成一个脚本;
(3)编辑脚本和设置Run-Time Settings项;
(4)编译脚本生成一个没有错误的可运行的脚本。
2、控制器(Controller):
(1)设计场景,包括手动场景设计和目标场景设计两种方式;
(2)场景监控,可以实时监控脚本的运行的情况。可以通过添加计数器来监控
Windows资源、应用服务器和
数据库使用情况。
场景设计的目的是设计出一个最接近用户实际使用的场景,场景设计越接近用户使用的实际情况,
测试出来的数据就越接近真实值。
3、负载发生器(Load Generators):模拟用户对服务器提交请求。
通常,在
性能测试过程中会将控制器和负载发生器分开;当使用多台负载发生器时,一定要保证负载均衡(指在进行性能测试的过程中,保证每台负载发生器均匀地对服务器进行施压)。
4、分析器(Analysis):主要用于对测试结果进行分析。
Selenium Grid是一个分布式
测试平台,它可以通过一个
server端的hub服务来控制多个用于提供Selenium脚本运行环境的client端,起到并发在多台机器上运行的作用。整个结构是由一个hub节点和若干个代理节点组成的。hub用于管理各个代理节点的注册和状态信息,并接受远程客户端代码的请求调用,然后把请求的命令再转给代理节点来执行,同时需要一个hub和至少一个代理节点。除此之外,grid还支持一种更友好的功能,其可以根据用例中启动测试的类型来相应的把用例转给符合匹配要求的测试代理。
下面说下本人如何搭建环境的:
1. 准备三台机器(一台为主机[提供hub服务],另两台为从机[client端])
2. 三台机器都安装好JDK环境(可参见本人的“Selenium2.0测试工程的搭建”)
3. 三台机器上都确保有selenium server的jar包
本人在主机上创建主节点,并在主机和两台从机上各创建一个子节点。最终的现象是在这三台机器上并发运行。
(1) 启动主节点
在主机上打开命令行,cd至selenium server jar包所在的路径下,用命令启动主节点hub服务:
java -jar selenium-server-standalone-2.39.0.jar -role hub 启动的默认端口是4444,如果要改变这个端口号,则命令后加上-port XXXX,如端口号改为6666,命令为java -jar selenium-server-standalone-2.39.0.jar -role hub -port 6666
(2) 启动子节点
在从机上打开命令行,cd至selenium server jar包所在的路径下,用命令启动子节点的服务:java -jar selenium-server-standalone-2.39.0.jar -role node -hub http://10.1.3.59/grid/register 启动的默认端口是5555。其中,10.1.3.45为hub所在主机的IP地址。由于本人在主机上创建了主节点和一个子节点,所以这两个节点的port应不同。
启动完成后,可以在主机上打开浏览器http://localhost:4444/grid/console,来查看各节点的状态。
最后,编辑代码(主节点的port为4444,各子节点的port为5555,主机IP为10.1.3.59,两台从机IP分别为10.1.3.197和10.1.3.200):
public string[] strServer = new string[3]; public const string strWeb = "http://www.baidu.com"; public void TestGrid() { strServer[0] = "http://10.1.3.197:5555/wd/hub"; strServer[1] = "http://10.1.3.200:5555/wd/hub"; strServer[2] = "http://10.1.3.59:5555/wd/hub"; int nThreadCount = 3; Thread[] threads = new Thread[nThreadCount]; for (int i = 0; i < nThreadCount; i++) { threads[i] = new Thread(new ThreadStart(OpenWeb)); threads[i].Name = i.ToString(); } foreach(Thread t in threads) { t.Start(); } Thread.Sleep(60000); } public void OpenWeb() { DesiredCapabilities dc = DesiredCapabilities.InternetExplorer(); int nId = Convert.ToInt32(Thread.CurrentThread.Name); Uri seleniumServer = new Uri(strServer[nId]); Uri addr = new Uri(strWeb); RemoteWebDriver driver = new RemoteWebDriver(seleniumServer, dc); driver.Navigate().GoToUrl(addr); } |
Scrum是
软件开发的
敏捷方法。它以2到4周为一个迭代开发出有价值的商业功能。Scrum团队有两个明显特征:他们是多面手(例如:他们具备完成
工作所必须的所有技能);他们是自管理的(例如:团队不断探索如何把工作做的最好的方法)。通过过去两年在Scrum团队中近2年的QA角色的不断实践,我认识到Scrum中的QA不仅仅是编写
测试用例和汇报缺陷那么简单。
对比传统瀑布模型的项目中的同步活动,Scrum期望开发活动根据实际需要的顺序来执行,例如异步。这点有悖传统,让许多客户、开发和业务关系人等新手产生疑惑 “如何在代码还没有完成之前进行有效的测试?” 本文重点解释了QA如何执行
敏捷测试以及Scrum团队中QA角色的重要性。我将通过本文分享在我对Scrum团队中QA角色及职责的认识。
不仅是编写测试用例
传统瀑布模型的项目中,QA介入的时机往往是在代码全部完成后的项目收尾阶段。这些项目中,QA拿到一份需求文档和已经完成的代码,然后开始编写和执行测试用例以检查应用程序是否符合需求文档。然而,Scrum中的QA角色不再仅仅是执行测试用例和汇报缺陷。
参加评估用户故事(Story)在Scrum团队中,QA分析师和其他团队成员一起参与或担当大量职责。他们从项目一开始就介入,并且与业务分析者和开发者密切联系。在Scrum中,QA不是一个单独的测试应用程序的团队。取而代之的是,Scrum团队是一个业务分析师、开发者、QA一起协同工作的综合团队。除了编写用例,QA还帮助产品负责人(PO)编写接受用例,相当于承担产品负责人代理的角色。当产品负责人没有时间时,QA作为代理保持团队持续前进。QA和产品负责人通过提出问题和质疑假设进行互动交流,最终澄清业务需求。
QA分析师一般很擅长根据用户需求创建测试用例场景。在识别和捕捉复杂和否定的用例上也很卓越。事实上,在这点上,QA往往比开发做的好,因为开发往往关注用户故事的好的方面。在版本和周期计划会议中评估用户故事时邀请QA参与能让团队不再仅仅思考好的方面,有利于产生一个综合好坏情况的客观真实的评估。评估是一个很难的事情,让所有人都参与评估是很好的实践。
有利于保持视角和目标明确
当团队执行测试和其他稳定产品的活动时,QA需要掌控计划、组织或让整个团队投入到测试工作中,并且像Scrum Master(SM)那样保持成员处于积极状态。很少有开发者喜欢做测试任务。QA需要和Scrum Master一起让测试对整个测试团队可见且目标明确。从而保持开发者或其他成员的积极性。有时一些测试场景的构建需要开发或其他团队成员的帮助,这样容易创新。力争让测试活动有趣,例如用刺激的测试场景、出人意料的测试数据和带有娱乐意味的竞争。总之,做任何事情,只要有助于团队乐于加入测试工作即可。
与客户和开发者紧密合作
QA的主要职责之一是将他们的测试结果反馈给产品负责人或收集他们的反馈。QA和产品负责人紧密合作,帮助他们制定详细的用户故事验收标准。随着团队在每个迭代中的深入了解,QA也可以帮助产品负责人修改或增强用户故事以更好地反映真实的需求。
偶尔,QA分析师还充当产品负责人代理。这种情况下,QA和开发者将坐在一起,作为一个团队一起工作以提高产品质量。QA可以和开发者结对来写
单元测试,讨论验收标准。合作的工作越多,需求也越清楚。一起工作带来的清晰需求将减少编码过程中经常遇到的各种问题或疑惑,从而给有效提高开发效率、节约开发和QA的时间。
根据团队每个人的需求和实际情况,整个团队将成为一体,都会协助测试。这样的实践平衡了团队和工作完成必须的共同职责,早期测试反馈和持续增长的质量下,它将使项目进展更快。
提供快速反馈
传统瀑布模型团队不断重复的“构建-测试-修复”周期徒增了大量额外工作量,浪费了大量时间。在Scrum中,QA和开发者在整个项目中一起工作,让活动变得更简单。在开发过程中,开发者能直接咨询QA验收标准或从用户视角任何功能上的期待行为。这让测试和缺陷修复更简单。
自动化回归测试
自动化测试常被誉为测试者最好的朋友,因为它可重复执行且执行一致,能得到更好的软件功能的测试覆盖率。在2到4周一个迭代的Scrum项目中这点尤为正确。因为QA大体都没有太多时间去测试应用程序。在2周的迭代中,QA必须完成迭代中所有新功能的功能点测试的同时还要完成对先前实现功能的测试。正因如此,这种职责提高了每个迭代中使用可用的自动化实践以减少QA压力的重要性。
当团队执行持续集成时,自动化测试在快速反馈上大有用途。每发布一个软件新版本,可以执行自动化测试并快速提供反馈以反映是否新老功能都正常工作。而没有自动化测试,就必须手工执行所有的测试,不仅单调,而且容易犯错。自动化测试能更早的发现缺陷,让QA有更多时间去探索新功能的一些特殊用例。通过自动化测试,QA可以更快更有效地完成测试工作。
参与发布准备演示
在每个迭代结束时,团队需要召开一个迭代审阅会议来向项目责任人和其他有兴趣的关系人展示这个迭代完成的用户故事。迭代审阅会议是团队中的“良药”,可以有效激发他们去尽可能完成更多用户故事。
在2到4周的小迭代中,为了让用户故事按时完成,团队中的每个人都必须沉浸在自己的工作。开发者关注实现用户故事和修复缺陷,QA关注用例编写、澄清产品责任人的问题、自动化之前迭代的测试。较短的迭代周期意味着开发没有多少时间去获知用户故事要求的全部功能。这样,开发一般要询问QA以更好的理解用户故事。因为QA知道完整的功能及每个需求和验收标准。在迭代审阅会议上,让QA演示项目和解答业务上的问题是很好的实践。这也让开发者更专注于处理技术层面的问题。
分析用户需求
QA是Scrum团队中产品负责人代理。他们擅长从用户视角理解业务需求。因为他们经常被问到用户如何使用项目。QA根据他们的测试经验给产品负责人提供反馈,帮助他们更好的从用户视角理解项目。
完善“完成定义”
对于敏捷团队,清晰的完成定义(DOD)是很重要的。“完成定义”是团队定义完成标准的简单列表,是在用户故事认定为完成必须要完成的事情。完成定义一般包括完成代码、执行功能和回归测试、获得产品负责人的认可。一个简单的完成定义可能包括如下项目:
代码完成
单元测试完成
功能和UI测试完成
产品负责人认可
定义“完成定义” 不是QA一个人的责任,QA的责任往往在于监管团队完成定义和每个完成的用户故事必须满足“完成定义”的基线要求。一个高效的敏捷团队在启动每个用户故事前都要审阅“完成定义”从而使团队每个人都了解目标。“完成定义”不是一层不变的,可能会根据Scrum的需要不断演变。“完成定义”既可以是迭代级的也可以是发布级的。
用测试策略规范测试
由于Scrum团队中没有测试领导甚至测试专家。构建测试计划或执行测试策略就存在问题。Scrum认为需要准备足够的文档去支持团队随时提出的需求。正因为此,需要准备足够的高层次测试策略的文档和计划来指导团队。因为没有QA领导在团队中,QA一般自己来制定测试策略。
测试者和分析者角色融合
测试者和分析者角色融合在敏捷团队中很常见,业务分析师的角色一般是负责创建和维护迭代和产品的Backlog、从业务角度分析用户故事、根据产品负责人要求划分用户故事优先级。而QA角色一般负责为每个故事定义或完善验收标准,确保之前完成的功能没有老的问题。QA测试每个用户故事,从终端用户视角验证定义的验收标准是否已经达到,他们也根据业务来分析用户故事。所以QA和业务分析师的角色责任、必备技能、总体目标有很多重合地方。
一般,在项目开始之时,敏捷团队从产品负责人那里拿到用户故事和提前定义的项目范围。在敏捷模式下,鼓励团队成员提出改善终端用户体验的新功能或改变已有功能,也鼓励团队一旦发现技术依赖或发现换一种实现将更有效而改变backlog中用户故事优先级和顺序。无论是定义需求、分析用户故事、定义和澄清验收标准、编写接受性验证用例或和用户紧密合作,对于小的团队,测试者和分析者的角色融为一体有很多优点,但也存在一些缺点,最大的顾虑是没有测试者或分析者能够投入过多精力来完全尽责。
结论
除了编写用例和汇报缺陷,QA在Scrum团队中承担更多的职责。他们是团队的重要组成部分,并且从项目一开始就参与其中。
过去两年在Scrum团队当QA分析师让我有了一个非常棒的体验,同时也获得了很多学习机会。我担当了很多不同的角色和职责,包括QA分析师、产品责任人代理、帮助开发写单元测试、确保团队的质量意识和跟踪问题和软件缺陷。总之,这些体验让我获得了很多 不错的技能,同时让我学习了如何做好不同的角色。更重要的是它告诉我如何去问问题而不再是仅仅遵循文档,也教会了我去做任何有助团队成功的事情。
上一篇我剩下的To-Do-List:
猜测数字
输入验证
生成答案
输入次数
输出猜测结果
今天争取全部搞定。
现在我们Guesser、生成答案、输入验证都有了。把它们组装成一起摇身一变成一个Game!
用一个类把这些职责单一的小模块组合起来。我暂且称它为GameManager.
分析剩下的需求。(1)输入6次GameOver.(2)输入合法数字返回猜测结果。(3)游戏结束提示重新开始游戏。(4)中途输入exit 退出游戏。(5)输入正确答案,GameOver。
先把之前写的Guesser提出一个接口。
public interface IGuesser { string AnswerNumber { get; } string Guess(string inputNumber); } |
public class Guesser :IGuesser { public string AnswerNumber { get; private set; } public Guesser(IAnswerGenerator generator) { AnswerNumber = generator.Generate(); } public string Guess(string inputNumber) { ... } } |
Test First.
新建GameManagerTest
[TestClass] public class GameManagerTest { [TestMethod] public void should_return_game_over_when_input_times_is_six_and_result_is_wrong() { IGuesser guess = new Guesser(new AnswerGeneratorForTest()); var game = new GameManager(guess); var input = "1368"; var maxtimes = 6; var actual = false; for (var time = 0; time < maxtimes; time++) { game.Guess(input); } actual = game.IsGameOver; Assert.AreEqual(true, actual); } } |
实现GameManager让测试通过。
public class GameManager { private readonly IGuesser guesser; public bool IsGameOver { get; private set; } private const int MAX_TIMES = 6; private int times; public GameManager(IGuesser guesser) { this.guesser = guesser; } public void Guess(string input) { times++; IsGameOver = times == MAX_TIMES; guesser.Guess(input); } } |
ok
猜测数字
输入验证
生成答案
输入次数
输出猜测结果
输入猜测结果。这里包含一个猜对的情况下应该返回"you win"并且Gameover。其他输入返回的结果,在Guesser和validator中已经Cover了。挑几个来测试一下输入输出。
[TestClass] public class GameManagerTest { private _gameManager _game; [TestInitialize] public void Init() { IGuesser guesser = new Guesser(new AnswerGeneratorForTest()); _game = new _gameManager(guesser); } [TestMethod] public void should_return__game_over_when_input_times_is_six_and_result_is_wrong() { const string input = "1368"; const int maxtimes = 6; var actual = false; for (var time = 0; time < maxtimes; time++) { _game.Guess(input); } actual = _game.Is_gameOver; Assert.AreEqual(true, actual); } [TestMethod] public void should_return_you_win_and__game_is_over_when_input_is_equal_answer_number() { const string input = "2975"; var actual = _game.Guess(input); Assert.AreEqual("You win!", actual); Assert.AreEqual(true, _game.Is_gameOver); } [TestMethod] public void should_return_try_again_input_must_be_four_digits_when_input_is_not_equal_four_digits() { const string input = "15243"; var actual = _game.Guess(input); Assert.AreEqual("try again the input must be four digits.", actual); } [TestMethod] public void should_return_try_again_input_can_not_be_empty_when_input_is_empty() { const string input = ""; var actual = _game.Guess(input); Assert.AreEqual("try again the input can't be empty.", actual); } [TestMethod] public void should_return_try_again_input_must_be_fully_digital_when_input_is_not_all_digital() { const string input = "a4sw"; var actual = _game.Guess(input); Assert.AreEqual("try again the input must be fully digital.", actual); } } |
修改GameManager类,让所有CASE PASS.
public string Guess(string input) { times++; IsGameOver = times == MAX_TIMES; var validator = new Validator(); if (!validator.Validate(input)) { return "try again " + validator.ErrorMsg; } var result = guesser.Guess(input); if (result == "4a0b") { IsGameOver = true; return "You win!"; } return "try again result is " + result + "."; } |
猜测数字
输入验证
生成答案
输入次数
输出猜测结果
最后:完善GameManager类的work flow。
public class GameManager { private const int MAX_TIMES = 6; private int times; private readonly IGuesser guesser; public bool IsGameOver { get; private set; } public GameManager(IGuesser guesser) { this.guesser = guesser; } private void Start() { times = 0; IsGameOver = false; OutputGameHeader(); } public void Run() { Start(); while (!IsGameOver) { Console.WriteLine(); Console.WriteLine(string.Format("[The {0}st time ] : please input number!", times + 1)); var input = Console.ReadLine(); if (IsExit(input)) continue; var result = Guess(input); Console.WriteLine(result); } OutputGamefooter(); } private bool IsExit(string input) { if (input.ToLower().Trim() == "exit") { Console.WriteLine("Make sure to exit game?(Y/N)"); var readLine = Console.ReadLine(); if (readLine != null) { var isexit = readLine.ToLower().Trim(); if (isexit == "y") { IsGameOver = true; } } return true; } return false; } public string Guess(string input) { times++; IsGameOver = times == MAX_TIMES; var validator = new Validator(); if (!validator.Validate(input)) { return "try again " + validator.ErrorMsg; } var result = guesser.Guess(input); if (result == "4a0b") { IsGameOver = true; return "You win!"; } return "try again result is " + result + "."; } private void OutputGameHeader() { Console.Clear(); Console.WriteLine(" --- Game Start! ---"); Console.WriteLine("---------------------------------------------------------------"); Console.WriteLine("| You can input a number or input exit for exiting this game! |"); Console.WriteLine("---------------------------------------------------------------"); } private void OutputGamefooter() { Console.WriteLine("--------------------------------"); Console.WriteLine("| Game Over! [Answer] is " + guesser.AnswerNumber + " |"); Console.WriteLine("--------------------------------"); } } Program.cs class Program { static void Main(string[] args) { var isRepeatGame = false; do { IGuesser guesser = new Guesser(new AnswerGenerator()); var game = new GameManager(guesser); game.Run(); Console.WriteLine("Try again?(Y/N)"); var line = Console.ReadLine(); if (line == null) continue; var readLine = line.ToLower().Trim(); isRepeatGame = readLine == "y"; } while (isRepeatGame); } } |
跑下所有的测试。
到这里。这个游戏的基本功能算做完了。做的比较简单。测试和产品代码也比较随意。
大家也可以试着做一做。感受感受测试驱动产品代码。
运行效果
下面是我在实践TDD中遇到的一些问题、以及我个人对它们的理解。
(1)先写测试在写代码开发速度降低了。
开发前期速度确实很慢。当项目越来越大。越来越复杂的时候。改一个bug,或者修改story之后。如何确保产品代码是否正确。手动测试需要多少时间呢?或者调试的时间有多长呢?有了这些测试。可以最大限度的节省你的时间。也许跑一遍测试就可以定位BUG。测试过了。你的修改也就没问题了。
(2)TDD驱动出来的代码。维护性、扩展性如何。
TDD有益于设计。把一个复杂的需求拆分成若干个小模块的过程当中,其实就在思考设计。如何保证每个小模块的职责单一。
(3)后写测试可以不?
我的理解是:第一 测试驱动开发是通过测试去驱动产品代码的,如果遇到一个很难的模块(你写不出来的),就可以通过测试一点点的去驱动。第二 如果在开发之后写测试的话,问问自己,会写吗?或者是能写全吗?如果有足够的信心。也可以写。
(4)TDD的产物可以方便后期的测试。
试想一下,项目到了后期,在庞大的系统面前,我们要修改某个类、某个方法、修改某个BUG、添加或扩展某个功能的时候。是不感觉特没安全感?会不会为了去找由修改一个小功能而导致其他功能崩溃的原因而抓狂呢?会不会为了定位一个BUG而在各个类之间不断的徘徊呢?会不会感觉到牵一发动全身的感觉呢?软件的坏味等等都会导致这种问题出现。到时候不但被老板骂,还要加班!还要陷入无止尽的各种调试中。(最主要的是你把TEAM里的MM给连累了!)。想避免这种问题吗?想尽快定位BUG的位置吗?如果你想!
说点体会:
(1)清晰的测试方法命名,让我们省去了文档维护的时间。
(2)站在不同角度分析用户需求。拆分Story有益于你的设计(DI)。
(3)所有TDD留下来的测试。可用来做自动化测试,无论你是修BUG,或者添功能。都可以通过自动化测试,快速得到反馈。
(4)有了重构的保证。
(5)进度可视化。可以看出一个复杂的模块,自己完成了多少。(有多少CASE通过)。
(6)小范围迭代。把当前工作重心放在当前这个“小步”上。
需要注意的:
(1)把握好测试的粒度。
(2)测试要尽可能的简单。
(3)测试不要依赖可变。
(4)断言优先。
其实TDD真正有威力的地方是Story划分。以及复杂模块代码的驱动。
以后如果有机会。能理解的更深。会把这两个写出来与大家分享分享。
对这段时间的TDD做个小总结。TDD的范围比较广。而且也比较抽象。以后会加深对TDD的理解。也会把这些记录下来。
代码比较简单。没源码!
相关文章:
Linux命令中的链接的意思是,通过操作符的行为将几个命令组合执行。Linux中的链接命令,有些像你在
shell中写短小的shell脚本,并直接在终端中执行。链接使得自动处理变得更方便。不仅如此,一个无人看管的机器在链接操作符的帮助下能够十分有条理地运行。
本文旨在介绍一些常用的链接操作符,通过简短的描述和相关的例子帮助读者提高生产力、降低系统负载、写出更加简短有意义的代码。
1. 和号操作符 (&)
‘&’的作用是使命令在后台运行。只要在命令后面跟上一个空格和 ‘&’。你可以一口气在后台运行多个命令。
在后台运行一个命令:
tecmint@localhost:~$ ping -c5 www.tecmint.com &
同时在后台运行两个命令:
root@localhost:/home/tecmint# apt-get update & mkdit
test &
2. 分号操作符 (;)
分号操作符使你可以一口气运行几个命令,命令顺序执行。
root@localhost:/home/tecmint# apt-get update ; apt-get upgrade ; mkdir test
上述命令先后执行了update和upgrade,最后在当前
工作目录下创建了一个‘test’文件夹
3. 与操作符 (&&)
如果第一个命令执行成功,与操作符 (&&)才会执行第二个命令,也就是说,第一个命令退出状态是0。(译注:原文的这里明显写错了,我们进行了改译,有兴趣的读者可以参看原文以及原文下面的评论。在UNIX里面,0表示无错误,而所有非0返回值都是各种错误)。这个命令在检查最后一个命令的执行状态时很有用。
比如,我想使用links 命令在终端中访问网站tecmint.com,但在这之前我需要检查主机是否在线或不在线。
root@localhost:/home/tecmint# ping -c3 www.tecmint.com && links www.tecmint.com
4. 或操作符 (||)
或操作符 (||)很像编程中的else语句。上面的操作符允许你在第一个命令失败的情况下执行第二个命令,比如,第一个命令的退出状态是1。
举例来说,我想要在非root帐户中执行‘apt-get update‘,如果第一个命令失败了,接着会执行第二个命令‘links www.tecmint.com‘。
tecmint@localhost:~$ apt-get update || links tecmint.com
上面的命令中,由于该用户不允许更新系统,这意味着第一个命令的退出状态是’1′,因此最后一个命令‘links tecmint.com‘会执行。
如果第一个命令成功执行并且退出状态是‘0‘呢?很明显的,第二个命令不会执行。
tecmint@localhost:~$ mkdir test || links tecmint.com
这里,用户在家目录创建了一个‘test‘文件夹,这是被允许的。命令成功的执行,退出状态是‘0‘,因此,最后的命令不会执行。
5. 非操作符 (!)
非操作符 (!)很像except语句。这个命令会执行除了提供的条件外的所有的语句。要理解这点,在你的主目录创建一个目录‘tecmint’,并‘cd’到它这里。
tecmint@localhost:~$ mkdir tecmint tecmint@localhost:~$ cd tecmint |
接下来,在文件夹‘tecmint’下创建不同类型的文件。
tecmint@localhost:~/tecmint$ touch a.doc b.doc a.pdf b.pdf a.xml b.xml a.html b.html
看一下我们在文件夹‘tecmint’创建的新文件。
tecmint@localhost:~/tecmint$ ls a.doc a.html a.pdf a.xml b.doc b.html b.pdf b.xml |
用一种聪明的办法马上删除除了 ‘html’之外的所有文件。
tecmint@localhost:~/tecmint$ rm -r !(*.html)
验证一下上次的执行结果,使用ls 命令列出可见所有文件。
tecmint@localhost:~/tecmint$ ls
a.html b.html
6. 与或操作符 (&& – ||)
上面的操作符实际上是‘与’和‘或’操作符的组合。它很像‘if-else‘语句。
比如,我们ping tecmint.com,如果成功打印‘已验证’,否则打印‘主机故障’。
tecmint@localhost:~/tecmint$ ping -c3 www.tecmint.com && echo "Verified" || echo "Host Down"
示例输出
PING www.tecmint.com (212.71.234.61) 56(84) bytes of data. 64 bytes from www.tecmint.com (212.71.234.61): icmp_req=1 ttl=55 time=216 ms 64 bytes from www.tecmint.com (212.71.234.61): icmp_req=2 ttl=55 time=224 ms 64 bytes from www.tecmint.com (212.71.234.61): icmp_req=3 ttl=55 time=226 ms --- www.tecmint.com ping statistics --- 3 packets transmitted, 3 received, 0% packet loss, time 2001ms rtt min/avg/max/mdev = 216.960/222.789/226.423/4.199 ms Verified |
现在,断开我们现在的网络连接诶,再试一下相同的命令。
tecmint@localhost:~/tecmint$ ping -c3 www.tecmint.com && echo "verified" || echo "Host Down"
实例输出
ping: unknown host www.tecmint.com
Host Down
7. 管道操作符 (|)
PIPE在将第一个命令的输出作为第二个命令的输入时很有用。比如,‘ls -l’的输出通过管道到‘less’,并看一下输出。
tecmint@localhost:~$ ls -l | less
8. 命令合并操作符 {}
合并两个或多个命令,第二个命令依赖于第一个命令的执行。
比如,检查一下文件‘xyz.txt’是否在Downloads目录下,如果不存在则创建之并输出提示信息。
tecmint@localhost:~$ [ -f /home/tecmint/Downloads/xyz.txt ] || touch /home/tecmint/Downloads/xyz.txt; echo "The file does not exist"
但是这样的命令的运行结果并不如我们预期的运行,会始终都输出提示信息。因此需要使用{}操作符来合并命令:
tecmint@localhost:~$ [ -f /home/tecmint/Downloads/xyz1.txt ] || {touch /home/tecmint/Downloads/xyz.txt; echo "The file does not exist"} “The file does not exist” |
(译注:原文这里应该也是复制或书写的时候,出现了一些问题,例子中并没有出现小标题中的"{}"操作符,所以这里我们进行了修改)
9. 优先操作符 ()
这个操作符可以让命令以优先顺序执行。
Command_x1 &&Command_x2 || Command_x3 && Command_x4.
在上面的伪代码中,如果Command_x1执行失败了会怎么样,Command_x2,Command_x3, Command_x4没有一个会执行,对于这种情况,我们使用优先操作符。
(Command_x1 &&Command_x2) || (Command_x3 && Command_x4)
在上面的伪代码中,如果Command_x1执行失败,Command_x2不会执行,但是Command_x3会继续执行, Command_x4会依赖于 Command_x3的退出状态。
10. 连接符 ()
连接符 ()如它名字所说,被用于连接shell中那些太长而需要分成多行的命令。可以在输入一个“\”之后就回车,然后继续输入命令行,直到输入完成。比如,下面的命令会打开文本文件test(1).txt。
tecmint@localhost:~/Downloads$ nano test\
1.txt
今天就到这里,我会近日开始另外一个有趣的文章。不要走开,继续关注我们。不要忘记在评论栏里提出有价值的反馈。