所有的
編程語言我都討厭。曾經我想自創一門語言,但我沒搞明白到底需要一門什么語言,所以也從未開始過。 許多時候,你沒法選擇使用哪種語言。不管我在用哪種語言,我都嘗試去接受它的優點和缺點。
Java
喜歡Java的人肯定喜歡打字。我指的就是敲打鍵盤上的鍵。你得不斷地重復又重復。
設計Java系統的人是個瘋子,他解決問題的方式就是,設計模式。如果你把設計模式看作是這個語言中解決問題的一種方式,那么你會發現Java里有許多這樣的設計模式。
另一方面,Sun的這些家伙的確是費了點心思在Java規范上的,這使得它能運行在嵌入式系統上,所以這塊我們還是堅持在使用它。我很難相信
Python或者C在我的
手機桌面系統上運行。
還有,那些個目錄又是怎么回事?我必須得使用Eclipse,因為只有它知道怎么跳過那1000個字長的路徑名。如果我在應用的同一個目錄下放10個類,會不會 傷害到某些人?
C
C是精確的。當我用C寫程序的時候,如果搞定了,我知道它是靠譜的。它就像是用一把小刷子在畫一幅巨作。在這么詳細的層面上寫代碼需要一種不同的心態。當你坐下來寫C的時候,在動手之前你就得規劃好到底怎么寫。否則后面肯定得費很多工夫去改。
如果你的經驗足夠豐富,內存泄露這種事就不太會找上門。它的第二特性——malloc/free總是形影不離。你不能忘了任何一個。否則就像是忘了沖水或者關燈。你就這么做就是了。
有句話說得好,如果你打算給房子上漆,一把好刷子可遠遠不夠。我猜你肯定想要個大滾軸。如果讓我寫一整個應用或者系統,能不用C的話我肯定不用。
C程序想要進行改動可得費老勁了。當我寫算法的時候,我知道第一遍肯定是不會對的,所以我通常都先用Python寫,搞定了之后再翻譯成C的。
C++
它就是個有string類的C。同時還有數組,列表,隊列等東西,你可以用它們來實現你想要的。一言以蔽之:別想著自創新模板。這太困難了。除了這個,C++還改良了一下C,用C++你可以寫出非常不錯的軟件。它這個額外的特性使得它可以用于一些大型系統上,只要大家都還遵循同樣的約束的話,難度還不算太大。
JavaScript
這是個沒人喜歡的語言。不過它喜歡你。當你剛開始
學習它的時候,你可能會寫出一些非常糟糕的代碼,把對象用作字典,別的對象作KEY,不過這樣也是OK的,因為這些代碼運行起來也沒有什么問題,只要瀏覽器還支持JavaScript就好。
JavaScript沒有連接器,因此所有的代碼都共享一個命名空間,不過還好大家都知道這一點,所以還能一起和諧相處。
CoffeeScirpt
CoffeeScirpt是一個解釋器,它將那些長得像
Ruby的奇怪的語言逐行地翻譯成JavaScript。它是一個擁有所有外來語法的JavaScript——括號,方括號,額外關鍵字移除。只有代碼的基本含義還保留著。
CoffeeScirpt挺不錯的。如果你要寫很多代碼的時候,它能讓你提高至少25%的效率。你可以一次在屏幕上看到更多行的代碼。
當你用CoffeeScript寫代碼的時候,你得時刻記住這是要生成JavaScript的。問題就在這。你得先去學習JavaScript。項目來的新人都得先學JavaScript,然后才能學CoffeeScript,最后才能去學習項目代碼。
node.js
我也希望能愛上它。我覺得我給過它機會了。它的回調讓我無法忍受。我知道會有這么一天,因為某個原因,其中一個回調并沒有出現,然后我的應用就會堵在那一直等待。真是要了命了。
還有一點就是,它幾乎沒有內建任何東西。如果你要做某件事情,總是會有一大堆模塊來實現這個功能的。該選哪個呢?如果出現問題了,哪個模塊會有人來支持?
Scala
Scala是一門函數式,強類型的語言,它會編譯成JVM代碼。
我是在
工作中學的Scala。有一家初創公司的生產系統用的是它,我是在后期才加入他們的。
這讓我看到了Scala丑陋的一面:類型推導。類型推薦被它用到了極致。每個對象都有類型,不過想確定它是什么類型的,你得檢查不同分層上的好幾個文件才行。Scala也繼承了Java的文件夾的壞毛病,因此你要查找某個類型的話得進入好幾層目錄才能找到對應的那個文件。
簡而言之,Scala是極好的——對于那些最初的開發人員而言。新加入的成員為了熟悉現有的代碼,得有一個很長的學習曲線。
Erlang
Erlang也是我曾經想愛上的一位。我真的努力了。它是一門美麗的函數式語言,它可以寫出很精致的小模塊,它們以一種精確的方式進行通信,你的系統可以運行10年以上,因為它能處理未知問題,如果必要的話還會重啟,然后繼續運行。
不過它的結構太復雜了。開發似乎要停留在伯克利發明socket的那個年代。當前時代所需的東西幾乎一樣都沒有。為什么開發一個簡單的WEB服務需要費這么大的工夫?
Go
Go很容易學習,對于新人而言也是如此。它使用40年前的語言概念來構建一個健壯的異步系統,但它讓你能像寫同步代碼一樣編程。你可以不費吹灰之力寫出1000個可以安全工作的線程。
在庫支持方面它仍需要改進。當我想做某事的時候,該用哪個庫——github上2011年的那個,還是2013年開始的那個半成品?一個是官方主頁鏈接的,不過它的官方主頁看起來并不是最新的。好吧,我覺得我還是自己寫一個吧。。。
還有,為什么追加元素到數組里也這么費勁?
Python
在Python里,不管你想做什么都會有一個對應的庫,如果你用的是Linux,它絕對是不二選擇,因為它可以一鍵安裝。
如果你想做些數字處理或者科學運算,選擇Python吧,你值得擁有。
Python中的字符串即可能是文本的也可能是二進制的,因此你得上來就學習下文本編碼的東東。
Python 3
Python 3和Python有許多共同的特性,不過它卻是門不同的語言。由于它比較新,因此支持的并不是很好。我也想使用它,不過總會有那么一個庫,它是只支持Python 2的。
雖然很早之前就成功裝過這兩個軟件了。但是前陣子重裝了系統再裝這兩個軟件時卻發現我又把破解的方法給忘了。后來從歷史文檔中搜索了好久才得到解決。想想這些還是需要總結,事情多了,難免忘記。也分享給需要的童鞋們。
LR11的破解方法
1)退出程序,把下載文件中的lm70.dll和mlr5lprg.dll覆蓋掉..\HP\LoadRunner\bin下的這兩個文件
2)注意,win7的話一定要以管理員身份運行啟動程序,啟動后,點擊 configuration->loadrunner license,此時可能會有兩個許可證信息存在,退出程序,點擊deletelicense.exe文件,來刪除剛才得許可證信息(即時原來沒有lisense最好也運行一下)
3)再次打開程序, configuration->loadrunner license->new license,在彈出的輸入框中輸入license序列號AEABEXFR-YTIEKEKJJMFKEKEKWBRAUNQJU-KBYGB,點擊確定,驗證通過后,則破解成功!
QTP11的破解方法
1)安裝成功后,手工創建:C:\Program Files\Common Files\Mercury Interactive下,
建立文件夾License Manager(這個很重要,必須要創建)
2)拷貝破解文件mgn-mqt82.exe至第一步創建的目錄下。
3)運行破解文件,提示在C:\Program Files\Common Files\Mercury Interactive下創建了lservrc文件
4)用記事本打開lservrc,拷貝第一個#號前的一大串字符串。運行
QTP,輸入這串字符
5)安裝QTP的
功能測試許可服務器安裝程序(打開qtp11的安裝包里邊的)
6)刪掉SafeNet Sentinel目錄(C:\ProgramData\SafeNet Sentinel)
7)運行\HP\QuickTest Professional\bin中的instdemo.exe
通過一定的工具結合相應的測試方法,對部署的系統應用進行測試,發現系統應用內部存在的代碼邏輯問題及應用部署的機器硬件資源瓶頸問題及應用部署架構存在架構錯誤問題,如:網絡端、客戶端、服務端搭建的架構問題;
負載測試:是一個分析軟件應用程序和支撐架構、模擬真實環境的使用,從而來確定能夠接收的性能過;
壓力測試(Stress Testing):是通過確定一個系統的瓶頸或者不能接收的性能點,來獲得系統能提供的最大服務級別的測試;
性能測試的目的:
性能測試的目的主要體現在三個方面:以真實的業務為依據,選擇有代表性的、關鍵的業務操作設計測試案例,以評價系統的當前性能;當擴展應用程序的功能或者新的應用程序將要被部署時,負載測試會幫助確定系統是否還能夠處理期望的用戶負載,以預測系統的未來性能;通過模擬成百上千個用戶,重復執行和運行測試,可以確認性能瓶頸并優化和調整應用,目的在于尋找到瓶頸問題;
項目開發周期:初始時刻,項目更多關注的是功能實現,此時
功能測試顯得尤為重要,測試的提前介入,可以提前預測風險,減少項目開發周期、節約開發成本;功能測試后的階段,個人認為應該是性能測試(試想,如果一個項目連功能都實現不了,更何談性能測試);在功能完畢之后,引入性能測試,通過性能測試對開發項目潛在的問題進行排查(功能測試,僅僅是幾個人或者幾十個人簡單的對應用功能的一個測試,對于應用真正上線后的大量用戶使用,應用存在的潛在風險,并不能做很好的預估,尤其是當前空前的競爭壓力下,應用上線后的失敗,很可能導致整個項目的失敗;例如:12306訂票網站,使用量之大,可能全世界前所未有,調動全國人力去測試應用性能問題,肯定是不可能的。如果事先不經過性能測試,貿然上線,在如此之多的用戶使用情況下,系統崩潰將是怎樣的一種后果。);
案例分享:編者曾經從事過一個項目,伴隨項目的始終。前期階段,由于測試提前介入,以及項目開發采用的
敏捷開發方式,項目很快在不到半年的時間內,功能近乎完美完成。項目經理本著穩妥起見,引入性能測試,對項目潛在的風險進行評估,然后就搭建了一套模擬環境,專用于性能測試,搭建的模擬環境30用戶并發運行,項目一點問題沒有,進一步提升并發用戶數,各種問題接踵而來;經過系統調優后(發布的應用系統參數等),部分問題解決;為了進一步測試實際情況下存在問題,性能測試環境由模擬環境切到了生產環境上,此時是大量用戶下的并發,部分業務是沒有問題的,但是更多的問題是集中在涉及到
工作流的一些業務場景上,后臺日志各種報錯;通過抓取后臺日志,對問題進行定位分析,很快排查解決了代碼開發中存在的一些邏輯問題;代碼修復后重新上線,問題已基本不存在了;項目也很快結束,大大的縮短了項目開發周期、節約了開發成功、更好的適用于用戶;
性能測試注意點:
錄制腳本盡量模擬實際用戶操作,在場景設計時,盡量與實際場景一致,對于用戶使用比較多的業務,應著重關注;
性能測試盡可能在實際生產環境上進行,普通模擬環境并不能真正發現實際生產環境下,應用存在的問題,但是并非棄用模擬環境;
性能測試,對于應用系統部署的環境上,可能需要部署一些系統性能監控軟件,在軟件的選取上,盡可能降低軟件自身運行對系統性能的影響;
性能測試,特別是應用與
數據庫交互的業務操作上,需要提前預制符合性能測試業務需求的數據,在此基礎上,盡量讓環境測試環境可多次重復使用,這就要求數據、應用可還原;
性能測試技能掌握要求:
測試環境搭建,環境搭建不僅僅是性能測試所需要具備的技能,也是測試人員所需要具備的基本技能;很多測試,包括應用的安裝卸載,都需要測試人員具備這一技能;
應用搭建使用協議的了解,很多情況下,性能測試人員需要錄制測試腳本,這就要測試人員對應用采用的協議有充分的了解;
服務器架構的了解,單一的一臺服務器、多臺服務器情況下的集群架構等,了解服務器架構,可以為性能測試人員初期性能調優提供幫助;
操作系統機能的掌握,特別是
Linux操作系統的了解,當前大多數的應用部署在Linux操作系統之上,性能測試人員需要掌握操作系統知識這一基礎技能;
數據庫知識,面對大數據時代,數據庫機能的掌握不僅僅可以為性能測試服務,還可以為你今后的華麗轉型,提供良好保障,華麗的DBA;
良好的編碼思量。基礎的編碼知識,對于編碼的了解,可以為你今后沖擊高級性能測試工程師提供有力保障,一個高級性能測試工程師,應當具有性能調優這一技能,編碼就顯得尤為重要;
對于新技術、新思想的一種追求與掌握;
1、數據庫連接jdbc.properties配置詳解 jdbc.url=jdbc:hsqldb:hsql://localhost/xdb
jdbc.username=sa
jdbc.password=
jdbc.driver=不同的數據庫廠商驅動,此處不一一列舉
接下來,詳細配置代碼如下:
<beans> <!-- picks up and registers AppConfig as a bean definition --> <context:component-scan base-package="com.acme"/> <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/> <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> </beans> |
DBCP連接池
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="${jdbc.driverClassName}" /> <property name="url" value="${jdbc.url}" /> <property name="username" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> </bean> C3P0 <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close"> <property name="driverClass" value="${jdbc.driverClassName}"/> <property name="jdbcUrl" value="${jdbc.url}"/> <property name="user" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> <context:property-placeholder location="jdbc.properties"/> proxool <bean id="dataSource" class="org.logicalcobwebs.proxool.ProxoolDataSource" destroy-method="close"> <property name="driverClass" value="${jdbc.driverClassName}"/> <property name="jdbcUrl" value="${jdbc.url}"/> <property name="user" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> <context:property-placeholder location="jdbc.properties"/> |
當然還有Druid 、DBPool 、Jakarta DBCP 等
一、多路徑解釋 多路徑,顧名思義就是有多種選擇的路徑。在SAN或IPSAN環境,主機和存儲之間外加了光纖交換機,這就導致主機和存儲之間交換速度和效率增強,一條路徑肯定是不行的,也是不安全不穩定的。多路徑就是要來解決從主機到磁盤之間最快,最高效的問題。主要實現如下幾個功能
故障的切換和恢復
IO流量的負載均衡
磁盤的虛擬化
多路徑之前一直是存儲廠商負責解決,竟來被拆分出來單獨賣錢了。
構架基本是這樣的:存儲,多路徑軟件,光纖交換機,主機,主機系統。
二、LINUX下的multipath
1、查看是否自帶安裝?
[root@web2 multipath]# rpm -qa|grep device
device-mapper-1.02.39-1.el5
device-mapper-1.02.39-1.el5
device-mapper-multipath-0.4.7-34.el5
device-mapper-event-1.02.39-1.el5
[root@web2 multipath]#
2、安裝
rpm -ivh device-mapper-1.02.39-1.el5.rpm #安裝映射包
rpm -ivh device-mapper-multipath-0.4.7-34.el5.rpm #安裝多路徑包
外加加入開機啟動
chkconfig –level 2345 multipathd on #設置成開機自啟動multipathd
lsmod |grep dm_multipath #來檢查安裝是否正常
3、配置
# on the default devices. blacklist { devnode "^(ram|raw|loop|fd|md|dm-|sr|sr|scd|st)[0-9]*" devnode "^hd[a-z]" } devices { device { vendor "HP" path_grouping_policy multibus features "1 queue_if_no_path" path_checker readsector() failback immediate } }<br><br>完整的配置如下: blacklist { devnode "^sda" } defaults { user_friendly_names no } multipaths { multipath { wwid 14945540000000000a67854c6270b4359c66c272e2f356321 alias iscsi-dm0 path_grouping_policy multibus path_checker tur path_selector "round-robin 0" } multipath { wwid 14945540000000000dcca2eda91d70b81edbcfce2357f99ee alias iscsi-dm1 path_grouping_policy multibus path_checker tur path_selector "round-robin 0" } multipath { wwid 1494554000000000020f763489c165561101813333957ed96 alias iscsi-dm2 path_grouping_policy multibus path_checker tur path_selector "round-robin 0" } multipath { wwid 14945540000000000919ca813020a195422ba3663e1f03cc3 alias iscsi-dm3 path_grouping_policy multibus path_checker tur path_selector "round-robin 0" } } devices { device { vendor "iSCSI-Enterprise" product "Virtual disk" path_grouping_policy multibus getuid_callout "/sbin/scsi_id -g -u -s /block/%n" path_checker readsector0 path_selector "round-robin 0" } } |
4、命令 [root@web2 ~]# multipath -h multipath-tools v0.4.7 (03/12, 2006) Usage: multipath [-v level] [-d] [-h|-l|-ll|-f|-F|-r] [-p failover|multibus|group_by_serial|group_by_prio] [device] -v level verbosity level 0 no output 1 print created devmap names only 2 default verbosity 3 print debug information -h print this usage text -b file bindings file location -d dry run, do not create or update devmaps -l show multipath topology (sysfs and DM info) -ll show multipath topology (maximum info) -f flush a multipath device map -F flush all multipath device maps -r force devmap reload -p policy force all maps to specified policy : failover 1 path per priority group multibus all paths in 1 priority group group_by_serial 1 priority group per serial group_by_prio 1 priority group per priority lvl group_by_node_name 1 priority group per target node device limit scope to the device's multipath (udev-style $DEVNAME reference, eg /dev/sdb or major:minor or a device map name) [root@web2 ~]# |
5、啟動關閉
# /etc/init.d/multipathd start #開啟mulitipath服務
service multipath start
service multipath restart
service multipath shutdown
6、如何獲取wwid
1、
[root@vxfs01 ~]# cat /var/lib/multipath/bindings
# Multipath bindings, Version : 1.0
# NOTE: this file is automatically maintained by the multipath program.
# You should not need to edit this file in normal circumstances.
#
# Format:
# alias wwid
#
mpath0 36006016051d50e0035744871c912de11
mpath1 36006016051d50e0034744871c912de11
mpath2 36006016051d50e0032744871c912de11
mpath3 36006016051d50e0039744871c912de11
mpath4 36006016051d50e003a744871c912de11
2、
[root@vxfs01 ~]# multipath -v3 |grep 3600
sdb: uid = 36006016051d50e003a744871c912de11 (callout)
sdc: uid = 36006016051d50e003a744871c912de11 (callout)
sdd: uid = 36006016051d50e003a744871c912de11 (callout)
sde: uid = 36006016051d50e003a744871c912de11 (callout)
36006016051d50e003a744871c912de11 1:0:0:0 sdb 8:16 0 [undef][ready] DGC,RAI
36006016051d50e003a744871c912de11 1:0:1:0 sdc 8:32 1 [undef][ready] DGC,RAI
36006016051d50e003a744871c912de11 2:0:0:0 sdd 8:48 1 [undef][ready] DGC,RAI
36006016051d50e003a744871c912de11 2:0:1:0 sde 8:64 0 [undef][ready] DGC,RAI
Found matching wwid [36006016051d50e003a744871c912de11] in bindings file.
Java的訪問控制權限相比于C++等語言可能稍微復雜一點,不過也不難理解。Java的訪問控制權限分為兩塊——“類或接口的訪問控制權限”與“變量和方法的訪問控制權限”。
1.類或接口的訪問控制權限
類或接口的訪問控制權限是指能不能用該類建立對象,接口能不能被實現等等。能夠修飾類或接口的訪問控制權限的修飾符(modifier)只有兩個——public和friendly.不過,值得一說的是friendly并不是Java的關鍵字,它只是一個習慣叫法,指的是“沒有寫訪問控制權限修飾符”的情況。
public修飾的類或接口在同一個包中的任何一個類中都可以被訪問。不同包呢?當然能訪問啦,否則引包機制不就失效了嘛(因為引包相當于拿到了一個包的public類或接口)。
friendly修飾的類或接口在同一個包中的任何一個類中都可以被訪問(和public相同),不能被不同包中的類訪問。
總結:類或接口的訪問控制權限分為“包中”和“包外”。無論修飾符(modifier)是什么,在“包中”均可訪問。對于“包外”,public修飾的類或接口可以被訪問,friendly修飾的類或接口不能被訪問。
2.變量和方法的訪問控制權限
變量和方法的訪問控制權限的修飾符(modifier)有四個——public,protected,friendly和private.
變量和方法在本類(定義該變量或方法的類)中不論訪問控制權限修飾符是什么,均可被訪問(這里先不考慮“靜態”的情況)。那么接下來只研究類外。類外也分“包內”和“包外”,接下來就從這兩方面說起,并且研究“包外”時只考慮引入的包中的public類,因為friendly的類連直接被訪問都做不到,何談訪問變量和方法。
public修飾的變量和方法在“包內”和“包外”均可被訪問。
protected修飾的變量和方法在“包內”可以被訪問,在“包外”只能被子類訪問。
friendly修飾的變量和方法在“包內”可以被訪問,在“包外”不能被訪問。
private修飾的變量和方法在“包內”或“包外”均不能被訪問。
總結:對于“類外”,public,protected,friendly和private的嚴格性逐漸遞增。public可以說沒限制,protected剝奪了“包外”非子類的訪問能力,friendly在protected基礎上進一步剝奪了“包外”子類的訪問能力,至此“包外”的訪問能力全無;private更嚴格,它在friendly基礎上更是一下剝奪了“包內”的訪問能力。
總的來說,看某個成員能否被訪問要分兩步:1.根據所在類的訪問控制權限看該類能否被訪問;2.根據該成員的訪問控制權限判斷取得所在類后該成員能否被訪問。
文檔需要全面,實時更新,并且易懂。我說的全面是指除了介紹程序的功能外還應該覆蓋到代碼中一些重要的地方。對很多人來說文檔的重要性不言而喻,但很難保持它的及時性和準確性。糟糕的文檔的后果通常會浪費更多的資源和時間。往往都是出于一些錯誤的原因而編寫的文檔。
要求文檔的一些原因
有很多原因導致我們需要編寫文檔。團隊經常會由于一些制度上的要求而編寫文檔,或者就是純粹出于無知。下面是一些編寫文檔的錯誤的理由:
有人認為文檔和項目的成敗息息相關。
文檔能夠證明某些人的存在。
需求方除了文檔也不知道要什么好
要你提供文檔的人也就是求個安心,知道事情都OK了
文檔都是過時的
軟件文檔的一個主要的問題就是它通常都不是最新的。代碼的某個部分可能發生了改動,但是文檔卻體現不出這個情況。這句話適用于幾乎所有的文檔,影響最大的其實還是需求和
測試用例。不管你多努力,文檔的過期無可避免。
文檔對誰有用?
取決于不同的受眾,文檔的類型和格式也會相應地有所不同。開發人員,測試人員,客戶,主管,最終用戶都是文檔的最大的潛在用戶。
開發人員
開發人員不應該依賴于文檔,因為它們通常都是過時的。除此之外,沒有什么文檔能比代碼本身更能提供詳細以及最新的信息了。如果你想知道某個方法做了些什么,看下這個方法吧。不確定某個類是干嘛的?看一眼它。通常只有代碼寫的太差了才需要給它添加文檔。
使用代碼本身作為文檔,這并不代表不需要其它的文檔了。關鍵是要避免冗余。如果看一下代碼就能獲取到系統的詳細信息,那么還可以有一些其它的文檔來提供快速導讀以及更高層面的一個概述的功能。代碼本身的文檔是回答不了這個系統是干嘛的或者這個系統用到了什么技術啊這種類型的問題。大多數情況下,對于開發人員而言,一個簡單的README.md就足夠他快速入門的了。像項目描述,環境配置,安裝,構建及打包指令這些東西對項目的新成員來說非常有用。但那之后,代碼就是你的圣經。產品代碼提供了所有需要的詳細信息,而測試代碼則是作為產品代碼的內在意圖的一個描述。測試用例就是可執行的文檔,而TDD(測試驅動開發)就是實現它的最常見的方式。
假設你用了某種持續集成的方式,如果測試-文檔(這里測試就是文檔,文檔也是測試)中有一部分不對了,這個用例會執行失敗,它將會很快得到修復。持續集成解決了測試-文檔不正確的問題,不過它不能保證所有功能都是有文檔的。由于這個原因(當然也有其它原因)測試-文檔應當用TDD的方式來創建。如果在代碼開發前,所有的功能都定義成測試用例,那么測試用例就能作為開發人員的一個完備的最新的文檔了。
那團隊的其它成員怎么辦?測試人員,客戶,主管,還有其它非碼農呢,他們可能無法從產品和測試的代碼中獲取到所需要的信息。
測試人員
最常見的兩種測試就是
黑盒測試和
白盒測試。這個區分很重要,因為它將測試人員也分成了兩類,一撥是知道怎么寫代碼的,至少是能讀懂代碼的(白盒測試),另一撥是不懂代碼的(黑盒測試)。有的時候測試人員也兩樣都干。不過一般而言,測試都是不懂代碼的,因此對開發人員有用的文檔對他們來說是沒意義的。如果說要從代碼中剝離出文檔的話,
單元測試可不是什么合適的東西。這就是BDD(行為驅動開發,Behavior Driven Development)存在的價值了。它能為非開發人員提供所需的文檔,但同時還兼備TDD和自動化的優點。
客戶
客戶需要能夠給系統增加新的功能,同時他們也需要獲取到關于當前系統的重要信息。給他們的文檔可不能太技術了(代碼當然不行),同時也得是最新的。行為驅動開發(BDD,Behavior Driven Development)的故事和場景應該是提供這類文檔的最佳方式了。它能夠作為驗收標準(在代碼開發前),還可以反復的執行,同時還能用自然語言編寫,這使得BDD不僅僅能夠保證文檔是最新的,同時它對那些不想看代碼的人來說也非常有用。
可執行的文檔
文檔是軟件不可或缺的一部分。正如軟件的其它部分一樣,它也得經常進行測試,這樣才能保證它是準確的并且是最新的。實現這個最有效的方法就是將這個可執行的文檔能夠集成到你的持續集成系統里面。TDD是這個方向的不二選擇。從較低層面來看的話,單元測試就非常適合作為這個文檔。另一方面來說的話,在功能層面來說BDD是一個很好的方式,它可以使用自然語言來進行描述,這保證了文檔的可讀性。
XMLHttpRequest Level 2 添加了一個新的接口——FormData。利用 FormData 對象,我們可以通過 JavaScript 用一些鍵值對來模擬一系列表單控件,我們還可以使用 XMLHttpRequest 的 send() 方法來異步的提交表單。與普通的 Ajax 相比,使用 FormData 的最大優點就是我們可以異步上傳二進制文件。
創建一個FormData對象
你可以先創建一個空的 FormData 對象,然后使用 append() 方法向該對象里添加字段,如下:
var oMyForm = new FormData(); oMyForm.append("username", "Groucho"); oMyForm.append("accountnum", 123456); // 數字123456被立即轉換成字符串"123456" // fileInputElement中已經包含了用戶所選擇的文件 oMyForm.append("userfile", fileInputElement.files[0]); var oFileBody = "<a id="a"><b id="b">hey!</b></a>"; // Blob對象包含的文件內容 var oBlob = new Blob([oFileBody], { type: "text/xml"}); oMyForm.append("webmasterfile", oBlob); var oReq = new XMLHttpRequest(); oReq.open("POST", "http://foo.com/submitform.php"); oReq.send(oMyForm); |
注:字段 "userfile" 和 "webmasterfile" 的值都包含了一個文件。通過 FormData.append() 方法賦給字段 "accountnum" 的數字被自動轉換為字符(字段的值可以是一個 Blob 對象,File對象或者字符串,剩下其他類型的值都會被自動轉換成字符串)。
在該例子中,我們創建了一個名為 oMyForm 的 FormData 對象,該對象中包含了名為"username","accountnum","userfile" 以及 "webmasterfile" 的字段名,然后使用XMLHttpRequest的 send() 方法把這些數據發送了出去。"webmasterfile" 字段的值不是一個字符串,還是一個 Blob 對象。
使用HTML表單來初始化一個FormData對象
可以用一個已有的 form 元素來初始化 FormData 對象,只需要把這個 form 元素作為參數傳入 FormData 構造函數即可:
var newFormData = new FormData(someFormElement);
例如:
var formElement = document.getElementById("myFormElement");
var oReq = new XMLHttpRequest();
oReq.open("POST", "submitform.php");
oReq.send(new FormData(formElement));
你還可以在已有表單數據的基礎上,繼續添加新的鍵值對,如下:
var formElement = document.getElementById("myFormElement");
formData = new FormData(formElement);
formData.append("serialnumber", serialNumber++);
oReq.send(formData);
你可以通過這種方式添加一些不想讓用戶編輯的固定字段,然后再發送. 使用FormData對象發送文件
你還可以使用 FormData 來發送二進制文件.首先在 HTML 中要有一個包含了文件輸入框的 form 元素:
<form enctype="multipart/form-data" method="post" name="fileinfo"> <label>Your email address:</label> <input type="email" autocomplete="on" autofocus name="userid" placeholder="email" required size="32" maxlength="64" /><br /> <label>Custom file label:</label> <input type="text" name="filelabel" size="12" maxlength="32" /><br /> <label>File to stash:</label> <input type="file" name="file" required /> </form> <div id="output"></div> <a href="javascript:sendForm()">Stash the file!</a> |
然后你就可以使用下面的代碼來異步的上傳用戶所選擇的文件:
function sendForm() { var oOutput = document.getElementById("output"); var oData = new FormData(document.forms.namedItem("fileinfo")); oData.append("CustomField", "This is some extra data"); var oReq = new XMLHttpRequest(); oReq.open("POST", "stash.php", true); oReq.onload = function(oEvent) { if (oReq.status == 200) { oOutput.innerHTML = "Uploaded!"; } else { oOutput.innerHTML = "Error " + oReq.status + " occurred uploading your file.<br \/>"; } }; oReq.send(oData); } |
你還可以不借助 HTML 表單,直接向 FormData 對象中添加一個 File 對象或者一個 Blob 對象:
data.append("myfile", myBlob);
如果 FormData 對象中的某個字段值是一個 Blob 對象,則在發送 HTTP 請求時,代表該 Blob 對象所包含文件的文件名的 "Content-Disposition" 請求頭的值在不同的瀏覽器下有所不同,Firefox使用了固定的字符串"blob",而 Chrome 使用了一個隨機字符串。
你還可以使用 jQuery 來發送 FormData,但必須要正確的設置相關選項:
var fd = new FormData(document.getElementById("fileinfo")); fd.append("CustomField", "This is some extra data"); $.ajax({ url: "stash.php", type: "POST", data: fd, processData: false, // 告訴jQuery不要去處理發送的數據 contentType: false // 告訴jQuery不要去設置Content-Type請求頭 }); |
瀏覽器兼容性
在做
單元測試時,
代碼覆蓋率常常被拿來作為衡量測試好壞的指標,甚至,用代碼覆蓋率來考核測試任務完成情況,比如,代碼覆蓋率必須達到80%或 90%。于是乎,測試人員費盡心思設計案例覆蓋代碼。用代碼覆蓋率來衡量,有利也有有弊。本文我們就代碼覆蓋率展開討論,也歡迎同學們踴躍評論。
首先,讓我們先來了解一下所謂的“代碼覆蓋率”。我找來了所謂的定義:
代碼覆蓋率 = 代碼的覆蓋程度,一種度量方式。
上面簡短精悍的文字非常準確的描述了代碼覆蓋率的含義。而代碼覆蓋程度的度量方式是有很多種的,這里介紹一下最常用的幾種:
1. 語句覆蓋(StatementCoverage)
又稱行覆蓋(LineCoverage),段覆蓋(SegmentCoverage),基本塊覆蓋(BasicBlockCoverage),這是最常用也是最常見的一種覆蓋方式,就是度量被測代碼中每個可執行語句是否被執行到了。這里說的是“可執行語句”,因此就不會包括像C++的頭文件聲明,代碼注釋,空行,等等。非常好理解,只統計能夠執行的代碼被執行了多少行。需要注意的是,單獨一行的花括號{} 也常常被統計進去。語句覆蓋常常被人指責為“最弱的覆蓋”,它只管覆蓋代碼中的執行語句,卻不考慮各種分支的組合等等。假如你的上司只要求你達到語句覆蓋,那么你可以省下很多功夫,但是,換來的確實測試效果的不明顯,很難更多地發現代碼中的問題。
這里舉一個不能再簡單的例子,我們看下面的被測試代碼:
int foo(int a, int b)
{
return a / b;
}
假如我們的測試人員編寫如下測試案例:
TeseCase: a = 10, b = 5
測試人員的測試結果會告訴你,他的代碼覆蓋率達到了100%,并且所有測試案例都通過了。然而遺憾的是,我們的語句覆蓋率達到了所謂的100%,但是卻沒有發現最簡單的
Bug,比如,當我讓b=0時,會拋出一個除零異常。
正因如此,假如上面只要求測試人員語句覆蓋率達到多少的話,測試人員只要鉆鉆空子,專門針對如何覆蓋代碼行編寫測試案例,就很容易達到主管的要求。當然了,這同時說明了幾個問題:
1.主管只使用語句覆蓋率來考核測試人員本身就有問題。
2.測試人員的目的是為了測好代碼,鉆如此的空子是缺乏職業道德的。
3.是否應該采用更好的考核方式來考核測試人員的
工作?
為了尋求更好的考核標準,我們必須先了解完代碼覆蓋率到底還有哪些,如果你的主管只知道語句覆蓋,行覆蓋,那么你應該主動向他介紹還有更多的覆蓋方式。比如:
2. 判定覆蓋(DecisionCoverage)
又稱分支覆蓋(BranchCoverage),所有邊界覆蓋(All-EdgesCoverage),基本路徑覆蓋(BasicPathCoverage),判定路徑覆蓋(Decision-Decision-Path)。它度量程序中每一個判定的分支是否都被測試到了。這句話是需要進一步理解的,應該非常容易和下面說到的條件覆蓋混淆。因此我們直接介紹第三種覆蓋方式,然后和判定覆蓋一起來對比,就明白兩者是怎么回事了。
3. 條件覆蓋(ConditionCoverage)
它度量判定中的每個子表達式結果true和false是否被測試到了。為了說明判定覆蓋和條件覆蓋的區別,我們來舉一個例子,假如我們的被測代碼如下:
int foo(int a, int b)
{
if (a < 10 || b < 10) // 判定
{
return 0; // 分支一
}
else
{
return 1; // 分支二
}
}
設計判定覆蓋案例時,我們只需要考慮判定結果為true和false兩種情況,因此,我們設計如下的案例就能達到判定覆蓋率100%:
TestCaes1: a = 5, b = 任意數字 覆蓋了分支一
TestCaes2: a = 15, b = 15 覆蓋了分支二
設計條件覆蓋案例時,我們需要考慮判定中的每個條件表達式結果,為了覆蓋率達到100%,我們設計了如下的案例:
TestCase1: a = 5, b = 5 true, true
TestCase4: a = 15, b = 15 false, false
通過上面的例子,我們應該很清楚了判定覆蓋和條件覆蓋的區別。需要特別注意的是:條件覆蓋不是將判定中的每個條件表達式的結果進行排列組合,而是只要每個條件表達式的結果true和false測試到了就OK了。因此,我們可以這樣推論:完全的條件覆蓋并不能保證完全的判定覆蓋。比如上面的例子,假如我設計的案例為:
TestCase1: a = 5, b = 15 true, false 分支一
TestCase1: a = 15, b = 5 false, true 分支一
我們看到,雖然我們完整的做到了條件覆蓋,但是我們卻沒有做到完整的判定覆蓋,我們只覆蓋了分支一。上面的例子也可以看出,這兩種覆蓋方式看起來似乎都不咋滴。我們接下來看看第四種覆蓋方式。
4. 路徑覆蓋(PathCoverage)
又稱斷言覆蓋(PredicateCoverage)。它度量了是否函數的每一個分支都被執行了。 這句話也非常好理解,就是所有可能的分支都執行一遍,有多個分支嵌套時,需要對多個分支進行排列組合,可想而知,測試路徑隨著分支的數量指數級別增加。比如下面的測試代碼中有兩個判定分支:
int foo(int a, int b)
{
int nReturn = 0;
if (a < 10)
{// 分支一
nReturn += 1;
}
if (b < 10)
{// 分支二
nReturn += 10;
}
return nReturn;
}
對上面的代碼,我們分別針對我們前三種覆蓋方式來設計測試案例:
a. 語句覆蓋
TestCase a = 5, b = 5 nReturn = 11
語句覆蓋率100%
b. 判定覆蓋
TestCase1 a = 5, b = 5 nReturn = 11
TestCase2 a = 15, b = 15 nReturn = 0
判定覆蓋率100%
c. 條件覆蓋
TestCase1 a = 5, b = 15 nReturn = 1
TestCase2 a = 15, b = 5 nReturn = 10
條件覆蓋率100%
我們看到,上面三種覆蓋率結果看起來都很酷!都達到了100%!主管可能會非常的開心,但是,讓我們再去仔細的看看,上面被測代碼中,nReturn的結果一共有四種可能的返回值:0,1,10,11,而我們上面的針對每種覆蓋率設計的測試案例只覆蓋了部分返回值,因此,可以說使用上面任一覆蓋方式,雖然覆蓋率達到了100%,但是并沒有測試完全。接下來我們來看看針對路徑覆蓋設計出來的測試案例:
TestCase1 a = 5, b = 5 nReturn = 0
TestCase2 a = 15, b = 5 nReturn = 1
TestCase3 a = 5, b = 15 nReturn = 10
TestCase4 a = 15, b = 15 nReturn = 11
路徑覆蓋率100%
太棒了!路徑覆蓋將所有可能的返回值都測試到了。這也正是它被很多人認為是“最強的覆蓋”的原因了。
還有一些其他的覆蓋方式,如:循環覆蓋(LoopCoverage),它度量是否對循環體執行了零次,一次和多余一次循環。剩下一些其他覆蓋方式就不介紹了。
總結
通過上面的學習,我們再回頭想想,覆蓋率數據到底有多大意義。我總結了如下幾個觀點,歡迎大家討論:
a. 覆蓋率數據只能代表你測試過哪些代碼,不能代表你是否測試好這些代碼。(比如上面第一個除零Bug)
b. 不要過于相信覆蓋率數據。
c. 不要只拿語句覆蓋率(行覆蓋率)來考核你的測試人員。
d. 路徑覆蓋率 > 判定覆蓋 > 語句覆蓋
e. 測試人員不能盲目追求代碼覆蓋率,而應該想辦法設計更多更好的案例,哪怕多設計出來的案例對覆蓋率一點影響也沒有。
使用HP
UFT 12.00版本(主要是
GUI測試)一段時間,有些小想法,本來是打算回復給hp相關人員,奈何郵件一直遭拒,所以暫且先放在這里吧。
關于HP 的UFT,簡單做一下評價,總體來說是一款很好的軟件,極容易上手和使用(這個也估計只是我沒有往深入研究),
測試流程和思路也很清晰,重點是和HP一些其他自動化工具,比如Loadrunner,QC可以結合起來用,堪稱完美。拍完馬屁說點實際的。
其實對于目前所在公司的項目,我一直很猶豫要不要引進HP的這款軟件,首先我對于這款軟件不是很熟悉,當然現在這個理由是可以排除的,對于這個軟件的效果怎么樣,我也不確定,之前公司沒有使用這個軟件,所以也沒有什么積累,一切都要從零開始。明確知道這個軟件可能在后期的回歸測試會減少點人力,但是前期的的腳本錄制,調試,以及維護,感覺也不是一個小工程。
學習使用HP UFT軟件的過程不算很艱難,網上的資料還算是比較多的,加上采用錄制的模式,只需點點點的就可以了,使用起來還是很輕松的,當然也會遇到一些問題,不過在網上查查資料基本上可以解決大部分。底層的腳本錄制基本上解決的差不多了,開始關注整個測試流程框架,也就是在這個地方,突然間發現HPUFT的GUI測試不靈活,感覺有些死板。
簡單說就是動作Action的執行順序只能按你設定好的跑,由于之前本人是測試android產品的,有使用過Monkeyrunner相關的測試工具,對于這種按設定路線走的
自動化測試工具有些不是很習慣。于是乎,突發奇思妙想,要是HPUFT的GUI測試也可以做到這種隨機的測試就好了,當然了也不是像Monkeyrunner那樣。
初步的想法是將各個Action進行封裝,采用隨機調用的方式執行測試,調用的次數可以是隨機的也可以設定,這個整體架構的改變會對Action的錄制有相應的要求,比如說執行完一個Action,它的出口和入口要一致,各個Action的要在同一級別,比如在同一頁面。如此可能會出現多個層次,比如一個Action中又可以劃分出多個Action,這個需要采用分層的思想進行解決,至于要分多少層,使用者可以按照自己的軟件的特點進行劃分。
說的好像又些復雜了,簡單的說就是按照一定的要求錄制Action,采用隨機的方式執行Action。此方案可以檢測出不同功能之間因調用順序的不同而出現的BUG,實際測試也證明這種BUG是存在的,同時,這種模式也使得該軟件的使用更為靈活。
暫且想法就這些,后續有再補上。