一、數據統計方面
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可以自動運行單元測試項目。如果被測試類有斷點的話,測試到該位置時,也會斷下來。