一、数据统计方面
1、报表统计数据的正确性
1)数据的正确
a)数据的来源:来源于哪张表,哪个字段,
数据库中的数值与界面上的一致
b)数据的范围:是否只显示了报表设置的对应范围,如:时间选择2012.11.01-
2012.11.19,那么是否应该包含1和19这天
c)数据的对应关系:数据库中的字段是否与报表中的一致
d)数据的格式:小数位、千位符,四舍五入等是否正确;单位或税率转换是否正确;
组合显示的数据是否合理
f)数据排序是否正确
g)流水号:如果报表使用流水号,流水号的生成和格式是否正确
h)明细与合计的一致性:各部分明细或小节是否与最后总和一致
2)格式正确
a)报表的整体风格
b)报表标题:报表的标题是否是正确的报表名
c)公司的一些标志:如logo,名称,地址之类的是否正确
d)报表的页首与页尾:是否采用了一致的规则
e)分页:当输出的内容多时,分页是否正确,翻页功能是否正确
f)友好性:数据或图表是否清晰,一目了然,数据的展示是否符合用户的习惯;需要提
醒的是数据(如合计,异常数据)是否突出显示;复杂算法处、用户不明白或容易混
淆处是否有注释;一些默认的格式是否让人感觉舒服,如对齐、边界、间隔等
3)权限的控制
a) 报表内容:报表中的内容不能显示用户根本没有权限的数据
b)报表的条件定义:在条件选择区域,有些下拉框中不能显示权限以外的数据
2、报表统计数据的完整性
3、报表统计数据的合法性;比如:统计金额字段需求要求有‘$’等
二、报表格式
1、表头字段表示的正确性
2、表头字段表示的完整性
3、表头字段表示的字体、字号,美观程度
4、各统计字段的显示是否满足需求;比如:数据过长时要折行还是缩小
5、页眉和页角的表示
三、报表输出界面
1、报表排列方式可调
2、报表标题明确,不能含糊误导用户
四、报表打印、预览、导出
昨天,一兄弟电话求助,有一套医院HIS数据库无法启动,RAC环境,无备份,尝试过重建控制文件操作,但失败。
远程连接后,情况如下:
2.recover database时提示需使用backup controlfile
3.rman list backupset无输出
由于没有记录具体的操作,这里主要就碰到的异常做一下描述
1.重启
数据库,在日志中发现另一节点没有被关闭,首先关闭另一节点
2.由于控制文件已被错误重建,并且,沟通后认为丢失一部分数据是可以接受的,因此,决定重新创建控制文件,在创建控制文件时,存在一个知识点:
RAC环境重建控制文件报错:
ORA-12720: operation requires database is in EXCLUSIVE mode
这里需要修改参数文件:
alter system set cluster_database=false scope=spfile;
3.重建控制文件后进行恢复操作
增加隐含参数:
alter system set "_allow_resetlogs_corruption"=true scope=spfile;
recover database using backup controlfile until cancel;
alter database open resetlogs;
报错
ORA-00600: internal error code, arguments: [2662]
这个错误以前碰到过,需要使用event调整SCN
首先,再增加一个隐含参数:
alter system set "_allow_error_simulation"=true scope=spfile;
重启重建控制文件并恢复后
alter session set events '10015 trace name ADJUST_SCN level 10';
alter database open;
4.经过上一步操作,SCN已经OK了,但仍然报错
ORA-00600: internal error code, arguments: [4194]
4000开头的错误,改UNDO为手动管理模式,修改初始化参数
undo_management='MANUAL'
完成启动。
总结:
1.这个例子,其实最开始控制文件没有丢失,应该先尝试进行恢复,不知道之前是不是遇到了错误,导致选择了重建控制文件的方法。不过,这里应该指出的是,最好不要一上来就重建控制文件。
2.备份!备份!备份!
感觉恢复数据库,就像伤寒论里面说的,观其脉证,知犯何逆,随证治之。
IOS的推送实现由这样几步来完成:
创建Push SSL Certification
IOS客户端注册Push功能并获得DeviceToken
使用Provider向APNS发送Push消息
IOS客户端接收处理由APNS发来的消息
创建Push SSL Certification
登录developer.apple.com,创建新的App ID,要求此ID的Bundle Identifier不包含通配符,否则不能启用Push以及IAP功能。例如 com.soso.sosoimage。
在App IDs列表页面,点击刚创建的app id右面的Configure链接,进入Configure App ID界面,选中"Enable for App Push Notification service"。点击Development Push SSL Certificate一行的Configure按钮,弹出"Apple Push Notification service SSL Certificate Assistant"对话框,依对话框操作,类似于创建开发或发布用的Certificate。
最终将Development Push SSL Certificate下载并安装到本地Keychain Access。导出成p12文件,备用。导出时需要设置密码,不得为空。
在developer.apple.com,创建一个新的Provisioning Profile,使用我们刚刚创建的支持Push功能的App ID。下载并安装到本地。
IOS客户端注册Push功能并获得DeviceToken
创建本地工程,info.plist中设置Bundle identifier为刚刚创建的Bundle Id。Com.soso.sosoimage。设定Code Signing Identity为刚刚创建的Provisioning Profile。
程序第一次执行的时候,调用如下代码.
[[UIApplication sharedApplication] registerForRemoteNotificationTypes:(UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound)];
三个参数分别代表消息(横幅或提醒,由用户Setting决定,程序不可更改)、数字标记、声音。
在AppDelegate.m中添加两个方法.
//iPhone 从APNs服务器获取deviceToken后回调此方法 - (void)application:(UIApplication *)app didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { NSString* dt = [[deviceToken description] stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"<>"]]; NSLog(@"deviceToken:%@", dt); } //注册push功能失败 后 返回错误信息,执行相应的处理 - (void)application:(UIApplication *)app didFailToRegisterForRemoteNotificationsWithError:(NSError *)err { NSLog(@"Push Register Error:%@", err.description); } |
获取DeviceToken后,将其传给Provider。
使用Provider向APNS发送Push消息
Provider,将推送信息发送给APNS(
苹果推送服务器)的程序。有很多开源的实现,我们使用javapns ( http://code.google.com/p/javapns/ )。
首先,Provider要有目标DeviceToken,这是发送目标,由客户端传给Provider之后存在某处。
安装javapns,需要导入的jar为bcprov-jdk15-146.jar, log4j-1.2.15.jar, JavaPNS_2.3_Alpha_5.jar。
将前面导出的P12文件放在Provider的工程目录下。
Provider向APNS发送消息可以参考javapns中NotificationTest.java。也可以参考如下例子。
(1)使客户端图标显示数字标记
Push.badge(2, keystore, password, false, "7bb8d508e32df651c6c239439737dbd40a88d2461ad2ac1e5dbe49ecea5ccc67");
其中,2为要显示的数字;
String keystore = "PushCertificates.p12"; //P12文件的路径;
String password = "sosoimage"; //P12文件的密码;
false,指的是使用测试环境,使用正式产品环境应传入true.
"7bb8d508e32df651c6c239439737dbd40a88d2461ad2ac1e5dbe49ecea5ccc67"为客户端获得并传给Provider的DeviceToken,此参数还可以传入String[]对象,以同时向多个客户端Push消息。
(2)使客户端显示横幅或提醒
Provider可以向客户端Push一条Message,但客户端有权限决定这条Message的显示方式(无、横幅、提醒)。
Push.alert("A Message", keystore, password, )false, "7bb8d508e32df651c6c239439737dbd40a88d2461ad2ac1e5dbe49ecea5ccc67");
(3)混合方式
可以在一个Push消息里附带多种信息,Message, 标记,声音,可以使用如下代码.
PushNotificationPayload payload = PushNotificationPayload.complex();
payload.addAlert("A Message");
payload.addBadge(2);
payload.addSound("test.aiff");
Push.payload(payload, , keystore, password, false, "7bb8d508e32df651c6c239439737dbd40a88d2461ad2ac1e5dbe49ecea5ccc67");
上面的代码都有可能会有相应的Exception抛出来,需要处理。更多的使用方式可以参考 http://code.google.com/p/javapns/
IOS客户端接收处理由APNS发来的消息
(1)当程序未启动,用户接收到消息。需要在AppDelegate中的didFinishLaunchingWithOptions得到消息内容。代码如下,
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { ... NSDictionary* payload = [launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey]; if (payload) { ... } ... } |
(2)当程序在前台运行,接收到消息不会有消息提示(提示框或横幅)。当程序运行在后台,接收到消息会有消息提示,点击消息后进入程序,AppDelegate的didReceiveRemoteNotification函数会被调用(需要自己重写),消息做为此函数的参数传入,代码如下
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)payload
{
...
}
(3)无论在哪个函数传入,消息总是一个NSDictionary对象,处理方式可以参考如下代码
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)payload { NSLog(@"remote notification: %@",[payload description]); NSString* alertStr = nil; NSDictionary *apsInfo = [payload objectForKey:@"aps"]; NSObject *alert = [apsInfo objectForKey:@"alert"]; if ([alert isKindOfClass:[NSString class]]) { alertStr = (NSString*)alert; } else if ([alert isKindOfClass:[NSDictionary class]]) { NSDictionary* alertDict = (NSDictionary*)alert; alertStr = [alertDict objectForKey:@"body"]; } application.applicationIconBadgeNumber = [[apsInfo objectForKey:@"badge"] integerValue]; if ([application applicationState] == UIApplicationStateActive && alertStr != nil) { UIAlertView* alertView = [[UIAlertView alloc] initWithTitle:@"Pushed Message" message:alertStr delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil]; [alertView show]; } } |
整个javascript代码共42行,其中主要函数Slide代码26行,可以改进哦!
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>滑动菜单</title> <style> a,body,div,h1,h2,li,ul{ margin:0; padding:0 } a{display:block;text-decoration:none;height:25px;color:#456;background-color:#ABC;cursor:pointer;} a:hover{color:#123;background-color:#789;font-weight:bold;} body{ font:12px/18px "Times New Roman",Times,"宋体",serif; text-align:center; } h1{ height:100px; width:25px; position:absolute; top:-1px; left:123px; cursor:pointer; color:#89A; font-size:18px; line-height:50px; background-color:#234; } h2{ height:24px; font-size:12px; border-bottom:1px solid #4C6CB9; color:#BBB; font-weight:600; cursor:pointer; } li{height:25px;list-style:none} #Container{width:800px;background-color:#DDD;border: 1px solid #999;margin:10px auto} #Top{height:30px;background-color:#DDD;border: 1px solid #999;} #Logo{height:100px;background-color:#DDD;border: 1px solid #999;} #Nav{height:30px;background-color:#DDD;border: 1px solid #999;} #SideBar{ position:fixed!important; position:absolute; top:200px; left:0px; } #SideBar a,#SideBar h2,#SideBar li,#SideBar ul{width:120px} #Main{height:800px;background-color:#DDD;border: 1px solid #999;} #Footer{height:60px;background-color:#DDD;border: 1px solid #999;} .Extracted{width:0px;} .Extrended{border:1px solid #555;background-color:#000;padding:1px} </style> </head> <body> <div id="Container"> <div id="Top">顶条 <div id="Logo">Logo <div id="Nav">导航条 <div id="SideBar" class="Extrended"> <h1>菜单</h1> <ul> <h2>功能栏1</h2> <li><a href="">功能1</li> <li><a href="">功能2</li> <li><a href="">功能3</li> <li><a href="">功能4</li> <li><a href="">功能5</li> </ul> <ul> <h2>功能栏2</h2> <li><a href="www.huiyi8.com">功能1</li> <li><a href="www.enterdesk.com">功能2</li> <li><a href="www.bizhizu.cn">功能3</li> <li><a href="www.679.com">功能4</li> <li><a href="www.enterdesk.org">功能5</li> </ul> <ul> <h2>功能栏3</h2> <li><a href="">功能1</li> <li><a href="">功能2</li> <li><a href="">功能3</li> <li><a href="">功能4</li> <li><a href="">功能5</li> </ul> <ul> <h2>功能栏4</h2> <li><a href="">功能1</li> <li><a href="">功能2</li> <li><a href="">功能3</li> <li><a href="">功能4</li> <li><a href="">功能5</li> </ul> <ul> <h2>功能栏5</h2> <li><a href="">功能1</li> <li><a href="">功能2</li> <li><a href="">功能3</li> <li><a href="">功能4</li> <li><a href="">功能5</li> </ul> <ul> <h2>功能栏6</h2> <li><a href="">功能1</li> <li><a href="">功能2</li> <li><a href="">功能3</li> <li><a href="">功能4</li> <li><a href="">功能5</li> </ul><div id="Main">内容区<div id="Footer">底条<script type="text/javascript"> function $(e){return document.getElementById(e)} function Slide(Element,Mod){ var LongthMax,LongthMin,Property,Count=25,Accum,ID,Dlt,DltDlt; if(Mod){ Property='left'; LongthMax=0; LongthMin=-124; } else{ Property='height'; LongthMax=Element.children.length*25; LongthMin=25; } DltDlt=(LongthMax-LongthMin)/(Count*5); if(Element.style[Property]==LongthMax+'px')DltDlt=-DltDlt; Accum=parseInt(Element.style[Property]); Dlt=7*DltDlt; ID=setInterval(function(){ if(Count--){ if(!(Count%5))Dlt-=DltDlt; Accum+=Dlt; Element.style[Property]=Math.floor(Accum)+'px'; return } clearInterval(ID); Element.style[Property]=(DltDlt>0)? LongthMax+'px':LongthMin+'px'; },20); } $('SideBar').style.left='0px'; $('SideBar').children[0].onclick=function(){ Slide(this.parentNode,1); }; (function(Menu,i,tmp){ Menu=document.getElementsByTagName('ul'); for(i=Menu.length;i--;){ tmp=Menu[i]; tmp.style.overflow='hidden'; tmp.style.height='25px'; tmp.children[0].onclick=function(){ Slide(this.parentNode,0); }; } }()); </script> </body> </html> |
一:简介
Skipfish是一个积极的Web应用程序的安全性侦察工具。 它准备了一个互动为目标的网站的站点地图进行一个递归爬网和基于字典的探头。 然后,将得到的地图是带注释的与许多活性(但希望非破坏性的)安全检查的输出。 最终报告工具生成的是,作为一个专业的网络应用程序安全评估的基础。
二:部署安装
2.1:部署环境
[root@cn-ptmind skipfish-read-only]# uname -a
Linux cn-ptmind 2.6.32-220.el6.x86_64 #1 SMP Tue Dec 6 19:48:22 GMT 2011 x86_64 x86_64 x86_64 GNU/Linux
[root@cn-ptmind skipfish-read-only]# more /etc/redhat-release
CentOS release 6.2 (Final)
2.2:安装skipfish依赖包:
yum -y install gcc gcc-c++ autoconf libjpeg libjpeg-devel libpng libpng-devel freetype freetype-devel libxml2 libxml2-devel zlib zlib-devel glibc glibc-devel glib2 glib2-devel bzip2 bzip2-devel zip unzip ncurses ncurses-devel curl curl-devel e2fsprogs e2fsprogs-devel krb5-devel libidn libidn-devel openssl openssh openssl-devel nss_ldap openldap openldap-devel openldap-clients openldap-servers libxslt-devel libevent-devel ntp libtool-ltdl bison libtool vim-enhanced python wget lsof iptraf strace lrzsz kernel-devel kernel-headers pam-devel Tcl/Tk cmake ncurses-devel bison setuptool popt-devel rsynx openssh system-config-network-tui svn
2.3:下载skipfish
svn checkout http://skipfish.googlecode.com/svn/trunk/ skipfish-read-only
#进入目录
cd skipfish-read-only
#执行make 编译,生成skipfish 命令。
make
三:网站扫面实例
./skipfish -o ptengine http://www.ptengine.com
# -o 选项制定生成扫描报告文件夹
#将报告文件夹进行打包
tar -cvf ptengine.tar ptengine
#将压缩包下载到本地
sz -bey ptengine.tar
扫描报告下载到本地PC后并解压,打开文件夹,找到index.html文件,用浏览器打开,即可查看扫描报告。
如果同一个脚本运行两次结果相差特别大.后一次的响应时间比前一次的响应时间慢了特别多,而期间开发又未修改过程序排查问题:
1.查看下linux内存利用情况 free -m 是查看内存情况 (通常可用内存占10%是可接受范围内) 如果可用内存很小的话 :一种可能是程序有问题 :另一种可能是磁盘空间不足了 当磁盘空间不足就会去写内存
2.可以先看下第二种情况成立否 : 命令 tf -m 查看磁盘空间情况结果看到磁盘空间为0了
3.查看哪个进程占用了这么多 ,最好通过log日志进行分析。在运行
性能测试脚本前一定要确认好log日志的级别,如果设置debug,程序就会不断的写日志,影响性能测试结果 通常都是设置成error级别 当然如果再运行脚本过程中 两次运行结果不可能完全一样.通常相差时间要小于100ms是可接受的
当启动chrome作selenium测试时,如果没有设置driver,则会出错.
driver下载地址https://code.google.com/p/chromedriver/downloads/list
三种方式
1.命令行中带参数-Dwebdriver.chrome.driver
java -Dwebdriver.chrome.driver=chromedriver_win_22_0_1203_0b.exe org.junit.runner.JUnitCore seleniumtest_name
2.命令行中可以没有-Dwebdriver.chrome.driver,但是需要在环境变量path中添加
chromedriver.exe(注意此处文件名只能是chromedriver.exe)的路径.
例如chromedriver.exe的放在d:/中, 则把"d:/"添加到path变量中
此时命令简化为:
java org.junit.runner.JUnitCore seleniumtest_name
3.可以在.java源文件中显示设置chromedriver
System.setProperty("webdriver.chrome.driver","d:/chromedriver_win_22_0_1203_0b.exe")
前几天安装了一个bugfree,但是
测试人员在用的时候发现保存bug的时候直接报500了,然后我看了下apache的错误日志,内容是:
[Tue Aug 12 14:42:34 2014] [error] [client 192.168.30.67] PHP Fatal error: Call to undefined function mb_detect_encoding() in /var/www/html/bugfree/protected/extensions/simple_html_dom.php on line 988, referer: http://192.168.30.30/bugfree/index.php/info/edit?type=result&action=opened&case_id=1 |
经过发现原来是少安装了两个php模块:
php-mcrypt 和 php-mbstring
于是乎安装吧:
yum -y install php-mbstring
yum -y install php-mcrypt
安装好后重启apache,OK,好了
今天跟大家聊一个老生常谈的话题:浏览器
兼容性测试。
测试国内网站的同学是不是已经非常头疼了,因为面对的浏览器除了国际大牌如IE, Chrome, Firefox, Opera, Safari, 还有国内诸多屌丝级的浏览器如:
360安全浏览器,360极速浏览器,360影视浏览器(360碉堡了,会不会再搞出一些购物浏览器,买火车票浏览器啊?)搜狗双核浏览器,猎豹,淘宝,世界之窗,
百度浏览器,还没完呢,听说过枫树浏览器吗?没听过的赶紧去科普科普吧!另外我今天还被不知不觉安装了云帆影视浏览器。再想想这些浏览器有多少个版本?还有运行在
Windows和MAC系统上的浏览器也是有些差异的。曾经在兰亭时就出现过在MAC/Chrome上有Windows/Chrome上没有的bug。这么多要覆盖的浏览器你若没晕说明身体和心理素质好,反正我是晕了!
可是我们需要考虑浏览器,版本,系统三个因子去组合吗?答案当然是否定的。
一般针对普通用户的网站都会嵌入GA统计代码,现在google被和谐了,可以换成百度统计,但是它们都可以跟踪到用户行为,其中一项就是浏览器访问占比,清清楚楚告诉你每个浏览器占比,每个版本占比,所谓好钢用在刀刃上,有个这项数据后,就应该清楚的知道我们的
工作重点在哪里,根据二八原则,我们也需要投入大部分精力在占比排名在80%前的上面,投入少量精力在占比比较小的浏览器上,我们之前的经验能保证正常功能能使用,一些小样式就可以容忍了,这种情况多是IE6,7,不过我一直都没有想明白,对于IE6,国外发达国家都已经绝迹了,为啥国人不能使用一些更高级的浏览器呢?回到正题,其实如果发布时间紧急,实在没有时间来做这么多浏览器覆盖的时候,就再分析一下国内诸多浏览器使用的内核以及他们的区别是什么, 如果是Chrome的内核,也许可以跟Chrome一起来测试,如果是IE的话,就跟IE一起来测试,当然不同的浏览器在渲染原理上必然还是有一些差异的,不过为了赶工,还是要做取舍的。对于IE的话,的确有必要再唠叨几句,基本在IE8及以后版本,基本都是标准模式,IE6,7的怪癖模式需要格外注意,如果你的网站在这两个版本上占比还不小的话,的确需要好好测试下这两个版本,IE9及之后的版本以及较新的Chrome,Firefox基本上都不会出问题。
而对于企业级用户的网站或者有专人在维护用户的平台,就好办多了,我们可以保证几个重要的浏览器重要版本的兼容性,然后让公司的运营或者销售人员与用户做好沟通,对哪几个浏览器支持比较好,建议他们使用我们支持的浏览器,因为有沟通渠道,是不是就好很多了。
测试国外用户特别是欧美的网站,就大有福气了,他们早就不用IE6,7了,而且国外的本土浏览器也并没有像国内这么大放异彩,需要覆盖的就少很多了。就像以前在兰亭的时候,根据GA统计数据,用户使用量大的基本都是比较新的浏览器版本,而且Chrome,Firefox占比比较大。
对于浏览器兼容性测试,你是不是想问有没有什么工具来帮助我们做?是有,不过像IETester也只是支持IE,而且需要人工检查兼容性问题, BrowserShots也只是做线上测试工具,并不支持测试环境。不过我之前用过一个Chrome的插件叫浏览器兼容性检测工具,可以在内网测试,会自动监测网页的设计是否满足对应浏览器及版本的规范,不满足的话就会详细提示出来,不过有些过于专业性了,更适合开发人员查看,测试人员可以推荐给开发人员使用,这样在开发阶段就规避掉一些兼容性问题岂不是更好。
我们搞开发的往往觉得自己写的代码没问题,用不着
测试,以前,我也这么认为,觉得测试浪费时间,也就没仔细研究过测试。
最近,闲来想试试
单元测试,结合之前的编程经验,发现,单元测试至少是保证软件质量的最佳方式之一。一波一波程序员开发、维护一个产品,程序员之间的差别太大了,就像“明显没有错误”和“没有明显错误”的区别,怎么来保证产品在不断迭代中的质量,保留里面正确的部分,去掉bug呢?架构设计里面讲究面向接口,单元测试就能起到接口的作用。
通过单元测试的类,它的行为是符合当初单元设计的目标的。只要编写单元测试时,从多方面检验类的行为,就能确保在这样的情景下,类是符合设计的。在Vistual Studio中,最简单的单元测试就是使用本身自带的功能(不需要从网上找NUnit的程序集,直接在项目上引用"Microsoft.VisualStudio.QualityTools.UnitTestFramework"程序集就ok了)。还有另外一个好处,是方便调试。单元测试项目可以在测试运行时,对被测试类下断点,非常节约调试时间。
我是这么做的,单元测试的代码放到独立的项目中去,引用要测试的项目,在被测试项目中添加Assembly特性如下:
[assembly: InternalsVisibleTo("Projky.UnitTests")]
这样,单元测试就能对被测试项目中internal修饰符的类可见,又不破坏程序集的可见性。
举一个简单的需求,要将如“30d9132169211a45”或者“30-D9-13-21-69-21-1A-45”或者“30 D9 13 21 69 21 1A 45”这样的16进制字符串转换为Byte[]数组。设计了一个ByteString的类来实现需求。
internal class ByteString { public static Byte[] ConverterToBytes(string value) { if (value.IndexOf("-") > -1) { value = value.Replace("-", ""); } else if (value.IndexOf(" ") > -1) { value = value.Replace(" ", ""); } Debug.Assert(value.Length % 2 == 0, "Invalid byte string length."); List<Byte> list = new List<Byte>(value.Length / 2); for (int i = 0; i < value.Length; i += 2) { int bHi = GetInteger(value[i]); int bLow = GetInteger(value[i + 1]); Byte temp = (Byte)(bHi << 4 | bLow); list.Add(temp); } return list.ToArray(); } static int GetInteger(char ch) { if (Char.IsDigit(ch)) { return ch - '0'; } int value = 10; switch (ch) { case 'a': case 'A': value = 10; break; case 'b': case 'B': value = 11; break; case 'c': case 'C': value = 12; break; case 'd': case 'D': value = 13; break; case 'e': case 'E': value = 14; break; case 'f': case 'F': value = 15; break; default: throw new NotSupportedException(ch.ToString() + " is not valid char."); } return value; } } |
那么,简单验证该类的行为(接口)可以编写下面的测试类:
[TestClass] public class ByteStringTest { [TestMethod] public void ConverterToBytes() { string input = "30d9132169211a45"; Byte[] bytes = ByteString.ConverterToBytes(input); Assert.IsTrue(bytes.Length == input.Length / 2); Assert.IsTrue(bytes[0] == 0x30); Assert.IsTrue(bytes[1] == 0xd9); Assert.IsTrue(bytes[2] == 0x13); Assert.IsTrue(bytes[3] == 0x21); Assert.IsTrue(bytes[4] == 0x69); Assert.IsTrue(bytes[5] == 0x21); Assert.IsTrue(bytes[6] == 0x1a); Assert.IsTrue(bytes[7] == 0x45); input = "30-D9-13-21-69-21-1A-45"; bytes = ByteString.ConverterToBytes(input); Assert.IsTrue(bytes.Length == 8); Assert.IsTrue(bytes[0] == 0x30); Assert.IsTrue(bytes[1] == 0xd9); Assert.IsTrue(bytes[2] == 0x13); Assert.IsTrue(bytes[3] == 0x21); Assert.IsTrue(bytes[4] == 0x69); Assert.IsTrue(bytes[5] == 0x21); Assert.IsTrue(bytes[6] == 0x1a); Assert.IsTrue(bytes[7] == 0x45); input = "30 D9 13 21 69 21 1A 45"; bytes = ByteString.ConverterToBytes(input); Assert.IsTrue(bytes.Length == 8); Assert.IsTrue(bytes[0] == 0x30); Assert.IsTrue(bytes[1] == 0xd9); Assert.IsTrue(bytes[2] == 0x13); Assert.IsTrue(bytes[3] == 0x21); Assert.IsTrue(bytes[4] == 0x69); Assert.IsTrue(bytes[5] == 0x21); Assert.IsTrue(bytes[6] == 0x1a); Assert.IsTrue(bytes[7] == 0x45); } } |
如果单元测试运行失败,例如Assert.IsTrue()方法中,参数为False,则在VS中会抛异常,这样,就知道哪里不正确了。
注意用[TestClass]标记作为单元测试承载的类,是public可见性,而里面单个的独立测试方法,则采用[TestMethod]标记,同样是public可见性。
在Visual Studio里面还有一个技巧,按Ctrl + R,A可以自动运行单元测试项目。如果被测试类有断点的话,测试到该位置时,也会断下来。