第一步就是安裝
Selenium這個模塊,當(dāng)然,前提是你的python已經(jīng)安裝好了
直接在dos窗口輸入
pip install selenium完成一鍵安裝
然后就可以新建一個py文件,在里面輸入
from selenium import webdriver from selenium.common.exceptions import TimeoutException from selenium.webdriver.support.ui import WebDriverWait # available since 2.4.0 from selenium.webdriver.support import expected_conditions as EC # available since 2.26.0 # Create a new instance of the Firefox driver driver = webdriver.Firefox() driver.get("http://www.google.com") # find the element that's name attribute is q (the google search box) inputElement = driver.find_element_by_name("q") # type in the search inputElement.send_keys("cheese!") # submit the form (although google automatically searches now without submitting) inputElement.submit() # the page is ajaxy so the title is originally this: print driver.title try: # we have to wait for the page to refresh, the last thing that seems to be updated is the title WebDriverWait(driver, 10).until(EC.title_contains("cheese!")) # You should see "cheese! - Google Search" print driver.title finally: driver.quit() |
這樣就打開google進(jìn)行查找cheese!后打印標(biāo)題并關(guān)閉瀏覽器
下面介紹各種獲取瀏覽器內(nèi)的各種元素的方法
By ID <divid="coolestWidgetEvah">...</div> element=driver.find_element_by_id("coolestWidgetEvah") By Class Name <divclass="cheese"><span>Cheddar</span></div><divclass="cheese"><span>Gouda</span></div> cheeses=driver.find_elements_by_class_name("cheese") By Tag Name <iframesrc="..."></iframe> frame=driver.find_element_by_tag_name("iframe") By Name <inputname="cheese"type="text"/> cheese=driver.find_element_by_name("cheese") By Link Text <a >cheese</a>> cheese=driver.find_element_by_link_text("cheese") By Partial Link Text <a >search for cheese</a> cheese=driver.find_element_by_partial_link_text("cheese") By CSS <divid="food"><spanclass="dairy">milk</span><spanclass="dairy aged">cheese</span></div> cheese=driver.find_element_by_css_selector("#food span.dairy.aged") By XPATH <inputtype="text"name="example"/><INPUTtype="text"name="other"/> inputs=driver.find_elements_by_xpath("http://input") |
(相關(guān)的XPATH教程具體可以參考W3C的教程進(jìn)行學(xué)習(xí))
Using JavaScript
這么這是兩個例子,例子一需要提前加載jqury的支持
element=driver.execute_script("return $('.cheese')[0]")
labels=driver.find_elements_by_tag_name("label")inputs=driver.execute_script("var labels = arguments[0], inputs = []; for (var i=0; i < labels.length; i++){"+"inputs.push(document.getElementById(labels[i].getAttribute('for'))); } return inputs;",labels)
User Input - Filling In Forms
select=driver.find_element_by_tag_name("select")allOptions=select.find_elements_by_tag_name("option")foroptioninallOptions:print"Value is: "+option.get_attribute("value")option.click()
有些選擇提取的元素是需要進(jìn)行篩選的如
# available since 2.12fromselenium.webdriver.support.uiimportSelectselect=Select(driver.find_element_by_tag_name("select"))select.deselect_all()select.select_by_visible_text("Edam")
那么這些form進(jìn)行如何提交呢?
driver.find_element_by_id("submit").click() element.submit() Moving Between Windows and Frames <a href="somewhere.html"target="windowName">Click here to open a new window</a> driver.switch_to_window("windowName") driver.switch_to_frame("frameName") driver.switch_to_frame("frameName.0.child") |
切換一些彈出來的比如登錄框等
Popup Dialogs
這類的有alerts, confirms, and prompts等
alert=driver.switch_to_alert()
返回的是一個彈窗的窗體對象
Cookies
# Go to the correct domaindriver.get("http://www.example.com")# Now set the cookie. Here's one for the entire domain# the cookie name here is 'key' and its value is 'value'driver.add_cookie({'name':'key','value':'value','path':'/'})# additional keys that can be passed in are:# 'domain' -> String,# 'secure' -> Boolean,# 'expiry' -> Milliseconds since the Epoch it should expire.# And now output all the available cookies for the current URLforcookieindriver.get_cookies():print"%s -> %s"%(cookie['name'],cookie['value'])# You can delete cookies in 2 ways# By namedriver.delete_cookie("CookieName")# Or all of themdriver.delete_all_cookies()
Changing the User Agent
profile=webdriver.FirefoxProfile()profile.set_preference("general.useragent.override","some UA string")driver=webdriver.Firefox(profile)
Drag And Drop
fromselenium.webdriver.common.action_chainsimportActionChainselement=driver.find_element_by_name("source")target=driver.find_element_by_name("target")ActionChains(driver).drag_and_drop(element,target).perform()
具體的詳細(xì)用法可以參考官方文檔:http://docs.seleniumhq.org/docs/03_webdriver.jsp
一、安裝JDK+tomcat
1.下載JDK
http://www.oracle.com/technetwork/java/javase/downloads/jdk-7u2-download-1377129.html
2.安裝JDK(RPM方式)
rpm -ivh jdk-7u2-linux-i586.rpm
3.下載tomcat
wget http://apache.etoak.com/tomcat/tomcat-7/v7.0.23/bin/apache-tomcat- 7.0.23.tar.gz
4.安裝tomcat
tar zxvf apache-tomcat-7.0.23.tar.gz
mv apache-tomcat-7.0.23 /usr/local/tomcat
修改/etc/profile
export CATALINA_HOME=/usr/local/tomcat
export CLASSPATH=$JAVA_HOME/lib:$CATALINA_HOME/lib
export PATH=$PATH:$CATALINA_HOME/bin
export JAVA_HOME=/usr/java/jdk1.7.0_02
source /etc/profile
/usr/local/tomcat/bin/catalina.sh start (關(guān)閉tomcat ./shutdown.sh stop)
屏幕輸出:
java version "1.7.0_02"
Java(TM) SE Runtime Environment (build 1.7.0_02-b13)
Java HotSpot(TM) Client VM (build 22.0-b10, mixed mode, sharing)
安裝完畢
netstat -ntl |grep 8080
http://localhost:8080/
wget http://www.atlassian.com/software/jira/downloads/binary/atlassian-jira-4.4.4-x32.bin
wget http://www.atlassian.com/software/jira/downloads/binary/atlassian-jira-4.4.4-x64.bin
chmod 755 atlassian-jira-4.4.4-x32.bin
./atlassian-jira-4.4.4-x32.bin
出現(xiàn)提示:
Unpacking JRE ...
Starting Installer ...
This will install JIRA 4.4 on your computer. #安裝jira4.4在你的機(jī)器上。
OK [o, Enter], Cancel [c] # 按回車確認(rèn)安裝
If JIRA is already installed on this machine, please read the following information carefully.Please choose between creating a new JIRA installation or upgrading an existing JIRA installation.
#如果j ira已經(jīng)安裝在這臺機(jī)器,請閱讀以下資料仔細(xì)。請選擇創(chuàng)建一個新的j ira之間安裝或升級現(xiàn)有的jira安裝。
Create a new JIRA installation. [1, Enter], Upgrade an existing JIRA installation. [2] #直接回車,安裝新的
#創(chuàng)建一個新的j ira安裝[1],更新現(xiàn)有的jira安裝[2]。
Where should JIRA 4.4 be installed? #安裝路徑。本人安裝在/usr/local/jira ,在后面輸入/usr/local/jira ,按回車;
[/opt/atlassian/jira]
/usr/local/jira
Default location for JIRA data #存放數(shù)據(jù)路徑,本人安裝在/usr/local/jira_home,在后面輸入/usr/local/jira_home ,按回車;
[/var/atlassian/application-data/jira]
/usr/local/jira_home
Configure which ports JIRA will use.
JIRA requires two TCP ports that are not being used by any other
applications on this machine. The HTTP port is where you will access JIRA
through your browser. The Control port is used to Startup and Shutdown JIRA.
#配置一種端口。需要兩個TCP端口j ira不被任何其他應(yīng)用占用。HTTP端口,你將通過瀏覽器訪問jira
Use default ports (HTTP: 8080, Control: 8005) - Recommended [1, Enter], Set custom value for HTTP and Control ports [2]
#使用默認(rèn)端口(HTTP:8080 ,控制:8005)——建議[1]],
JIRA can be run in the background.
You may choose to run JIRA as a service, which means it will start
automatically whenever the computer restarts.
Install JIRA as Service?
Yes [y, Enter], No [n] #按回車
#讓 j ira可以在后臺運(yùn)行。當(dāng)計算機(jī)重啟時自動啟動jira程序。
Setup has finished installing JIRA 4.4 on your computer.
JIRA 4.4 can be accessed at http://localhost:8080
Finishing installation ...
下載插件文件:
對于JIRA4.3及以上版本,將文件名稱修改為jira-lang-zh_CN-JIRA版本號.jar
將插件復(fù)制到JIRA安裝路徑下:/usr/local/jira/atlassian-jira/WEB-INF/lib
重新啟動JIRA服務(wù):service jira start
停止JIRA服務(wù):server jiar stop
6.安裝mysql驅(qū)動
wget http://dev.mysql.com/get/Downloads/Connector-J/mysql-connector- java-5.1.13.tar.gz/from/http://gd.tuwien.ac.at/db/mysql/
tar zxvf java-5.1.13.tar.gz
cp mysql-connector-java-5.1.13-bin.jar /usr/local/jira/atlassian-jira/WEB-INF/lib
7.創(chuàng)建數(shù)據(jù)庫
create database jira character set utf8;
grant all on jira.* to 'jira'@'localhost' indentified by 'jira';
flush privileges;
quit;
鑒于這個系列寫的內(nèi)容是希望幫助“大多數(shù)2-3年
工作經(jīng)驗、急切盼望提升自身能力的 tester找到捅破‘
測試自動化’窗戶紙的辦法”,所以木有高深內(nèi)容,高手們請直接飄過,呵呵。
1.“關(guān)鍵字驅(qū)動”的由來
說到“關(guān)鍵字驅(qū)動”和“測試自動化”,就不能不提到 Mosley Daniel 的《
軟件測試自動化》一書,這本書03年引入國內(nèi),04年市面上開始有賣,書中有兩個相信能吸引到很多 tester 的話題:
(1)腳本應(yīng)該錄制還是編寫?——想知道答案的自己下載電子書看吧:)
(2)“數(shù)據(jù)驅(qū)動”和“關(guān)鍵字驅(qū)動”。
彼時的我,剛剛經(jīng)歷了一次不成功的自動化實踐,雖然
Rational Robot 提供了類似UI庫管理,數(shù)據(jù)池管理之類的強(qiáng)大功能,但是痛苦依然。對測試自動化理解的不夠深入,不知道該如何應(yīng)對RAD模式下UI和業(yè)務(wù)快速調(diào)整,以及對C/S下非標(biāo)準(zhǔn)控件的識別等問題,導(dǎo)致了無法快速維護(hù)腳本、replay 次數(shù)不夠,回放通過率不夠,最后的結(jié)果是ROI無法滿足要求,自動化項目宣告結(jié)束。
帶著很多疑問的我原本想從那本書中找到些答案,遺憾的是那時功力實在太差,居然沒有看懂,唯一留下印象的就是作者在80年代就開始探索自動化回歸的技術(shù),并且在90年代已經(jīng)嘗試了“數(shù)據(jù)驅(qū)動”和“關(guān)鍵字驅(qū)動”的技術(shù),想來當(dāng)時 Robot framework 之類的都還沒有出現(xiàn),所以我相信“關(guān)鍵字驅(qū)動”的技術(shù)源自這書的作者和他的朋友們。
2. 從“數(shù)據(jù)驅(qū)動”到“關(guān)鍵字驅(qū)動”
所謂的數(shù)據(jù)驅(qū)動,原本沒有什么特別的,無非就是把hard code 在腳本中的數(shù)據(jù)參數(shù)化出來,之所以算是Robot、WinRunner甚至
QTP時代測試工具的賣點(diǎn),其實主要是因為那個年代大多數(shù)system tester 不懂開發(fā),總需要有個功能來幫助自己完成參數(shù)抽取、數(shù)據(jù)維護(hù)、自動替換之類的功能。
而關(guān)鍵字驅(qū)動,則進(jìn)一步在技術(shù)上把 tester 分成了完全不懂技術(shù)的和懂點(diǎn)技術(shù)的,前者只能根據(jù)格式填寫一下 excel 表格,后者對工具/框架內(nèi)置的所謂關(guān)鍵字庫進(jìn)行增補(bǔ)或二次開發(fā)。
找些例子來看看吧。
(1)簡單的數(shù)據(jù)驅(qū)動。
如下面代碼,定義了一個class并實例化了幾個對象,用來存放不同的用戶登錄信息;而在負(fù)責(zé)完成登錄操作的 do_login_as() 方法中,把 send_keys 原來向表單中填充的數(shù)據(jù)參數(shù)化,根據(jù)每次調(diào)用 do_login_as() 方法時傳入的不同對象來實現(xiàn)用不同帳號登錄的效果——雖然我對以“登錄”為樣例介紹
自動化測試腳本深惡痛絕,不過這里為了表述簡單,還是用了。(下面的代碼只是一個示例,不是直接用來運(yùn)行的。)
其實數(shù)據(jù)驅(qū)動本來也沒有什么技術(shù)含量,寫過代碼的誰還不知道什么是變量啊?不過在早期的商業(yè)工具中,的確把這個作為了一個賣點(diǎn),畢竟10年前的測試工具設(shè)計思路也與現(xiàn)在不同。早期的工具始終都假設(shè)”
系統(tǒng)測試工程師不懂開發(fā)“,所以他們在開發(fā)測試腳本時需要借助類似錄制回放+編輯調(diào)試的模式;另外,對于數(shù)據(jù)驅(qū)動所需要的測試數(shù)據(jù),最好也是通過工具內(nèi)置的data pool 管理,通過表格編輯,甚至讀取 csv、excel 文件之類的。如果我們依照這個思路去實現(xiàn)自己的測試框架,那肯定是商業(yè)工具很有價值,畢竟自己實現(xiàn)data pool管理、外部數(shù)據(jù)文件讀取之類的功能,代碼量也不小,要處理好那些數(shù)據(jù)格式和內(nèi)容校驗之類的,貌似跟我們所需要完成的自動化也沒啥太大關(guān)系。
可問題在于,為什么一定要有data pool+外部數(shù)據(jù)文件來實現(xiàn)”數(shù)據(jù)驅(qū)動“呢?
(2)傳統(tǒng)的“關(guān)鍵字驅(qū)動”
還是先用個例子來看看傳統(tǒng)的“關(guān)鍵字驅(qū)動”長什么樣子。

上面是一個
Selenium 0.x 時代Core模式下(什么是selenium的core模式和RC模式請自行g(shù)oogle,不過話說現(xiàn)在已經(jīng)全民webdriver時代了,不知道也就不知道吧)的例子(QTP的實現(xiàn)效果也類似),簡單說就是第一行是
test case name,下面3列開始進(jìn)入正題,第一列表示你想干啥,第二列是被操作對象是誰,第三列是你對它干了啥。06年剛剛開始嘗試
web測試自動化的時候,我們一度認(rèn)為這是一個比之前的測試工具要智能的多的東東,不過在嘗試了很長一段時間之后,還是發(fā)現(xiàn)了這種模式下的問題,又逐漸轉(zhuǎn)回了編寫腳本的方式。
對比上面的第一個腳本,可以看出關(guān)鍵字驅(qū)動進(jìn)一步屏蔽了底層的實現(xiàn)細(xì)節(jié),例如,你只需要clickAndWait那個btnG,而不需要知道btnG到底是個啥,以及它在哪里,你只需要填寫表格。等你把一個個 test case 變成了一個個表格,把一個個step變成了一行行表格中的內(nèi)容,基本上你的自動化測試就搞完了。——真的是這樣嗎?
現(xiàn)在回想一下,關(guān)鍵字驅(qū)動之所以會出現(xiàn),可能初衷還是為了降低自動化實施的門檻——因為tester都不太懂開發(fā)嘛,所以開發(fā)能力強(qiáng)的人把框架實現(xiàn)出來,原來那些只會寫excel的 system tester填寫表格就可以了。也許現(xiàn)在還有很多公司會傾向于這個方案,美其名曰“分工協(xié)作,人盡其能”。不過關(guān)于這個問題,暫時先不展開討論,先看看這種傳統(tǒng)的關(guān)鍵字驅(qū)動模式會遇到什么問題。
首先,頁面對象的識別問題。這是任何一種基于UI實現(xiàn)回歸測試自動化的框架都會遇到的問題。在C/S時代,就已經(jīng)出現(xiàn)了很多定制化UI組件難以被工具識別的問題,可好歹總逃不出windows的手心。到了web時代,前端實現(xiàn)技術(shù)百花齊放,而前端代碼的編寫也更是一個開發(fā)人員一個習(xí)慣,如果缺少統(tǒng)一的編碼規(guī)范,對于那些沒有使用id或者name之類屬性的頁面對象,傳統(tǒng)的關(guān)鍵字驅(qū)動框架就沒有例子中看起來那么美好了。當(dāng)然,有人說xpath可以解決一切問題。嗯,xpath是很強(qiáng),但是一個沒有開發(fā)經(jīng)驗的tester想掌握它其實也不會比學(xué)會一點(diǎn)基本的編碼技術(shù)容易多少;另外,xpath并不是萬能的,在實際中還是有些它處理不了的情況。下面再給個例子(如果tester看不懂這個例子,估計學(xué)會xpath也有點(diǎn)困難):
上面這個圖中是一個表格,id列(第一列)和需求名稱列(第二列)下顯示的內(nèi)容,都是可以點(diǎn)擊的超鏈接,最后的“操作”一列中的一個個小圖標(biāo)也各自指向一個鏈接(分別是“變更”、“評審”、“編輯”、“建用例”),通過查看第一行記錄的html代碼,我們發(fā)現(xiàn):
假如想要編輯一條記錄,沒有可利用的id或name屬性,唯一可用的是link;
link指向的url中包含了這條記錄的ID信息,因為這里是第一行記錄的代碼,所以都是341,而后面的每一行記錄的代碼都是各自的ID。
上面這個例子是企業(yè)應(yīng)用中常見的場景,關(guān)鍵問題在于數(shù)據(jù)記錄ID是一個不可控因素,而數(shù)據(jù)記錄的title/name之類的是可控的,為了提高test case的可維護(hù)性和相互獨(dú)立,我們肯定不會依賴ID去模擬頁面操作,而是根據(jù)title/name之類取回ID信息,再拼裝回所需要操作的鏈接。這種情況下,xpath恐怕也搞不定了。(如果這里再涉及到要在幾個iframe之間跳來跳去,恐怕寫腳本的人就要崩潰了。)
當(dāng)然,有人會說關(guān)鍵字驅(qū)動框架一般也可以定義function來實現(xiàn)類似的操作啊。唉,都到了編寫自定義function的地步了,干嘛還非要糾纏在關(guān)鍵字驅(qū)動上啊。
第二,腳本的可維護(hù)性問題。如一開始的例子,傳統(tǒng)的關(guān)鍵字驅(qū)動是一種純“面向過程”的腳本組織方式(就像C/pascal),表格中填寫的是一個操作序列。如果多個test case 都涉及到某個頁面,基本上就會在多個case中都看到類似 clickAndWait btnG這樣的內(nèi)容,而一旦頁面中btnG改名叫buttonG了,或者 clickAndWait btnG與verifyTextPresent 之間增加了一個 clickAndWait XXX的step,那基本上每個case都需要修改。
嗯,問題來了,當(dāng)你的腳本數(shù)量從1增長到100、1000的時候,當(dāng)UI的變動無法避免的時候,當(dāng)你發(fā)現(xiàn)100個case回歸執(zhí)行只有90個通過,執(zhí)行失敗的10個需要逐個檢查錯誤日志和查看截圖,再挨個修改的時候,當(dāng)下次回歸測試又發(fā)生這種問題的時候——基本上這就是一個死循環(huán)了。如果解決不了根本問題,前期投資可以舍棄了,別糾結(jié)了。
當(dāng)然,有人說關(guān)鍵字驅(qū)動已經(jīng)進(jìn)化了,可以跟最新的webdriver結(jié)合起來,提升關(guān)鍵字的封裝層次,解決這個可維護(hù)性的問題。好吧,這個問題等下再詳細(xì)說。
第三,腳本的可擴(kuò)展性問題。在大規(guī)模實施自動化的過程中,腳本一般都會簡單的是一行行的browser.find_elmenet_by_id(xxxx).click() 這樣的模式,根據(jù)各種條件來判斷執(zhí)行的分支,進(jìn)行各種異常處理,第三方類庫/包的調(diào)用,主機(jī)環(huán)境的訪問,諸如此類,這些對于所有的3GL/4GL來說其實都很容易實現(xiàn),但對于傳統(tǒng)的關(guān)鍵字驅(qū)動來說,嗯,也可以實現(xiàn),大概是下面這個樣子【摘自robot framework(此robot非rational robot)】:
下面是一個在 keyword 表格里面實現(xiàn)的 FOR 循環(huán):
有人可能會說“你看,關(guān)鍵字驅(qū)動框架也可以擴(kuò)展的很強(qiáng)大啊!”。是,在programming 的世界中,沒有什么不能做的,不過都弄到這個份兒上了,學(xué)習(xí)這一套東西跟學(xué)習(xí)一個標(biāo)準(zhǔn)的編程語言還有什么差別嗎?先不說這樣的框架越擴(kuò)展越難維護(hù),可靠性也就越差,單單這些關(guān)鍵字的用途被局限在自己的框架中,你所積累的知識和經(jīng)驗無法重用到其他測試代碼的編寫中這一個理由,就應(yīng)該徹底放棄這種方式了。
如果要說的直白一些,傳統(tǒng)的關(guān)鍵字驅(qū)動框架的時代在前幾年就已經(jīng)開始遠(yuǎn)去(是had been,不是have been),我們感謝上一代tester的努力探索和實踐,但最終歷史證明這是一個不算成功的嘗試,一個框架如果不具備開放性,一切都自給自足,那么有一天這也會成為限制自己發(fā)展的最大原因。
(3)穿馬甲的“關(guān)鍵字驅(qū)動”
時代在進(jìn)步,關(guān)鍵字驅(qū)動也在進(jìn)步,這個領(lǐng)域中的代表 robot framework(此robot非rational robot) 也在進(jìn)步,于是,test case 變成了下面這個樣子。
依舊不變的是“表格”,改變的是填寫方式——其實這背后的,是關(guān)鍵字定義終于被開放出來,tester可以自己定義keyword然后“注冊”到框架中,而那些依然沒有學(xué)會基本編程技能的tester,繼續(xù)用這些keyword重復(fù)上個時代的事情——填寫表格。
其實相對于最初對關(guān)鍵字驅(qū)動的定義,這個真的已經(jīng)不是關(guān)鍵字驅(qū)動了,如果非說它是,那么只能說上個時代的關(guān)鍵字驅(qū)動中,test case 表格的每行都是一個頁面操作,而“新的”關(guān)鍵字驅(qū)動中,test case 的每行都已經(jīng)是一個完整的業(yè)務(wù)操作,以上面的“Create Valid User” step 為例,robot framework希望的實現(xiàn)方式是tester通過python等4GL實現(xiàn)一個同名的function,這個function接受兩個參數(shù),分別是“fred”和“P4ssw0rd”,再把這個function注冊到robot framework中。而“Create Valid User”內(nèi)部的實現(xiàn),可以類似于一開始“數(shù)據(jù)驅(qū)動”中的那個例子,充分利用4GL的特性和已有的其他第三方組件(例如webdriver),來實現(xiàn)各種復(fù)雜的基于UI的操作,這樣也就解決了剛剛“傳統(tǒng)的關(guān)鍵字驅(qū)動”所遇到的問題。
最后,當(dāng)完成了這個function的開發(fā)并在robot framework中注冊后,做手工測試的system tester就可以很容易的把原本excel中的一個個case轉(zhuǎn)變?yōu)樽詣踊_本了。
其實這個思路有它的優(yōu)點(diǎn),例如:通過分工協(xié)作降低實施門檻,可以一開始就編寫符合robot所需格式的manual test case,等到keyword開完全了以后這些case就可以直接導(dǎo)入執(zhí)行了;不再自給自足,而是保持一定的開放,并利用其他第三方組件的特性。這樣很大程度上解決了自動化項目實施遇到的人員能力問題和可維護(hù)性、可擴(kuò)展性的問題。
另外,新的關(guān)鍵字驅(qū)動還有一個更加先進(jìn)的“近親”BDD作為參照,很容易把它的一些實踐也一起融合進(jìn)來。
一切看起來都很美好,不過問題也還是有。
表格化的test case畢竟不同于編寫代碼,調(diào)試就變成了一個問題,如果寫錯了關(guān)鍵字的一個字母,要及時發(fā)現(xiàn)并定位到問題就不那么容易。當(dāng)然,可以再開發(fā)一個web平臺,讓編寫case的人僅能從一個list中選擇已經(jīng)定義好的keyword,不過這個成本恐怕就不是一般研發(fā)團(tuán)隊能承受的了。
作為一個軟件,易用性和復(fù)雜度總是成反比的,當(dāng)框架提供了方便的表格化編寫case功能時,也相對的增加了底層的復(fù)雜度(雖然沒看過robot framework的代碼,但是相信底層代碼的分層也應(yīng)該比較復(fù)雜),對于想要真的掌握框架的團(tuán)隊來說,無形中增加了一道門檻。另外,復(fù)雜度與可擴(kuò)展性也是成反比的,就像我可以用木頭做一輛車,也可以把木頭車拆了做些別的東西,但是我沒法把一輛汽車拆了弄成別的東西——前兩年廣東美院那位把解放卡車拆了做成關(guān)公像的牛人除外。當(dāng)然,最終實施自動化時到底如何進(jìn)行框架選型,就要團(tuán)隊自己在易用性/復(fù)雜度/可擴(kuò)展性上進(jìn)行評估了。
把excel里面的manual test case通過新的關(guān)鍵字驅(qū)動直接變成可執(zhí)行的腳本是最好的方法嗎?這似乎只是一個傳統(tǒng)system test 的慣性思維在作怪,為什么沒看過開發(fā)人員把unit test 也寫到一個個表格里面?為什么manual test case 就一定要先寫在excel里面,而不是一開始就是代碼?
如果僅僅是考慮把 step組裝起來,再把case組織成suite執(zhí)行,其實代碼實現(xiàn)上可以說毫無技術(shù)含量,但是對于一個沒有開發(fā)經(jīng)驗的tester來說,這畢竟是一個跟coding簡單親密接觸的機(jī)會,可以讓tester從低難度的代碼開始培養(yǎng)興趣和信息。而keyword,無論新的還是舊的,卻剝奪了這個機(jī)會;當(dāng)tester希望學(xué)習(xí)框架的時候,會發(fā)現(xiàn)表格的層面跟下層框架之間的不是樓梯,而是一道溝。
3. “關(guān)鍵字驅(qū)動”的未來
我們?nèi)缃袼幍沫h(huán)境總是在變化著,今天與10年前相比,最大的變化就是測試行業(yè)獲得了極大的發(fā)展,大多數(shù)企業(yè)都認(rèn)可了測試工作的重要性,并且開始思考如何提升測試工作自身的質(zhì)量和效率,而且不同規(guī)模的企業(yè)都在探索著合適自己的研發(fā)流程和技術(shù);而tester們的技術(shù)能力也在不斷增強(qiáng),至少能寫代碼的人比5年前多了很多。當(dāng)然,還要感謝開源世界帶來的眾多框架、組件,讓自動化的門檻不斷降低。
就像傳統(tǒng)的關(guān)鍵字驅(qū)動已經(jīng)遠(yuǎn)去一樣,新的關(guān)鍵字驅(qū)動未來會如何,大家討論吧。:-)
1、Core OS 核心層:包含Accelerate Framework、External Accessory Framework、Security Framework、System等幾個框架,基本都是基于c語言的接口
2、Core Services核心服務(wù)層:包含Address Book Framework、CFNetwork Framework、Core Data Framework、Core Foundation Framework、Core Location Framework、Core Media Framework、Core Telephony Framework、Event Kit Framework、Foundation Framework、Mobile Core Services Framework、Quick Look Framework、Store Kit Framework、System Configuration Framework、Block Objects、Grand Central Dispatch 、In App Purchase、Location Services、SQLite、XML Support等一些框架,也基本都是基于c語言的接口。
3、Mediah媒體層:包含Core Graphics、Core Animation、OpenGL ES、Core Text、Image I/O、Assets Library Framework、Media Player Framework、AV Foundation、OpenAL、Core Audio Frameworks、Core Media等等
4、 Cocoa Touch 觸摸層:包括Address Book UI Framework、Event Kit UI Framework、Game Kit Framework、iAd Framework、Map Kit Framework、Message UI Framework、UIKit Framework等等,這一層基本都是基于 Objective-c的接口
(1)數(shù)據(jù)庫創(chuàng)建在內(nèi)存卡中,大小受限,創(chuàng)建位置位于/data/data/應(yīng)用程序名/databases中(可使用Eclispe的DDMS查看)。
(2)如果無法獲取Root權(quán)限,則無法直接查看創(chuàng)建的數(shù)據(jù)庫。
鑒于上述限制及實際需要,打算使用SQLiteOpenHelper管理
SD卡上的數(shù)據(jù)庫,通過研究SQLiteOpenHelper的源碼,發(fā)現(xiàn)其創(chuàng)建或打開數(shù)據(jù)庫的代碼位于getWritableDatabase()函數(shù)中(getReadableDatabase本身也是調(diào)用了getWritableDatabase):
if (mName == null) {
db = SQLiteDatabase.create(null);
}
else {
db = mContext.openOrCreateDatabase(mName, 0, mFactory);
}
分析上述代碼發(fā)現(xiàn),當(dāng)數(shù)據(jù)庫名字為非空時,創(chuàng)建數(shù)據(jù)庫或打開由mContext完成,這個mContext由SQLiteOpenHelper的構(gòu)造函數(shù)傳入:SQLiteOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version)。那么我們對于傳入的context,重載其openOrCreateDatabase函數(shù),使其將數(shù)據(jù)庫創(chuàng)建到SD卡中就可完成我們的目標(biāo)了~。
對應(yīng)的SQLiteOpenHelper實現(xiàn)類SdCardDBHelper
import android.content.Context; import android.database.SQLException; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.util.Log; /** * 數(shù)據(jù)庫管理和維護(hù)類 **/ public class SdCardDBHelper extends SQLiteOpenHelper{ public static final String TAG = "SdCardDBHelper"; /** * 數(shù)據(jù)庫名稱 **/ public static String DATABASE_NAME = "sddb.db"; /** * 數(shù)據(jù)庫版本 **/ public static int DATABASE_VERSION = 1; /** * 構(gòu)造函數(shù) * * @param context 上下文環(huán)境 **/ public SdCardDBHelper(Context context){ super(context, DATABASE_NAME, null, DATABASE_VERSION); } /** * 創(chuàng)建數(shù)據(jù)庫時觸發(fā),創(chuàng)建離線存儲所需要的數(shù)據(jù)庫表 * * @param db **/ @Override public void onCreate(SQLiteDatabase db) { Log.e(TAG, "開始創(chuàng)建數(shù)據(jù)庫表"); try{ //創(chuàng)建用戶表(user) db.execSQL("create table if not exists user" + "(_id integer primary key autoincrement,name varchar(20),password varchar(20),role varchar(10),updateTime varchar(20))"); Log.e(TAG, "創(chuàng)建離線所需數(shù)據(jù)庫表成功"); } catch(SQLException se){ se.printStackTrace(); Log.e(TAG, "創(chuàng)建離線所需數(shù)據(jù)庫表失敗"); } } /** 更新數(shù)據(jù)庫時觸發(fā), * * @param db * @param oldVersion * @param newVersion **/ @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { //db.execSQL("ALTER TABLE person ADD COLUMN other STRING"); } } |
重載的openOrCreateDatabase在sd卡上創(chuàng)建數(shù)據(jù)庫的Context
import java.io.File; import java.io.IOException; import android.content.Context; import android.content.ContextWrapper; import android.database.DatabaseErrorHandler; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase.CursorFactory; import android.util.Log; /** * 用于支持對存儲在SD卡上的數(shù)據(jù)庫的訪問 **/ public class DatabaseContext extends ContextWrapper { /** * 構(gòu)造函數(shù) * @param base 上下文環(huán)境 */ public DatabaseContext(Context base){ super(base); } /** * 獲得數(shù)據(jù)庫路徑,如果不存在,則創(chuàng)建對象對象 * @param name * @param mode * @param factory */ @Override public File getDatabasePath(String name) { //判斷是否存在sd卡 boolean sdExist = android.os.Environment.MEDIA_MOUNTED.equals(android.os.Environment.getExternalStorageState()); if(!sdExist){//如果不存在, Log.e("SD卡管理:", "SD卡不存在,請加載SD卡"); return null; } else{//如果存在 //獲取sd卡路徑 String dbDir=android.os.Environment.getExternalStorageDirectory().getAbsolutePath(); dbDir += "/database";//數(shù)據(jù)庫所在目錄 String dbPath = dbDir+"/"+name;//數(shù)據(jù)庫路徑 //判斷目錄是否存在,不存在則創(chuàng)建該目錄 File dirFile = new File(dbDir); if(!dirFile.exists()) dirFile.mkdirs(); //數(shù)據(jù)庫文件是否創(chuàng)建成功 boolean isFileCreateSuccess = false; //判斷文件是否存在,不存在則創(chuàng)建該文件 File dbFile = new File(dbPath); if(!dbFile.exists()){ try { isFileCreateSuccess = dbFile.createNewFile();//創(chuàng)建文件 } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } else isFileCreateSuccess = true; //返回數(shù)據(jù)庫文件對象 if(isFileCreateSuccess) return dbFile; else return null; } } /** * 重載這個方法,是用來打開SD卡上的數(shù)據(jù)庫的,android 2.3及以下會調(diào)用這個方法。 * * @param name * @param mode * @param factory */ @Override public SQLiteDatabase openOrCreateDatabase(String name, int mode, SQLiteDatabase.CursorFactory factory) { SQLiteDatabase result = SQLiteDatabase.openOrCreateDatabase(getDatabasePath(name), null); return result; } /** * Android 4.0會調(diào)用此方法獲取數(shù)據(jù)庫。 * * @see android.content.ContextWrapper#openOrCreateDatabase(java.lang.String, int, * android.database.sqlite.SQLiteDatabase.CursorFactory, * android.database.DatabaseErrorHandler) * @param name * @param mode * @param factory * @param errorHandler */ @Override public SQLiteDatabase openOrCreateDatabase(String name, int mode, CursorFactory factory, DatabaseErrorHandler errorHandler) { SQLiteDatabase result = SQLiteDatabase.openOrCreateDatabase(getDatabasePath(name), null); return result; } } |
調(diào)用程序:
DatabaseContext dbContext = new DatabaseContext(this);
SdCardDBHelper dbHelper = new SdCardDBHelper(dbContext);
這里尤其值得注意的是,不同版本的android API會調(diào)用不同的openOrCreateDatabase函數(shù)。
當(dāng)然也可直接使用SQLiteDatabase創(chuàng)建SD卡上的數(shù)據(jù)庫,或者直接修改SQLiteOpenHelper的源碼重新編譯,不過前者沒有對數(shù)據(jù)庫進(jìn)行一些檢驗容錯處理,也不及SQLiteOpenHelper對數(shù)據(jù)庫操作方便。后者工作量較大,不建議采用。
最后注意記得加入對SD卡的讀寫權(quán)限:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission>
摘要
要學(xué)習(xí)Java,你必須理解構(gòu)造器。因為構(gòu)造器可以提供許多特殊的方法,這個對于初學(xué)者經(jīng)常混淆。但是,構(gòu)造器和方法又有很多重要的區(qū)別。
原作者:Robert Nielsen 原站:www.javaworld.com
我們說構(gòu)造器是一種方法,就象講澳大利亞的鴨嘴獸是一種哺育動物。(按:老外喜歡打比喻,我也就照著翻譯)。要理解鴨嘴獸,那么先必須理解它和其他哺育動物的區(qū)別。同樣地,要理解構(gòu)造器,那么就要了解構(gòu)造器和方法的區(qū)別。所有學(xué)習(xí)java的人,尤其是對那些要認(rèn)證考試的,理解構(gòu)造器是非常重要的。下面將簡單介紹一下 ,最后用一個表作了些簡單的總結(jié)。
功能和作用的不同
構(gòu)造器是為了創(chuàng)建一個類的實例。這個過程也可以在創(chuàng)建一個對象的時候用到:Platypus p1 = new Platypus();
相反,方法的作用是為了執(zhí)行java代碼。
修飾符,返回值和命名的不同
構(gòu)造器和方法在下面三個方便的區(qū)別:修飾符,返回值,命名。和方法一樣,構(gòu)造器可以有任何訪問的修飾: public, protected, private或者沒有修飾(通常被package 和 friendly調(diào)用). 不同于方法的是,構(gòu)造器不能有以下非訪問性質(zhì)的修飾: abstract, final, native, static, 或者 synchronized。
返回類型也是非常重要的。方法能返回任何類型的值或者無返回值(void),構(gòu)造器沒有返回值,也不需要void。
最后,談?wù)剝烧叩拿?gòu)造器使用和類相同的名字,而方法則不同。按照習(xí)慣,方法通常用小寫字母開始,而構(gòu)造器通常用大寫字母開始。構(gòu)造器通常是一個名詞,因為它和類名相同;而方法通常更接近動詞,因為它說明一個操作。
"this"的用法
構(gòu)造器和方法使用關(guān)鍵字this有很大的區(qū)別。方法引用this指向正在執(zhí)行方法的類的實例。靜態(tài)方法不能使用this關(guān)鍵字,因為靜態(tài)方法不屬于類的實例,所以this也就沒有什么東西去指向。構(gòu)造器的this指向同一個類中,不同參數(shù)列表的另外一個構(gòu)造器,我們看看下面的代碼:
public class Platypus {
String name;
Platypus(String input) {
name = input;
}
Platypus() {
this("John/Mary Doe");
}
public static void main(String args[]) {
Platypus p1 = new Platypus("digger");
Platypus p2 = new Platypus();
}
}
在上面的代碼中,有2個不同參數(shù)列表的構(gòu)造器。第一個構(gòu)造器,給類的成員name賦值,第二個構(gòu)造器,調(diào)用第一個構(gòu)造器,給成員變量name一個初始值 "John/Mary Doe".
在構(gòu)造器中,如果要使用關(guān)鍵字this,那么,必須放在第一行,如果不這樣,將導(dǎo)致一個編譯錯誤。
"super"的用法
構(gòu)造器和方法,都用關(guān)鍵字super指向超類,但是用的方法不一樣。方法用這個關(guān)鍵字去執(zhí)行被重載的超類中的方法。看下面的例子:
class Mammal {
void getBirthInfo() {
System.out.println("born alive.");
}
}
class Platypus extends Mammal {
void getBirthInfo() {
System.out.println("hatch from eggs");
System.out.print("a mammal normally is ");
super.getBirthInfo();
}
}
在上面的例子中,使用super.getBirthInfo()去調(diào)用超類Mammal中被重載的方法。
構(gòu)造器使用super去調(diào)用超類中的構(gòu)造器。而且這行代碼必須放在第一行,否則編譯將出錯。看下面的例子:
public class SuperClassDemo {
SuperClassDemo() {}
}
class Child extends SuperClassDemo {
Child() {
super();
}
}
在上面這個沒有什么實際意義的例子中,構(gòu)造器 Child()包含了 super,它的作用就是將超類中的構(gòu)造器SuperClassDemo實例化,并加到 Child類中。
編譯器自動加入代碼
編譯器自動加入代碼到構(gòu)造器,對于這個,java程序員新手可能比較混淆。當(dāng)我們寫一個沒有構(gòu)造器的類,編譯的時候,編譯器會自動加上一個不帶參數(shù)的構(gòu)造器,例如:public class Example {}
編譯后將如下代碼:
public class Example {
Example() {}
}
在構(gòu)造器的第一行,沒有使用super,那么編譯器也會自動加上,例如:
public class TestConstructors {
TestConstructors() {}
}
編譯器會加上代碼,如下:
public class TestConstructors {
TestConstructors() {
super;
}
}
仔細(xì)想一下,就知道下面的代碼
public class Example {}
經(jīng)過會被編譯器加代碼形如:
public class Example {
Example() {
super;
}
}
繼承
構(gòu)造器是不能被繼承的。子類可以繼承超類的任何方法。看看下面的代碼:
public class Example {
public void sayHi {
system.out.println("Hi");
}
Example() {}
}
public class SubClass extends Example {
}
類 SubClass 自動繼承了父類中的sayHi方法,但是,父類中的構(gòu)造器 Example()卻不能被繼承。
總結(jié)
|
主題 | 構(gòu)造器 | 方法 |
---|
功能 | 建立一個類的實例 | java功能語句 |
修飾 | 不能用bstract , final , native ,static , or synchronized | 能
|
返回類型 | 沒有返回值,沒有void
| 有返回值,或者void |
命名 | 和類名相同;通常為名詞,大寫開頭 | 通常代表一個動詞的意思,小寫開頭 |
this | 指向同一個類中另外一個構(gòu)造器,在第一行 | 指向當(dāng)前類的一個實例,不能用于靜態(tài)方法 |
super | 調(diào)用父類的構(gòu)造器,在第一行 | 調(diào)用父類中一個重載的方法 |
繼承 | 構(gòu)造器不能被繼承 | 方法可以被繼承 |
編譯器自動加入一個缺省的構(gòu)造器 | 自動加入(如果沒有) | 不支持 |
編譯器自動加入一個缺省的調(diào)用到超類的構(gòu)造器 | 自動加入(如果沒有) | 不支持
|
很多常見的
面試題都會出諸如抽象類和接口有什么區(qū)別,什么情況下會使用抽象類和什么情況你會使用接口這樣的問題。本文我們將仔細(xì)討論這些話題。
在討論它們之間的不同點(diǎn)之前,我們先看看抽象類、接口各自的特性。
抽象類
抽象類是用來捕捉子類的通用特性的 。它不能被實例化,只能被用作子類的超類。抽象類是被用來創(chuàng)建繼承層級里子類的模板。以JDK中的GenericServlet為例:
public abstract class GenericServlet implements Servlet, ServletConfig, Serializable { // abstract method abstract void service(ServletRequest req, ServletResponse res); void init() { // Its implementation } // other method related to Servlet } |
當(dāng)HttpServlet類繼承GenericServlet時,它提供了service方法的實現(xiàn):
public class HttpServlet extends GenericServlet { void service(ServletRequest req, ServletResponse res) { // implementation } protected void doGet(HttpServletRequest req, HttpServletResponse resp) { // Implementation } protected void doPost(HttpServletRequest req, HttpServletResponse resp) { // Implementation } // some other methods related to HttpServlet } |
接口
接口是抽象方法的集合。如果一個類實現(xiàn)了某個接口,那么它就繼承了這個接口的抽象方法。這就像契約模式,如果實現(xiàn)了這個接口,那么就必須確保使用這些方法。接口只是一種形式,接口自身不能做任何事情。以Externalizable接口為例:
public interface Externalizable extends Serializable {
void writeExternal(ObjectOutput out) throws IOException;
void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
}
當(dāng)你實現(xiàn)這個接口時,你就需要實現(xiàn)上面的兩個方法:
public class Employee implements Externalizable { int employeeId; String employeeName; @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { employeeId = in.readInt(); employeeName = (String) in.readObject(); } @Override public void writeExternal(ObjectOutput out) throws IOException { out.writeInt(employeeId); out.writeObject(employeeName); } } |
抽象類和接口的對比
什么時候使用抽象類和接口
如果你擁有一些方法并且想讓它們中的一些有默認(rèn)實現(xiàn),那么使用抽象類吧。
如果你想實現(xiàn)多重繼承,那么你必須使用接口。由于Java不支持多繼承,子類不能夠繼承多個類,但可以實現(xiàn)多個接口。因此你就可以使用接口來解決它。
如果基本功能在不斷改變,那么就需要使用抽象類。如果不斷改變基本功能并且使用接口,那么就需要改變所有實現(xiàn)了該接口的類。
Java8中的默認(rèn)方法和靜態(tài)方法
Oracle已經(jīng)開始嘗試向接口中引入默認(rèn)方法和靜態(tài)方法,以此來減少抽象類和接口之間的差異。現(xiàn)在,我們可以為接口提供默認(rèn)實現(xiàn)的方法了并且不用強(qiáng)制子類來實現(xiàn)它。這類內(nèi)容我將在下篇博客進(jìn)行闡述。
有人說若是QA早一點(diǎn)開始加入項目, 應(yīng)該可以幫助項目質(zhì)量變好, 可以幫忙厘清需求, 可以縮短
測試時間. 聽起來真的好處多多.
可是真的是這樣嗎? 我想以各位看倌多年的經(jīng)驗, 應(yīng)該會覺得不會這么容易. 是的, 是不容易, 但是原因是什么呢?
就我個人觀感第一個原因是mindset, 是的, 是mindset.
像我現(xiàn)在在run Agile, 如果大家對Agile有所認(rèn)識, 應(yīng)該知道Agile強(qiáng)調(diào)就是mindset的轉(zhuǎn)變, 如果心態(tài)沒有轉(zhuǎn)變成, 要因應(yīng)變化而積極作調(diào)整, 那你在執(zhí)行的任何practice都因而事倍功半, 最常見的就是便成mini waterfall. 因為我們只是把一個大的, 長的開發(fā)時程, 便成一個為期2 weeks 或4 weeks的小型項目. 事實上幫助會有限.
同樣的, 如果你認(rèn)為QA早一點(diǎn)進(jìn)去就會有幫助, 那同樣也是不切實際的. 因為這要work, 需要很多人的mindset都要改變, RD, QA, manager都要做修正.
在傳統(tǒng)開發(fā)流程時, 測試是最后一個階段, 因此QA養(yǎng)成一個習(xí)慣, 那就是需求要ready, design要ready, 程序要ready, 否則就無法開始. 因此不打破這個想法, QA早點(diǎn)進(jìn)去是沒有用的, 因為他會認(rèn)為這些東西都還沒有好, 他什么事也不能作. 所以還是得等到design or code ready, QA才會開始動作.
所以QA需要轉(zhuǎn)換做事的想法, 不要再認(rèn)為你只需要被動接受RD或是manager給你東西, 你需要真的積極加入, 自己去創(chuàng)造或是找出你要的東西. 也就是說早點(diǎn)跟manager討論需求, 和UI designer討論UI行為的運(yùn)作, 和RD討論design的細(xì)節(jié), error handling的細(xì)節(jié)等等. QA是可以領(lǐng)導(dǎo)或是驅(qū)動項目的進(jìn)行, 而不是單純的被動接受者.
在開立測試個案時, 心態(tài)上也要和以前不同. 你的重點(diǎn)不是要去逮到RD的小辮子, 去沖高bug的數(shù)量. 你應(yīng)該要做的是和RD一國, 一起去提升軟件的質(zhì)量. 也就是說事前就要和RD再三確認(rèn), 是否你開的這些case, RD已經(jīng)加以考慮, 不管是細(xì)部功能的運(yùn)作, 或是例外處理的部份, 都要一一確認(rèn)清楚. 如果這些東西一開始都設(shè)計進(jìn)去, 都考慮進(jìn)去, 之后就不會
有冗長的bug fixing時間. 需知道有很多bug通常, 都是因為事前沒有人說要考慮或是要處理, 導(dǎo)致于最后要花更多時間去修復(fù), 甚至還要在那邊討價還價. 若是這些事前能談清楚, 那將會節(jié)省之后很多時間的.
此外若是早點(diǎn)請RD review 過測試個案, 說不定可以知道有些測試個案可以不需要開立, 或是需要再加以補(bǔ)充. 像是有地方, 可能你開的case是在測到3 party或是別的team的code, 但是并沒有打到自己要測的部份, 像這些可能就可以不要測. 或者, 有時候因為QA對于實作細(xì)節(jié)不了解, 或是缺乏coding skill, 有些個案便會開不到, RD這時候的建議便可以幫助你補(bǔ)足你不夠的部份.
另外在設(shè)計測試自動化的時候, 更是需要和RD早點(diǎn)討論. 一方面可以讓他考慮testaability, 一方面你不會多走一些冤枉路. 有些QA因為怕麻煩RD, 獨(dú)立自行去開發(fā)測試程序, 或是來作performance
test program, 結(jié)果事后卻被RD指出, 有容易做到的方法, 或是這樣的行為可能和受測軟件架構(gòu)不同. 這時候啟不是很冤嗎?
當(dāng)然啦, 一個巴掌是拍不響的, 同樣的RD的心態(tài)也要轉(zhuǎn)變. 在設(shè)計時不要認(rèn)為QA聽不懂, 或是無法貢獻(xiàn)意見, 就不找他. 至少他加減聽的狀況下(注1), 當(dāng)你不完整的文件出來后, 他也比較容易看的懂. 當(dāng)然啦, 若是他也有coding的基礎(chǔ), 便可以很快知道你內(nèi)部運(yùn)作的行為, 對于之后測試個案的開立, 或是bug trouble shooting, 會有很大的幫助.
(注1: 之前有post篇 "招募SDET來當(dāng)QA是必要的嗎? 正確的嗎?" , QA你能加強(qiáng)這篇所說的能力, 否則RD看不起你, 你的
工作也有可能被所謂的SDET所取代.)
另外當(dāng)QA找RD作test case review時, RD也不要認(rèn)為這跟你沒有什么關(guān)系, 你需要好好看看這些scenario你是否都已經(jīng)考慮到了, 你可以趁此機(jī)會和QA一起brainstorm, 找出是否需求面或是設(shè)計面上是否有考慮不足的地方, 我想這時候花時間, 讓之后你程序沒有bug,或是bug較少, 這不是件很劃算的事情嗎?
最后, 當(dāng)然是manager也要改變心態(tài), 需知道前面這些事情要發(fā)生, 要開花結(jié)果, 都是需要時間. 若是你缺乏耐心, 覺得怎么大家前面花的時間變長了, schedule怎么delay了, 因此而責(zé)怪, 責(zé)罵, 那只會讓這件事情毀掉而已. 這時候你需要的就是穩(wěn)住, 要信任大家, 也要讓大家信任你是愿意要這改變發(fā)生.
看到這里, 我想大家應(yīng)該了解, 不是單純讓QA早點(diǎn)加入就好, mindset也是同時要做轉(zhuǎn)變的.管理, 讓大家能夠真正以起合作.
Wapiti 是一套 OpenSource 的站點(diǎn)漏洞檢測工具,比較特殊的是,它并不依賴特征
數(shù)據(jù)庫,也因此掃描的速度相當(dāng)快,而探測的則是一些共通性問題,或是作者所宣稱的未知漏洞。Wapiti 其實是一只
Python Script,可在多數(shù)平臺運(yùn)行,在網(wǎng)頁開發(fā)過程中,用這套工具反復(fù)進(jìn)行
測試,以便初步修飾一些問題,算是相當(dāng)快速而方便,,WapitiMILY: 宋體">能檢測以下漏洞:
·文件處理錯誤。
·數(shù)據(jù)庫注入(包括PHP/JSP/ASP
SQL注入和XPath注入)。
·XSS(跨站腳本)注入。
·LDAP注入。
命令執(zhí)行檢測(例如,eval(),system(),passtru()等)。
CRLF注入。
文件處理瑕疵
能執(zhí)行的系統(tǒng)命令
Database Injection
XSS Injection
LDAP Injection
CRLF (換行字符) Injection
建議采用 Python 2.5 來執(zhí)行,已內(nèi)含所需之 ctypes,我用一個正在開發(fā)中的站點(diǎn)測試,很快就找出了兩個當(dāng)初偷懶而衍生的問提,測試畫面 :
雖然很早之前就成功裝過這兩個軟件了。但是前陣子重裝了系統(tǒng)再裝這兩個軟件時卻發(fā)現(xiàn)我又把破解的方法給忘了。后來從歷史文檔中搜索了好久才得到解決。想想這些還是需要總結(jié),事情多了,難免忘記。也分享給需要的童鞋們。
LR11的破解方法
1)退出程序,把下載文件中的lm70.dll和mlr5lprg.dll覆蓋掉..\HP\LoadRunner\bin下的這兩個文件
2)注意,win7的話一定要以管理員身份運(yùn)行啟動程序,啟動后,點(diǎn)擊 configuration->loadrunner license,此時可能會有兩個許可證信息存在,退出程序,點(diǎn)擊deletelicense.exe文件,來刪除剛才得許可證信息(即時原來沒有l(wèi)isense最好也運(yùn)行一下)
3)再次打開程序, configuration->loadrunner license->new license,在彈出的輸入框中輸入license序列號AEABEXFR-YTIEKEKJJMFKEKEKWBRAUNQJU-KBYGB,點(diǎn)擊確定,驗證通過后,則破解成功!
QTP11的破解方法
1)安裝成功后,手工創(chuàng)建:C:\Program Files\Common Files\Mercury Interactive下,
建立文件夾License Manager(這個很重要,必須要創(chuàng)建)
2)拷貝破解文件mgn-mqt82.exe至第一步創(chuàng)建的目錄下。
3)運(yùn)行破解文件,提示在C:\Program Files\Common Files\Mercury Interactive下創(chuàng)建了lservrc文件
4)用記事本打開lservrc,拷貝第一個#號前的一大串字符串。運(yùn)行
QTP,輸入這串字符
5)安裝QTP的
功能測試許可服務(wù)器安裝程序(打開qtp11的安裝包里邊的)
6)刪掉SafeNet Sentinel目錄(C:\ProgramData\SafeNet Sentinel)
7)運(yùn)行\(zhòng)HP\QuickTest Professional\bin中的instdemo.exe