下载地址:http://httpd.apache.org/
1. 安装Apache
# tar zxvf httpd-2.2.11.tar.gz
# cd httpd-2.2.11
# ./configure --prefix=/usr/local/apache --enable-so
//编译时加上加载模块参数--enable-so
# make
# make install
2. 配置系统启动时自动启动Apache服务。
# vi /etc/rc.d/rc.local
//在rc.local上加入一行/usr/local/apache/bin/apachectl –k start。
二、配置Apache
1. 修改httpd.conf文件
# vi /usr/local/apache/conf/httpd.conf
1) 设置根目录的路径
根目录是指Apache存放配置文件和日志文件的目录,配置参数为ServerRoot,默认位于“/usr/local/apache”。命令如下:
2) 设置监听IP地址及端口号
默认侦听本机所有IP地址的TCP80端口,命令如下:
Listen 80
用户也可以按自己的需求,使用多个Listen语句在多个地址和端口上侦听客户端请求。比如:
Listen 192.168.99.9:80
Listen 172.16.0.20:8080
3) 设置系统管理员E-mail
使用ServerAdmin参数设置管理员E-mail,比如管理员的Email地址为root@guoxuemin.cn
4) 设置服务器主机的名称
参数ServerName用来设置服务器的主机名称,如果没有域名则填入服务器的IP地址,比如服务器的IP地址为192.168.99.9:
5) 设置主目录的路径
用户可以使用参数DocumentRoot配置服务器主目录默认路径,比如,主目录路径为:/usr/local/apache2/htdocs
6) 设置默认文件
Apache的默认文件名为index.html,可以使用Directory Index参数来配置,比如,将index.php设置为默认文件名:index.php index.html
打开浏览器,输入地址:http://192.168.99.9,可以打开站点了:
2. 配置目录权限
使用<Directory 目录路径>和</Directory>设置目录的权限。比如:
<Directory “/var/www/icons”>
Options Indexes MultiViews
AllowOverride None
Order allow,deny
Allow from all
</Directory>
说明:
1)定义目录特性选项Options
可选参数:
Indexes:该特性表明目录允许“目录浏览”;
MultiViews:该特性表明目录允许内容协商的多重试图;
All:包含了除MultiViews外的所有特性;
ExecCGI:该特性表明允许在该目录下执行CGI脚本;
FollowSymLinks:该特性表明允许在该目录下使用符号连接。
2).htaccess文件
可以通过.htaccess文件(访问控制文件)设置目录的权限。
AccessFileName .htaccess
配置参数AllowOverride指定目录的.htaccess文件中指令的类型,包括All、None与Options、FileInfo、AuthConfig、Limit的任意组合。一般将AllowOverride设置为“None”,禁止使用.htaccess文件,当AllowOverride参数为All时,.htaccess文件可以覆盖任何以前的配置。
3)设置访问控制
使用Order选项来定义访问权限。
比如以下语句表明允许所有客户机的访问:
Order allow,deny
Allow from all
以下语句表明只允许网段192.168.99.0/24的客户机访问,但IP地址为192.168.99.254这个客户机除外:
Order allow,deny
Allow from 192.168.99.0/24
Deny from 192.168.99.254
用户可以根据需要,按上述方法配置自己的目录权限。
3. 创建虚拟目录
使用Alias选项创建虚拟目录,比如,建立“/icons/”这个虚拟目录,其对应的物理路径为“/var/www/icons/”:
Alias /icons/ “/var/www/icons/”
4. 用户认证
比如,有一个名为myweb的虚拟目录,其对应的物理路径是“/usr/local/myweb”,现对其启用用户认证功能,只允许用户Tonyguo和Wayne访问。
1)建立虚拟目录并设置用户认证:
Alias /myweb/ “/usr/local/myweb/”
<Directory “/usr/local/myweb/”>
Options none
AllowOverride None
Order allow,deny
Allow from all
AuthType Basic
AuthName “Please Login: ”
AuthUserFile/usr/local/apache/bin/mywebpwd
Require User Tongguo wayne
</Directory>
2) 建立口令文件并为用户设置口令
/usr/local/apache/bin/htpasswd –c /usr/local/apache/bin/mywebpwd Tonyguo
-c选项表示无论口令文件是否已经存在,都会重新写入文件并删除原内容。所以第二个用户wayne不需要使用-c选项
3)测试
在浏览器中输入:http://192.168.99.9/myweb,可以看到如下对话框:
输入用户名和密码后就可以访问网站了:
三、配置虚拟主机
1. 配置基于IP的虚拟主机
1)IP地址相同,但端口号不同的虚拟主机配置
比如使用192.168.99.9的两个不同端口80和8080发布两个不同站点, 虚拟主机分别对应的目录为/usr/local/apache/htdocs/web1和/usr/local/apache/htdocs/web2:
Listen 80 Listen 8080 <VirtualHost 192.168.99.9:80> ServerSignature email DocumentRoot /usr/local/apache/htdocs/web1 DirectoryIndex index.html index.htm LogLevel warm HostNameLookups off </VirtualHost> <VirtualHost 192.168.99.9:8080> ServerSignature email DocumentRoot /usr/local/apache/htdocs/web2 DirectoryIndex index.html index.htm LogLevel warm HostNameLookups off </VirtualHost> |
2)端口相同,ip不同的虚拟主机配置
比如服务器有两个IP地址192.168.99.9和192.168.99.10,使用这两个IP创建两台虚拟主机,虚拟主机分别对应的目录为/usr/local/apache/htdocs/web1和/usr/local/apache/htdocs/web2。设置方法如下:
<VirtualHost 192.168.99.9> ServerName 192.168.99.9:80 DocumentRoot /usr/local/apache/htdocs/web1 DirectoryIndex index.html index.htm </VirtualHost> <VirtualHost 192.168.99.10> ServerName 192.168.99.10:80 DocumentRoot /usr/local/apache/htdocs/web2 DirectoryIndex index.html index.htm </VirtualHost> |
2. 配置基于域名的虚拟主机
比如有两个域名guoxuemin.cn和tonyguo.com需要使用同一台服务器192.168.99.9,那么可以这样配置:
NameVirtualHost 192.168.99.9 <VirtualHost www.guoxuemin.cn> ServerName www.guoxuemin.cn:80 ServerAdmin admin@guoxuemin.cn DocumentRoot /usr/local/apache/htdocs/web1 DirectoryIndex index.html index.htm ErrorLog logs/web1/error_log Customlog logs/web1/access_log combined </VirtualHost> <VirtualHost www.tonyguo.com> ServerName www.tonyguo.com:80 ServerAdmin admin@tonyguo.com DocumentRoot /usr/local/apache/htdocs/web2 DirectoryIndex index.html index.htm ErrorLog logs/web1/error_log Customlog logs/web1/access_log combined </VirtualHost> <VirtualHost *:8088> serverAdmin new@student.com DocumentRoot "/web/web1" <Directory /web/web1> Options FollowSymlinks AllowOverride None Order Allow,Deny Allow from all </Directory> DirectoryIndex index.html index.php index.htm </VirtualHost> <VirtualHost *:8089> serverAdmin new@student.com DocumentRoot "/web/web2" <Directory /web/web2> Options FollowSymlinks AllowOverride None Order Allow,Deny Allow from all </Directory> DirectoryIndex index.html index.php index.htm </VirtualHost> <VirtualHost 192.168.88.144:80> serverAdmin new@student.com DocumentRoot "/web/web3" <Directory /web/web3> Options FollowSymlinks AllowOverride None Order Allow,Deny Allow from all </Directory> DirectoryIndex index.html index.php index.htm </VirtualHost> <VirtualHost 192.168.88.145:80> serverAdmin new@student.com DocumentRoot "/web/web4" <Directory /web/web4> Options FollowSymlinks AllowOverride None Order Allow,Deny Allow from all </Directory> DirectoryIndex index.html index.php index.htm </VirtualHost> <VirtualHost ftp.com> ServerName ftp.com:80 DocumentRoot /web/ftp <Directory /web/ftp> Options FollowSymlinks AllowOverride None Order Allow,Deny Allow from all </Directory> DirectoryIndex index.html index.php index.htm </VirtualHost> <VirtualHost mail.com> ServerName mail.com:80 DocumentRoot /web/mail <Directory /web/mail> Options FollowSymlinks AllowOverride None Order Allow,Deny Allow from all </Directory> DirectoryIndex index.html index.php index.htm </VirtualHost> |
负载均衡
#访问test目录时负载均衡
在modules目录下:导入mod
/usr/local/apache2/bin/apxs -c -i mod_proxy.c proxy_util.c /usr/local/apache2/bin/apxs -c -i mod_proxy_balancer.c /usr/local/apache2/bin/apxs -c -i mod_proxy_http.c vi http.conf LoadModule proxy_module modules/mod_proxy.so LoadModule proxy_balancer_module modules/mod_proxy_balancer.so LoadModule proxy_http_module modules/mod_proxy_http.so ProxyRequests Off <Proxy balancer://clusterphpinfo> BalancerMember http://192.168.88.134:8089 loadfactor=5 BalancerMember http://192.168.88.134:8088 loadfactor=1 #weight ProxySet lbmethod=bytraffic </Proxy> ProxyPass /test balancer://clusterphpinfo stickysession=STICK_PORT_TOKEN nofailover=On ProxyPassReverse /test balancer://clusterphpinfo <Location /balancer-manager> SetHandler balancer-manager Order Deny,Allow Allow from all #Allow from 192.168.88.* </Location> |
Appium官网所描述的特性,都很吸引人,刚好最近在研究Mobile Automation Testing,所以很有兴趣探索下Appium这个年轻的工具。
不过看了官网的documents,实在是让初入门的我感觉摸不着头脑。
所以,我只能search网上有限的资源,先从运行Appium提供的支持
Python,Javascript,Java,
Ruby等语言的examples开始慢慢体会Appium的
工作原理。
在此,记录这个探索的过程。
首先尝试成功的是,在Mac OS上的Python example。
这里,Appium的使用,主要有四个方面的因素:
一,Appium Server
1. Appium Server的安装
前提:已经安装node.js&npm
#sudo npm install -g appium //加上sudo以防Permission的问题
#npm install wd //这个还不清楚有什么影响??
------------
正常情况下,这样,Appium Server就安装成功了。
启动:
#appium & //若显示如下信息,说明Appium Server启动成功!(不加&,也可以启动~~~)
因为是Python版,所以就去Selenium官网下载Python的WebDriver(selenium-2.39.0.tar.gz)
https://pypi.python.org/pypi/selenium
解压:
#gzip -dc selenium-2.39.0.tar.gz | tar xvf -
安装:
#cd selenium-2.39.0
#sudo python setup.py install //sudo依旧是解决Permission的问题
-----------
这样,WebDriver就安装成功了。
测试的是appium提供的TestApp
首先,我们需要用xcode编译这个app
#cd appium
#cd sample-code/apps/TestApp
#xcodebuild -sdk iphonesimulator //为了防止iphonesimulator和设置的冲突,没有注明iphonesimulator的版本
-----------
如果看到** BUILD SUCCEEDED **,这个TestApp就build成功了。
四,Automation Scripts
自动化脚本,也是用appium提供的,在appium目录下可以找到
#cd appium
#cd sample-code
#cd examples/python
#python simple.py //执行测试脚本
-----------
此时,iOS的模拟器就会打开,开始执行simple.py的测试脚本了!!!
Test Case ID: CUST.01
Function: Add a new Customer
Data Assumptions: Customer database has been restored
General Description:
Add a new customer, via the Customer Add screen, and validate that new Customer was displayed corrected on the All Customer Screen.
Benefits:
1. Easier to automated - all have same structure
2. Data requirements are clearly defined
3. Navigation is precise
4. Expected results for each action is specific - no guess work involved
在系统无压力下,单用户迭代执行连续时间或次数,取得事物平均相应时间,作为分析衡量的标准。
---->验证脚本和参数的正确性
---->获取系统处理事物的性能数据
2.并发测试
检测系统多并发情况下,服务器硬件资源的利用情况、网络使用情况、应用服务器情况等。
同时也可以检查系统服务器是否健壮(是否会出现原本逻辑正确的事物,在并放情况下出现了逻辑错误。)
3、混合测试
混合场景测试,对典型脚本按照一定比例组成的混合脚本(最接近生产环境),找出系统可能存在的瓶颈
4、浪涌测试
高强度和低负载的交叉压力测试,验证系统在两种情况下的稳定性,以找出在增加和减少负载的过程中由于突然的占用和释放系统资源而引起的问题
5、容量测试
测出系统的最大容量。通过不断调整负载,找出系统在满足性能指标的条件下的最优容量,此配置下系统的最大并发数
6、稳定性测试
模拟一定数量用户长时间运行,验证系统在长时间运行后用户对系统的访问操作成功率是否降低,以找出系统潜在的内存泄漏等问题。
7、疲劳测试
就是对已饱和的
系统测试。采用满足性能指标的条件下的最大用户数持续执行一段时间业务,通过综合分析业务执行指标和资源监控指标来确定系统处理的最大
工作量的性能指标
8、扩展测试
已知性能瓶颈的前提下提高系统容量、或提升硬件等,找出性能瓶颈
9、批处理测试
处理大量数据,测试程序处理效率(在规定的时间内完成接收来自上游系统的数据和传递数据给下游系统)和服务器资源情况
数据库连接代码(php+Mysql)以及读取表中内容:
1.为了更好地设置数据连接,一般会将数据连接所涉及的值定义成变量.
$mysql_server_name='localhost'; //改成自己的mysql数据库服务器
$mysql_username='root'; //改成自己的mysql数据库用户名
$mysql_password='123456'; //改成自己的mysql数据库密码
$mysql_database='Mydb'; //改成自己的mysql数据库名
也可把以上变量放在一个文件里,可以随时让其他文件调用.
例如: 将以上内容放在:db_config.php 那么在其他需要用到数据库的页面直接调用.
调用代码:require("db_config.php");
2.连接数据库
$conn=mysql_connect($mysql_server_name,$mysql_username,$mysql_password) or die("error connecting") ; //连接数据库
mysql_query("set names 'utf8'"); //数据库输出编码 应该与你的数据库编码保持一致.南昌网站建设公司百恒网络PHP工程师建议用UTF-8 国际标准编码.
mysql_select_db($mysql_database); //打开数据库
$sql ="select * from news "; //SQL语句
$result = mysql_query($sql,$conn); //查询
3.读取表中的内容,这里我们用while,可以根据具体情况,用for 或其他的.
while($row = mysql_fetch_array($result))
{
echo "<div style=\"height:24px; line-height:24px; font-weight:bold;\">"; //排版代码
echo $row['Topic'] . "<br/>";
echo "</div>"; //排版代码
}
4.php写入数据库,Mysql数据的写入
$conn=mysql_connect($mysql_server_name,$mysql_username,$mysql_password); //连接数据库
mysql_query("set names 'utf8'"); //数据库输出编码
mysql_select_db($mysql_database); //打开数据库
$sql = "insert into messageboard (Topic,Content,Enabled,Date) values ('$Topic','$Content','1','2011-01-12')";
mysql_query($sql);
mysql_close(); //关闭MySQL连接
一、问题出现
我在用ASP.NET MVC4做微信开发的时候,用Forms验证方式做为authentication。
<authentication mode="Forms" >
<forms loginUrl="~/Account/Login" name="webcookies" slidingExpiration="true" timeout="30" />
</authentication>
然后用户登录成功后就设置Cookies,代码如下:
public static void SetTicket(HttpResponseBase response, bool remeberMe, int version, string identity, string displayName) { FormsAuthentication.SetAuthCookie(identity, remeberMe); string userData = displayName; var authTicket = new FormsAuthenticationTicket( version, identity, DateTime.Now, DateTime.Now.AddDays(remeberMe ? 30 : 1), remeberMe, userData); string encrytedTicket = FormsAuthentication.Encrypt(authTicket); HttpCookie authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encrytedTicket); response.Cookies.Add(authCookie); } |
我这里用FormsAuthenticationTicket.Version存储用户角色ID。
一开始还为自己的机智和精巧的设计洋洋得意,而且在安卓
手机上运行完全没有问题。但是在IOS上,不管我Version设置成什么值,它的值始终都是2。
根据msdn的解释:如果 FormsAuthenticationTicket 是使用不提供 version 参数的构造函数创建的,则 Version 属性返回 2;否则,Version 属性返回提供给FormsAuthenticationTicket 构造函数的值。
地址:http://technet.microsoft.com/zh-cn/magazine/system.web.security.formsauthenticationticket.version(VS.110).aspx
我明明已经使用了提供 version 参数的构造函数创建的,但是在IOS上就是不好使。
找了很多资料都没有得到一个很好的解释,希望博客园的园们能帮我解释一下这个问题呀。
Windows Phone 8.1, Windows Phone 8, Windows 8.1, Windows Server 2012 R2, Windows 8, Windows Server 2012, Windows 7, Windows Vista SP2, Windows Server 2008(不支持服务器核心角色), Windows Server 2008 R2(支持带 SP1 或更高版本的服务器核心角色;不支持 Itanium)
我也就释怀了。
不管怎么着,困扰我许久的问题终于找到问题所在了,谨以此文告诫后来者。
笔主利用这个七夕前后两天的寂寞时光,用JAVA磨了一个简单的图像相似度计算小程序,就在刚才终于纠结完毕,输出了1.0版本,小小的满足了一下可怜的虚荣心..
UI设计图:
实际运行效果图:
关键算法:
1 // 全流程 2 public static void main(String[] args) throws IOException { 3 // 获取图像 4 File imageFile = new File("c:/1.jpg"); 5 Image image = ImageIO.read(imageFile); 6 // 转换至灰度 7 image = toGrayscale(image); 8 // 缩小成32x32的缩略图 9 image = scale(image); 10 // 获取灰度像素数组 11 int[] pixels = getPixels(image); 12 // 获取平均灰度颜色 13 int averageColor = getAverageOfPixelArray(pixels); 14 // 获取灰度像素的比较数组(即图像指纹序列) 15 pixels = getPixelDeviateWeightsArray(pixels, averageColor); 16 // 获取两个图的汉明距离(假设另一个图也已经按上面步骤得到灰度比较数组) 17 int hammingDistance = getHammingDistance(pixels, pixels); 18 // 通过汉明距离计算相似度,取值范围 [0.0, 1.0] 19 double similarity = calSimilarity(hammingDistance); 20 } 21 22 // 将任意Image类型图像转换为BufferedImage类型,方便后续操作 23 public static BufferedImage convertToBufferedFrom(Image srcImage) { 24 BufferedImage bufferedImage = new BufferedImage(srcImage.getWidth(null), 25 srcImage.getHeight(null), BufferedImage.TYPE_INT_ARGB); 26 Graphics2D g = bufferedImage.createGraphics(); 27 g.drawImage(srcImage, null, null); 28 g.dispose(); 29 return bufferedImage; 30 } 31 32 // 转换至灰度图 33 public static BufferedImage toGrayscale(Image image) { 34 BufferedImage sourceBuffered = convertToBufferedFrom(image); 35 ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_GRAY); 36 ColorConvertOp op = new ColorConvertOp(cs, null); 37 BufferedImage grayBuffered = op.filter(sourceBuffered, null); 38 return grayBuffered; 39 } 40 41 // 缩放至32x32像素缩略图 42 public static Image scale(Image image) { 43 image = image.getScaledInstance(32, 32, Image.SCALE_SMOOTH); 44 return image; 45 } 46 47 // 获取像素数组 48 public static int[] getPixels(Image image) { 49 int width = image.getWidth(null); 50 int height = image.getHeight(null); 51 int[] pixels = convertToBufferedFrom(image).getRGB(0, 0, width, height, 52 null, 0, width); 53 return pixels; 54 } 55 56 // 获取灰度图的平均像素颜色值 57 public static int getAverageOfPixelArray(int[] pixels) { 58 Color color; 59 long sumRed = 0; 60 for (int i = 0; i < pixels.length; i++) { 61 color = new Color(pixels[i], true); 62 sumRed += color.getRed(); 63 } 64 int averageRed = (int) (sumRed / pixels.length); 65 return averageRed; 66 } 67 68 // 获取灰度图的像素比较数组(平均值的离差) 69 public static int[] getPixelDeviateWeightsArray(int[] pixels,final int averageColor) { 70 Color color; 71 int[] dest = new int[pixels.length]; 72 for (int i = 0; i < pixels.length; i++) { 73 color = new Color(pixels[i], true); 74 dest[i] = color.getRed() - averageColor > 0 ? 1 : 0; 75 } 76 return dest; 77 } 78 79 // 获取两个缩略图的平均像素比较数组的汉明距离(距离越大差异越大) 80 public static int getHammingDistance(int[] a, int[] b) { 81 int sum = 0; 82 for (int i = 0; i < a.length; i++) { 83 sum += a[i] == b[i] ? 0 : 1; 84 } 85 return sum; 86 } 87 88 // 通过汉明距离计算相似度 89 public static double calSimilarity(int hammingDistance){ 90 int length = 32*32; 91 double similarity = (length - hammingDistance) / (double) length; 92 93 // 使用指数曲线调整相似度结果 94 similarity = java.lang.Math.pow(similarity, 2); 95 return similarity; 96 } |
UI部分的代码就不公开了,成品下载地址如下(使用JDK1.5):
http://download.csdn.net/detail/u011088871/7711833
解压后打开 run.bat 批处理文件就可以跑起来了 :)
需求变更是软件项目一个突出的特点,也是软件项目最为普遍的一个特点。虽然这与人类认识问题的自然规律是一致的,但是频繁而无管理的需求变更非常容易导致复杂、无形的软件在多变的情况下失控,加剧了
软件开发过程中的不稳定性,从而造成多方的损失。那么如何对需求变更加以有效的控制和管理,从而保证软件开发的进度、成本和质量,便成为软件开发过程中一个值得思考的问题。
下面,我们将“需求变更管理”作为一个“项目”,按照问题定义、需求分析、设计、开发等步骤,从软件工程的角度来加以分析,而这样的讨论过程也正符合了我们的开发流程,相信大家会对需求变更管理的认识更加深刻。
问题定义
根据软件工程思想,需求说明书一般要经过需求评审的过程。在需求说明书经过论
证后,需要在原有基础上补充新的需求或对其进行修改和删减,则均属于需求变更。
这是软件开发过程中不可避免的问题,如何有效应对和处理需求变更,已经成为一个广泛受到关注的话题。
需求分析及评价
由于频繁需求变更且同时缺乏有效控制流程而导致软件项目失败的案例不胜枚举,
面临这种不稳定性,如果开发团队缺少明确的需求变更管理控制或者采用的控制机制无效,很可能出现成本增高、人力紧缺、项目拖延甚至是失败,这也就有了进行需求变更管理的需求。
诚然,需求变更管理不可能根本解决问题,但是实施严格的软件需求变更管理能最大限度地控制需求变更带来的负面影响,从而保证项目的可控性和稳定性,这也正是进行需求变更管理的目的所在。
设计
这里的设计指的就是如何来制定需求变更管理的执行计划。主要分以下几个阶段:
获取需求基线:需求的基线是指是否容许需求变更的分界线。需求分析人员在充分与客户用户进行沟通的基础上形成第一个版本的需求文档,这个需求文档在通过需求评审后即可以建立第一个需求基线。此后每次需求变更并经过需求评审后,都要重新确定新的需求基线。变更控制委员会为有效进行需求变更控制,必然要做的
工作就是保存好各个版本的需求基线,维护需求基线文档,以备不时之需。随着项目的进展,基线将越定越高,即容许的需求变更将越来越少。
分析变更影响:对于提交的每项需求变更请求,应确定它对项目整体进度的影响和对其他相关开发任务的影响,并且一定要明确完成这些变更相关任务的工作量。只有经过全面的分析,变更控制委员会才能够做出更好的决策。进行变更影响分析可以对申请的需求变更有更深刻的理解,通过对变更内容的更深刻的理解,才能做出对正在进行的工作的调整的部署。
维护变更记录:记录每个需求变更文档的版本号、日期、所做的变更、原因等,当然应该明确该文档由谁来负责更新。
衡量需求稳定性:变更控制委员会需要对需求变更的整体有良好的把握,通过记录需求基准的数量可以获得宏观需求的变更次数;同时还应该记录一段时间内(如每周、每月)的变更数量,最好按变更的类别来列出详细信息。如果某一需求过于频繁变更,则说明对该问题的认识还不深入或者说还没有达成一致的处理意见;如果需求变更的总体数量过高,则意味着项目范围并未很好地确定下来或是政策变化较大。
使用
需求管理工具:需求变更控制委员会可以采取商业化的需求管理工具,以此来在
数据库中存储不同类型的需求。这些工具提供了对每项需求的属性描述,状态跟踪等,并可以在需求与其它的相关工作产品建立跟踪能力联系链。
开发
这里的开发指的就是如何在项目的开发过程中有效实施需求变更管理。实施需求变更管理需要遵循如下三个阶段的原则:
需求变更的前绪工作:
1.项目的高效进行,需要良好的高质量的需求,同时它也是需求变更的依据。
2.建立以文档形式存在的简单、有效的变更控制流程。
3.建立项目变更管理执行小组及变更控制委员会。委员会成员组成涉及项目的多方人员,至少应包括用户方代表和开发方的决策人员。小组成员可以由负责需求的人员中有经验的需求分析员来担当。
需求变更进行时:
需求变更的流程一定要遵循由变更控制委员会制定的变更控制流程。变更控制一般要经过变更申请、变更评估、委员会决策、委员会回复、实施变更、变更验证六个步骤。
1.变更申请:随着项目的深入,需求变更也是不可避免的。需求分析小组一定要在充分考虑用户需求,项目进度,需求基线的基础上提交变更申请,并提供尽可能详细的说明以供变更控制委员会进行变更评估。
2.变更评估:需要对提出的变更需求进行影响分析,评估变更是否在项目范围内,对项目计划安排和其它需求的影响,需要的工作量等等。
3.委员会决策:根据评估作出决策以确定选择哪些,放弃哪些,并设置实现的优先顺序,制定目标版本。
4.委员会回复:回复包括同意实施变更和拒绝实施变更,并制定相应变更方案或说明拒绝理由。
5.实施变更:维护需求变更文档,包括:日期以及所做的变更、原因、负责人及新的版本号等等。该工作可以由委员会或者责成执行小组来完成。
6.变更验证:充分和提交变更申请人进行沟通,以使其得到满意答复。然后根据变更方案和需求基线,进行相应的需求变更后的工作。该工作可以由执行小组来完成。
需求变更后:
参照需求跟踪能力矩阵找到受需求变更影响的工作产品,并进行一致性变更,同时要维护变更历史记录。所有这些工作也是至关重要的内容,需要慎重细致对待,否则对持续的需求变更来讲,将是一场灾难。
项目结束
一个项目的交付验收,并不意味着项目的真正结束,一个优秀的项目管理人员善于在项
目结束后进行总结。项目总结工作当然要包括那些没有预料到而发生的需求变更,以及这些变更的应对措施。根据实际工作中遇到的需求变更管理的问题,笔者总结如下几点,以供参考和交流:
◆良好气氛下的充分交流 讨论需求及变更需求时,需求人员与客户及用户应该尽量采取协作的态度,良好的工作氛围也会提高工作效率,很难想象双方在“刁难”与“对付”的态度下是一种该有多糟糕的工作场景。确定需求基线的过程也就是与客户用户交流的过程,而频繁大量的需求变更在很大程度上也是交流不充分的后果。所以,有效的充分的交流尤为重要,需求人员认真听取客户用户的要求,进行分析和整理。同时还应该有能力设想项目的开发过程中可能会遇到的由该需求导致的问题,同时要让客户认识到如果此时再提出需求变更,将会给整个项目带来的各种影响和冲击。
◆专职人员负责需求变更管理 在具有相当规模的项目中,专职的需求人员和由此组成的需求变更执行小组是项目稳定、进度良好的保证。没有变更管理而直接由开发人员处理的需求变更将会给项目带来毁灭性的灾难。这些专职人员应该具有专业的需求分析技巧技能,针对用户的变更需求,可以给用户说明利弊,可以按紧迫程度为开发人员提供工作重点,同时, 他们应该还能控制需求变更的频率。
◆明确合同约束,限制需求变更 需求在软件项目中的地位已经越来越重要,需求变更给软件开发带来的影响也是有目共睹,甚至因为质量低下的需求或者频繁无控制的需求变更而导致项目的失败。因此,应该让客户明白需求变更给项目带来的工期、成本等各方面的影响,在互相理解的基础上增加合同条款,比如明确说明客户可以提出需求变更的期限,超过期限的需求变更的具体处理细则(如增加开发费用等,需求变更与开发费用本身也是关联的,这个要求并不过分)。
◆良好的软件结构适应需求变更 优秀的软件体系结构可以快速应对不同情况的需求变
更,这样就可以适当降低需求的基线(当然是在成本影响的允许范围内),从而来提高客户的满意度。适应需求变更必须遵循一些设计原则,如松散耦合、合理的接口定义等,要力求减少会对接口入口参数产生变化。
开发人员对委托方提供的设计资料的理解程度直接影响着开发进度和质量,从过去的产品质量数据分析结果来看,编程错误和对设计资料的理解错误是产生质量问题的两个主要原因。
特别是对设计资料的理解错误如不从一开始就采取措施进行预防,对程序本身及其他程序的质量将可能产生较大的影响。针对这一点,系统开发事业本部大连开发部开 展了以“预防/消除设计资料理解错误”为主题的质量控制(QC)活动。随着活动开展的深入,质量控制逐渐取得了明显的成效。
“预防/消除设计资料理解错误”质量活动经过
1.2003年11月末,各开发项目组提出了各项目质量分析报告;
2.在对质量报告进行分析后,发现设计资料理解错误是质量问题的原因之一;
3.讨论预防和消除设计资料理解错误问题的应对措施;
4.2003年12月26日,召开以“预防/消除设计资料理解错误”为主题的质量活动发表大会;
5.2004年1月以各项目组为单位实施“预防/消除设计资料理解错误”质量控制活动;
6.2004年3月在第八届NEC中国地区质量控制大会上进行了活动汇报;
7.计划于2004年6月,对本次质量管理活动的结果进行总结报告。
设计资料理解错误的原因分析及预防/消除对策
减少设计资料理解错误的建议
1.对于设计者的建议(依赖事项)
(1)明确实现的功能,对处理的条件要充分描述。
(2)充分地对设计书进行审查。
(3)对编程前或编程中发生的问题,由设计人员到编程现场或通过网络会议进行设计及原因说明。
2.对于开发者的建议
(1)加强审查 !
(2)加强确认 !
(3)加强沟通 !
安装Eclipse插件
For Eclipse 3.4 and above, enter http://beust.com/eclipse.
For Eclipse 3.3 and below, enter http://beust.com/eclipse1.
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.homeinns.web</groupId> <artifactId>homeinns-testng</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>homeinns-testng</name> <url>http://maven.apache.org</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>org.testng</groupId> <artifactId>testng</artifactId> <version>6.1.1</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>2.16</version> <configuration> <suiteXmlFiles> <suiteXmlFile>testng.xml</suiteXmlFile> <!-- <suiteXmlFile>src/test/resources/testng.xml</suiteXmlFile> --> </suiteXmlFiles> </configuration> </plugin> </plugins> </build> </project> |
配置TestNg suite
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd"> <suite name="Suite" parallel="none"> <!--enabled="true"让测试生效,也可根据情况关闭某些测试 --> <test name="Test" enabled="true"> <!--指定参数 --> <parameter name="Name" value="Irving" /> <parameter name="Sex" value="Man" /> <!--指定测试包 --> <packages> <package name="com.homeinns.web.testng.*" /> </packages> <!--指定测试类 --> <classes> <class name="com.homeinns.web.testng.AppTest" /> </classes> </test> <!-- Test --> </suite> <!-- Suite --> TestNg注解配置 public class NgTest { @Test public void f() { } @Test( // 在指定的时间内启用3个线程并发测试本方法10次 threadPoolSize = 3, invocationCount = 10, timeOut = 10000, // 等待测试方法t0测试结束后开始本测试 dependsOnMethods = { "f" }, // 指定测试数据源CLASS和数据源名称(参考注解@DataProvider),返回几条数据会跑测试方法几次 dataProvider = "generate", dataProviderClass = GeneratorRandomNum.class, // 分组名称 groups = { "checkin-test" }) // 读取配置文件中的参数,配置如上,用@Optional设置默认值 @Parameters({ "Name" }) public void f1(@Optional("name") String name) { } } |
测试报告
运行测试后 在my-testng/test-output/ 目录下(maven \target\surefire-reports)
gradle配置
subprojects { apply plugin: 'java' // Disable the test report for the individual test task test { reports.html.enabled = false } } task testReport(type: TestReport) { destinationDir = file("$buildDir/reports/allTests") //Include the results from the `test` task in all subprojects reportOn subprojects*.test }Grouping TestNG tests test { useTestNG { excludeGroups 'integrationTests' includeGroups 'unitTests' } } |