<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    qileilove

    blog已經轉移至github,大家請訪問 http://qaseven.github.io/

    Selenium對瀏覽器的各種操作

     第一步就是安裝Selenium這個模塊,當然,前提是你的python已經安裝好了
      直接在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()
    # go to the google home page
    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進行查找cheese!后打印標題并關閉瀏覽器
      下面介紹各種獲取瀏覽器內的各種元素的方法
    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")
      (相關的XPATH教程具體可以參考W3C的教程進行學習)
      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()
      有些選擇提取的元素是需要進行篩選的如
      # 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進行如何提交呢?
      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()
      具體的詳細用法可以參考官方文檔:http://docs.seleniumhq.org/docs/03_webdriver.jsp

    posted @ 2014-07-31 09:55 順其自然EVO 閱讀(1219) | 評論 (0)編輯 收藏

    JIRA的安裝配置

    一、安裝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 (關閉tomcat ./shutdown.sh stop)
      java -version
      屏幕輸出:
      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/
      5.安裝JIRA
      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
    出現提示:
      Unpacking JRE ...
      Starting Installer ...
      This will install JIRA 4.4 on your computer.  #安裝jira4.4在你的機器上。
      OK [o, Enter], Cancel [c]                 # 按回車確認安裝
      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 ira之間安裝或升級現有的jira安裝。
      Create a new JIRA installation. [1, Enter], Upgrade an existing JIRA installation. [2]  #直接回車,安裝新的
      #創建一個新的j ira安裝[1],更新現有的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    #存放數據路徑,本人安裝在/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不被任何其他應用占用。HTTP端口,你將通過瀏覽器訪問jira
      Use default ports (HTTP: 8080, Control: 8005) - Recommended [1, Enter], Set custom value for HTTP and Control ports [2]
      #使用默認端口(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可以在后臺運行。當計算機重啟時自動啟動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
      將插件復制到JIRA安裝路徑下:/usr/local/jira/atlassian-jira/WEB-INF/lib
      重新啟動JIRA服務:service jira start
      停止JIRA服務:server jiar stop
      6.安裝mysql驅動
      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.創建數據庫
      create database jira character set utf8;
      grant all on jira.* to 'jira'@'localhost' indentified by 'jira';
      flush privileges;
      quit;

    posted @ 2014-07-31 09:44 順其自然EVO 閱讀(300) | 評論 (0)編輯 收藏

    《我所知道的軟件測試自動化》—關鍵字驅動的過去和未來

    鑒于這個系列寫的內容是希望幫助“大多數2-3年工作經驗、急切盼望提升自身能力的 tester找到捅破‘測試自動化’窗戶紙的辦法”,所以木有高深內容,高手們請直接飄過,呵呵。
      1.“關鍵字驅動”的由來
      說到“關鍵字驅動”和“測試自動化”,就不能不提到 Mosley Daniel 的《軟件測試自動化》一書,這本書03年引入國內,04年市面上開始有賣,書中有兩個相信能吸引到很多 tester 的話題:
      (1)腳本應該錄制還是編寫?——想知道答案的自己下載電子書看吧:)
      (2)“數據驅動”和“關鍵字驅動”。
      彼時的我,剛剛經歷了一次不成功的自動化實踐,雖然Rational Robot 提供了類似UI庫管理,數據池管理之類的強大功能,但是痛苦依然。對測試自動化理解的不夠深入,不知道該如何應對RAD模式下UI和業務快速調整,以及對C/S下非標準控件的識別等問題,導致了無法快速維護腳本、replay 次數不夠,回放通過率不夠,最后的結果是ROI無法滿足要求,自動化項目宣告結束。
      帶著很多疑問的我原本想從那本書中找到些答案,遺憾的是那時功力實在太差,居然沒有看懂,唯一留下印象的就是作者在80年代就開始探索自動化回歸的技術,并且在90年代已經嘗試了“數據驅動”和“關鍵字驅動”的技術,想來當時 Robot framework 之類的都還沒有出現,所以我相信“關鍵字驅動”的技術源自這書的作者和他的朋友們。
      2. 從“數據驅動”到“關鍵字驅動”
      所謂的數據驅動,原本沒有什么特別的,無非就是把hard code 在腳本中的數據參數化出來,之所以算是Robot、WinRunner甚至QTP時代測試工具的賣點,其實主要是因為那個年代大多數system tester 不懂開發,總需要有個功能來幫助自己完成參數抽取、數據維護、自動替換之類的功能。
      而關鍵字驅動,則進一步在技術上把 tester 分成了完全不懂技術的和懂點技術的,前者只能根據格式填寫一下 excel 表格,后者對工具/框架內置的所謂關鍵字庫進行增補或二次開發。
      找些例子來看看吧。
      (1)簡單的數據驅動。
      如下面代碼,定義了一個class并實例化了幾個對象,用來存放不同的用戶登錄信息;而在負責完成登錄操作的 do_login_as() 方法中,把 send_keys 原來向表單中填充的數據參數化,根據每次調用 do_login_as() 方法時傳入的不同對象來實現用不同帳號登錄的效果——雖然我對以“登錄”為樣例介紹自動化測試腳本深惡痛絕,不過這里為了表述簡單,還是用了。(下面的代碼只是一個示例,不是直接用來運行的。)
      其實數據驅動本來也沒有什么技術含量,寫過代碼的誰還不知道什么是變量啊?不過在早期的商業工具中,的確把這個作為了一個賣點,畢竟10年前的測試工具設計思路也與現在不同。早期的工具始終都假設”系統測試工程師不懂開發“,所以他們在開發測試腳本時需要借助類似錄制回放+編輯調試的模式;另外,對于數據驅動所需要的測試數據,最好也是通過工具內置的data pool 管理,通過表格編輯,甚至讀取 csv、excel 文件之類的。如果我們依照這個思路去實現自己的測試框架,那肯定是商業工具很有價值,畢竟自己實現data pool管理、外部數據文件讀取之類的功能,代碼量也不小,要處理好那些數據格式和內容校驗之類的,貌似跟我們所需要完成的自動化也沒啥太大關系。
      可問題在于,為什么一定要有data pool+外部數據文件來實現”數據驅動“呢?
      (2)傳統的“關鍵字驅動”
      還是先用個例子來看看傳統的“關鍵字驅動”長什么樣子。
      上面是一個 Selenium 0.x 時代Core模式下(什么是selenium的core模式和RC模式請自行google,不過話說現在已經全民webdriver時代了,不知道也就不知道吧)的例子(QTP的實現效果也類似),簡單說就是第一行是 test case name,下面3列開始進入正題,第一列表示你想干啥,第二列是被操作對象是誰,第三列是你對它干了啥。06年剛剛開始嘗試web測試自動化的時候,我們一度認為這是一個比之前的測試工具要智能的多的東東,不過在嘗試了很長一段時間之后,還是發現了這種模式下的問題,又逐漸轉回了編寫腳本的方式。
    對比上面的第一個腳本,可以看出關鍵字驅動進一步屏蔽了底層的實現細節,例如,你只需要clickAndWait那個btnG,而不需要知道btnG到底是個啥,以及它在哪里,你只需要填寫表格。等你把一個個 test case 變成了一個個表格,把一個個step變成了一行行表格中的內容,基本上你的自動化測試就搞完了。——真的是這樣嗎?
      現在回想一下,關鍵字驅動之所以會出現,可能初衷還是為了降低自動化實施的門檻——因為tester都不太懂開發嘛,所以開發能力強的人把框架實現出來,原來那些只會寫excel的 system tester填寫表格就可以了。也許現在還有很多公司會傾向于這個方案,美其名曰“分工協作,人盡其能”。不過關于這個問題,暫時先不展開討論,先看看這種傳統的關鍵字驅動模式會遇到什么問題。
      首先,頁面對象的識別問題。這是任何一種基于UI實現回歸測試自動化的框架都會遇到的問題。在C/S時代,就已經出現了很多定制化UI組件難以被工具識別的問題,可好歹總逃不出windows的手心。到了web時代,前端實現技術百花齊放,而前端代碼的編寫也更是一個開發人員一個習慣,如果缺少統一的編碼規范,對于那些沒有使用id或者name之類屬性的頁面對象,傳統的關鍵字驅動框架就沒有例子中看起來那么美好了。當然,有人說xpath可以解決一切問題。嗯,xpath是很強,但是一個沒有開發經驗的tester想掌握它其實也不會比學會一點基本的編碼技術容易多少;另外,xpath并不是萬能的,在實際中還是有些它處理不了的情況。下面再給個例子(如果tester看不懂這個例子,估計學會xpath也有點困難):
      上面這個圖中是一個表格,id列(第一列)和需求名稱列(第二列)下顯示的內容,都是可以點擊的超鏈接,最后的“操作”一列中的一個個小圖標也各自指向一個鏈接(分別是“變更”、“評審”、“編輯”、“建用例”),通過查看第一行記錄的html代碼,我們發現:
      假如想要編輯一條記錄,沒有可利用的id或name屬性,唯一可用的是link;
      link指向的url中包含了這條記錄的ID信息,因為這里是第一行記錄的代碼,所以都是341,而后面的每一行記錄的代碼都是各自的ID。
      上面這個例子是企業應用中常見的場景,關鍵問題在于數據記錄ID是一個不可控因素,而數據記錄的title/name之類的是可控的,為了提高test case的可維護性和相互獨立,我們肯定不會依賴ID去模擬頁面操作,而是根據title/name之類取回ID信息,再拼裝回所需要操作的鏈接。這種情況下,xpath恐怕也搞不定了。(如果這里再涉及到要在幾個iframe之間跳來跳去,恐怕寫腳本的人就要崩潰了。)
      當然,有人會說關鍵字驅動框架一般也可以定義function來實現類似的操作啊。唉,都到了編寫自定義function的地步了,干嘛還非要糾纏在關鍵字驅動上啊。
      第二,腳本的可維護性問題。如一開始的例子,傳統的關鍵字驅動是一種純“面向過程”的腳本組織方式(就像C/pascal),表格中填寫的是一個操作序列。如果多個test case 都涉及到某個頁面,基本上就會在多個case中都看到類似 clickAndWait btnG這樣的內容,而一旦頁面中btnG改名叫buttonG了,或者 clickAndWait btnG與verifyTextPresent 之間增加了一個 clickAndWait XXX的step,那基本上每個case都需要修改。
      嗯,問題來了,當你的腳本數量從1增長到100、1000的時候,當UI的變動無法避免的時候,當你發現100個case回歸執行只有90個通過,執行失敗的10個需要逐個檢查錯誤日志和查看截圖,再挨個修改的時候,當下次回歸測試又發生這種問題的時候——基本上這就是一個死循環了。如果解決不了根本問題,前期投資可以舍棄了,別糾結了。
      當然,有人說關鍵字驅動已經進化了,可以跟最新的webdriver結合起來,提升關鍵字的封裝層次,解決這個可維護性的問題。好吧,這個問題等下再詳細說。
      第三,腳本的可擴展性問題。在大規模實施自動化的過程中,腳本一般都會簡單的是一行行的browser.find_elmenet_by_id(xxxx).click() 這樣的模式,根據各種條件來判斷執行的分支,進行各種異常處理,第三方類庫/包的調用,主機環境的訪問,諸如此類,這些對于所有的3GL/4GL來說其實都很容易實現,但對于傳統的關鍵字驅動來說,嗯,也可以實現,大概是下面這個樣子【摘自robot framework(此robot非rational robot)】:
     下面是一個在 keyword 表格里面實現的 FOR 循環:
      有人可能會說“你看,關鍵字驅動框架也可以擴展的很強大啊!”。是,在programming 的世界中,沒有什么不能做的,不過都弄到這個份兒上了,學習這一套東西跟學習一個標準的編程語言還有什么差別嗎?先不說這樣的框架越擴展越難維護,可靠性也就越差,單單這些關鍵字的用途被局限在自己的框架中,你所積累的知識和經驗無法重用到其他測試代碼的編寫中這一個理由,就應該徹底放棄這種方式了。
      如果要說的直白一些,傳統的關鍵字驅動框架的時代在前幾年就已經開始遠去(是had been,不是have been),我們感謝上一代tester的努力探索和實踐,但最終歷史證明這是一個不算成功的嘗試,一個框架如果不具備開放性,一切都自給自足,那么有一天這也會成為限制自己發展的最大原因。
      (3)穿馬甲的“關鍵字驅動”
      時代在進步,關鍵字驅動也在進步,這個領域中的代表 robot framework(此robot非rational robot) 也在進步,于是,test case 變成了下面這個樣子。
      依舊不變的是“表格”,改變的是填寫方式——其實這背后的,是關鍵字定義終于被開放出來,tester可以自己定義keyword然后“注冊”到框架中,而那些依然沒有學會基本編程技能的tester,繼續用這些keyword重復上個時代的事情——填寫表格。
      其實相對于最初對關鍵字驅動的定義,這個真的已經不是關鍵字驅動了,如果非說它是,那么只能說上個時代的關鍵字驅動中,test case 表格的每行都是一個頁面操作,而“新的”關鍵字驅動中,test case 的每行都已經是一個完整的業務操作,以上面的“Create Valid User” step 為例,robot framework希望的實現方式是tester通過python等4GL實現一個同名的function,這個function接受兩個參數,分別是“fred”和“P4ssw0rd”,再把這個function注冊到robot framework中。而“Create Valid User”內部的實現,可以類似于一開始“數據驅動”中的那個例子,充分利用4GL的特性和已有的其他第三方組件(例如webdriver),來實現各種復雜的基于UI的操作,這樣也就解決了剛剛“傳統的關鍵字驅動”所遇到的問題。
      最后,當完成了這個function的開發并在robot framework中注冊后,做手工測試的system tester就可以很容易的把原本excel中的一個個case轉變為自動化腳本了。
      其實這個思路有它的優點,例如:通過分工協作降低實施門檻,可以一開始就編寫符合robot所需格式的manual test case,等到keyword開完全了以后這些case就可以直接導入執行了;不再自給自足,而是保持一定的開放,并利用其他第三方組件的特性。這樣很大程度上解決了自動化項目實施遇到的人員能力問題和可維護性、可擴展性的問題。
      另外,新的關鍵字驅動還有一個更加先進的“近親”BDD作為參照,很容易把它的一些實踐也一起融合進來。
      一切看起來都很美好,不過問題也還是有。
      表格化的test case畢竟不同于編寫代碼,調試就變成了一個問題,如果寫錯了關鍵字的一個字母,要及時發現并定位到問題就不那么容易。當然,可以再開發一個web平臺,讓編寫case的人僅能從一個list中選擇已經定義好的keyword,不過這個成本恐怕就不是一般研發團隊能承受的了。
      作為一個軟件,易用性和復雜度總是成反比的,當框架提供了方便的表格化編寫case功能時,也相對的增加了底層的復雜度(雖然沒看過robot framework的代碼,但是相信底層代碼的分層也應該比較復雜),對于想要真的掌握框架的團隊來說,無形中增加了一道門檻。另外,復雜度與可擴展性也是成反比的,就像我可以用木頭做一輛車,也可以把木頭車拆了做些別的東西,但是我沒法把一輛汽車拆了弄成別的東西——前兩年廣東美院那位把解放卡車拆了做成關公像的牛人除外。當然,最終實施自動化時到底如何進行框架選型,就要團隊自己在易用性/復雜度/可擴展性上進行評估了。
      把excel里面的manual test case通過新的關鍵字驅動直接變成可執行的腳本是最好的方法嗎?這似乎只是一個傳統system test 的慣性思維在作怪,為什么沒看過開發人員把unit test 也寫到一個個表格里面?為什么manual test case 就一定要先寫在excel里面,而不是一開始就是代碼?
      如果僅僅是考慮把 step組裝起來,再把case組織成suite執行,其實代碼實現上可以說毫無技術含量,但是對于一個沒有開發經驗的tester來說,這畢竟是一個跟coding簡單親密接觸的機會,可以讓tester從低難度的代碼開始培養興趣和信息。而keyword,無論新的還是舊的,卻剝奪了這個機會;當tester希望學習框架的時候,會發現表格的層面跟下層框架之間的不是樓梯,而是一道溝。
      3. “關鍵字驅動”的未來
      我們如今所處的環境總是在變化著,今天與10年前相比,最大的變化就是測試行業獲得了極大的發展,大多數企業都認可了測試工作的重要性,并且開始思考如何提升測試工作自身的質量和效率,而且不同規模的企業都在探索著合適自己的研發流程和技術;而tester們的技術能力也在不斷增強,至少能寫代碼的人比5年前多了很多。當然,還要感謝開源世界帶來的眾多框架、組件,讓自動化的門檻不斷降低。
      就像傳統的關鍵字驅動已經遠去一樣,新的關鍵字驅動未來會如何,大家討論吧。:-)

    posted @ 2014-07-31 09:36 順其自然EVO 閱讀(354) | 評論 (0)編輯 收藏

    IOS操作系統的層次結構

      1、Core OS 核心層:包含Accelerate Framework、External Accessory Framework、Security Framework、System等幾個框架,基本都是基于c語言的接口
      2、Core Services核心服務層:包含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的接口

    posted @ 2014-07-30 10:56 順其自然EVO 閱讀(153) | 評論 (0)編輯 收藏

    如何在SD卡中創建數據庫

     使用Android中自帶的SQLiteOpenHelper可以完成數據庫的創建與管理,但有兩點局限:
      (1)數據庫創建在內存卡中,大小受限,創建位置位于/data/data/應用程序名/databases中(可使用Eclispe的DDMS查看)。
      (2)如果無法獲取Root權限,則無法直接查看創建的數據庫。
      鑒于上述限制及實際需要,打算使用SQLiteOpenHelper管理SD卡上的數據庫,通過研究SQLiteOpenHelper的源碼,發現其創建或打開數據庫的代碼位于getWritableDatabase()函數中(getReadableDatabase本身也是調用了getWritableDatabase):
      if (mName == null) {
      db = SQLiteDatabase.create(null);
      }
      else {
      db = mContext.openOrCreateDatabase(mName, 0, mFactory);
      }
      分析上述代碼發現,當數據庫名字為非空時,創建數據庫或打開由mContext完成,這個mContext由SQLiteOpenHelper的構造函數傳入:SQLiteOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version)。那么我們對于傳入的context,重載其openOrCreateDatabase函數,使其將數據庫創建到SD卡中就可完成我們的目標了~。
      對應的SQLiteOpenHelper實現類SdCardDBHelper
    import android.content.Context;
    import android.database.SQLException;
    import android.database.sqlite.SQLiteDatabase;
    import android.database.sqlite.SQLiteOpenHelper;
    import android.util.Log;
    /**
    * 數據庫管理和維護類
    **/
    public class SdCardDBHelper extends SQLiteOpenHelper{
    public static final String TAG = "SdCardDBHelper";
    /**
    * 數據庫名稱
    **/
    public static String DATABASE_NAME = "sddb.db";
    /**
    * 數據庫版本
    **/
    public static int DATABASE_VERSION = 1;
    /**
    * 構造函數
    *
    * @param    context 上下文環境
    **/
    public SdCardDBHelper(Context context){
    super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }
    /**
    * 創建數據庫時觸發,創建離線存儲所需要的數據庫表
    *
    * @param    db
    **/
    @Override
    public void onCreate(SQLiteDatabase db) {
    Log.e(TAG, "開始創建數據庫表");
    try{
    //創建用戶表(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, "創建離線所需數據庫表成功");
    }
    catch(SQLException se){
    se.printStackTrace();
    Log.e(TAG, "創建離線所需數據庫表失敗");
    }
    }
    /** 更新數據庫時觸發,
    *
    * @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卡上創建數據庫的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卡上的數據庫的訪問
    **/
    public class DatabaseContext extends ContextWrapper {
    /**
    * 構造函數
    * @param    base 上下文環境
    */
    public DatabaseContext(Context base){
    super(base);
    }
    /**
    * 獲得數據庫路徑,如果不存在,則創建對象對象
    * @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";//數據庫所在目錄
    String dbPath = dbDir+"/"+name;//數據庫路徑
    //判斷目錄是否存在,不存在則創建該目錄
    File dirFile = new File(dbDir);
    if(!dirFile.exists())
    dirFile.mkdirs();
    //數據庫文件是否創建成功
    boolean isFileCreateSuccess = false;
    //判斷文件是否存在,不存在則創建該文件
    File dbFile = new File(dbPath);
    if(!dbFile.exists()){
    try {
    isFileCreateSuccess = dbFile.createNewFile();//創建文件
    } catch (IOException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    }
    }
    else
    isFileCreateSuccess = true;
    //返回數據庫文件對象
    if(isFileCreateSuccess)
    return dbFile;
    else
    return null;
    }
    }
    /**
    * 重載這個方法,是用來打開SD卡上的數據庫的,android 2.3及以下會調用這個方法。
    *
    * @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會調用此方法獲取數據庫。
    *
    * @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;
    }
    }
      調用程序:
      DatabaseContext dbContext = new DatabaseContext(this);
      SdCardDBHelper dbHelper = new SdCardDBHelper(dbContext);
      這里尤其值得注意的是,不同版本的android API會調用不同的openOrCreateDatabase函數。
      當然也可直接使用SQLiteDatabase創建SD卡上的數據庫,或者直接修改SQLiteOpenHelper的源碼重新編譯,不過前者沒有對數據庫進行一些檢驗容錯處理,也不及SQLiteOpenHelper對數據庫操作方便。后者工作量較大,不建議采用。
      最后注意記得加入對SD卡的讀寫權限:
      <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission>

    posted @ 2014-07-30 10:53 順其自然EVO 閱讀(1312) | 評論 (0)編輯 收藏

    Java構造器和方法的區別

    摘要
    要學習Java,你必須理解構造器。因為構造器可以提供許多特殊的方法,這個對于初學者經常混淆。但是,構造器和方法又有很多重要的區別。
    原作者:Robert Nielsen 原站:www.javaworld.com 

    我們說構造器是一種方法,就象講澳大利亞的鴨嘴獸是一種哺育動物。(按:老外喜歡打比喻,我也就照著翻譯)。要理解鴨嘴獸,那么先必須理解它和其他哺育動物的區別。同樣地,要理解構造器,那么就要了解構造器和方法的區別。所有學習java的人,尤其是對那些要認證考試的,理解構造器是非常重要的。下面將簡單介紹一下 ,最后用一個表作了些簡單的總結。 

    功能和作用的不同
    構造器是為了創建一個類的實例。這個過程也可以在創建一個對象的時候用到:Platypus p1 = new Platypus(); 

    相反,方法的作用是為了執行java代碼。 

    修飾符,返回值和命名的不同
    構造器和方法在下面三個方便的區別:修飾符,返回值,命名。和方法一樣,構造器可以有任何訪問的修飾: public, protected, private或者沒有修飾(通常被package 和 friendly調用). 不同于方法的是,構造器不能有以下非訪問性質的修飾: abstract, final, native, static, 或者 synchronized。 

    返回類型也是非常重要的。方法能返回任何類型的值或者無返回值(void),構造器沒有返回值,也不需要void。 

    最后,談談兩者的命名。構造器使用和類相同的名字,而方法則不同。按照習慣,方法通常用小寫字母開始,而構造器通常用大寫字母開始。構造器通常是一個名詞,因為它和類名相同;而方法通常更接近動詞,因為它說明一個操作。 

    "this"的用法
    構造器和方法使用關鍵字this有很大的區別。方法引用this指向正在執行方法的類的實例。靜態方法不能使用this關鍵字,因為靜態方法不屬于類的實例,所以this也就沒有什么東西去指向。構造器的this指向同一個類中,不同參數列表的另外一個構造器,我們看看下面的代碼: 

    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個不同參數列表的構造器。第一個構造器,給類的成員name賦值,第二個構造器,調用第一個構造器,給成員變量name一個初始值 "John/Mary Doe". 

    在構造器中,如果要使用關鍵字this,那么,必須放在第一行,如果不這樣,將導致一個編譯錯誤。 

    "super"的用法 
    構造器和方法,都用關鍵字super指向超類,但是用的方法不一樣。方法用這個關鍵字去執行被重載的超類中的方法。看下面的例子: 

    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()去調用超類Mammal中被重載的方法。 

    構造器使用super去調用超類中的構造器。而且這行代碼必須放在第一行,否則編譯將出錯。看下面的例子: 

    public class SuperClassDemo { 
    SuperClassDemo() {} 


    class Child extends SuperClassDemo { 
    Child() { 
    super(); 



    在上面這個沒有什么實際意義的例子中,構造器 Child()包含了 super,它的作用就是將超類中的構造器SuperClassDemo實例化,并加到 Child類中。 

    編譯器自動加入代碼
    編譯器自動加入代碼到構造器,對于這個,java程序員新手可能比較混淆。當我們寫一個沒有構造器的類,編譯的時候,編譯器會自動加上一個不帶參數的構造器,例如:public class Example {} 
    編譯后將如下代碼: 

    public class Example { 
    Example() {} 



    在構造器的第一行,沒有使用super,那么編譯器也會自動加上,例如: 

    public class TestConstructors { 
    TestConstructors() {} 


    編譯器會加上代碼,如下: 

    public class TestConstructors { 
    TestConstructors() { 
    super; 



    仔細想一下,就知道下面的代碼 

    public class Example {} 

    經過會被編譯器加代碼形如: 

    public class Example { 
    Example() { 
    super; 



    繼承

    構造器是不能被繼承的。子類可以繼承超類的任何方法。看看下面的代碼: 

    public class Example { 
    public void sayHi { 
    system.out.println("Hi"); 


    Example() {} 


    public class SubClass extends Example { 


    類 SubClass 自動繼承了父類中的sayHi方法,但是,父類中的構造器 Example()卻不能被繼承。

    總結 

    主題構造器方法
    功能建立一個類的實例java功能語句
    修飾不能用bstractfinalnative,static, or synchronized
    返回類型沒有返回值,沒有void有返回值,或者void
    命名和類名相同;通常為名詞,大寫開頭通常代表一個動詞的意思,小寫開頭
    this指向同一個類中另外一個構造器,在第一行指向當前類的一個實例,不能用于靜態方法
    super調用父類的構造器,在第一行調用父類中一個重載的方法
    繼承構造器不能被繼承方法可以被繼承
    編譯器自動加入一個缺省的構造器自動加入(如果沒有)不支持
    編譯器自動加入一個缺省的調用到超類的構造器自動加入(如果沒有)不支持

    posted @ 2014-07-30 10:49 順其自然EVO 閱讀(141) | 評論 (0)編輯 收藏

    Java抽象類與接口的區別

    很多常見的面試題都會出諸如抽象類和接口有什么區別,什么情況下會使用抽象類和什么情況你會使用接口這樣的問題。本文我們將仔細討論這些話題。
      在討論它們之間的不同點之前,我們先看看抽象類、接口各自的特性。
      抽象類
      抽象類是用來捕捉子類的通用特性的 。它不能被實例化,只能被用作子類的超類。抽象類是被用來創建繼承層級里子類的模板。以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
    }
      當HttpServlet類繼承GenericServlet時,它提供了service方法的實現:
    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
    }
      接口
      接口是抽象方法的集合。如果一個類實現了某個接口,那么它就繼承了這個接口的抽象方法。這就像契約模式,如果實現了這個接口,那么就必須確保使用這些方法。接口只是一種形式,接口自身不能做任何事情。以Externalizable接口為例:
      public interface Externalizable extends Serializable {
      void writeExternal(ObjectOutput out) throws IOException;
      void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
      }
      當你實現這個接口時,你就需要實現上面的兩個方法:
    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);
    }
    }
    抽象類和接口的對比
      什么時候使用抽象類和接口
      如果你擁有一些方法并且想讓它們中的一些有默認實現,那么使用抽象類吧。
      如果你想實現多重繼承,那么你必須使用接口。由于Java不支持多繼承,子類不能夠繼承多個類,但可以實現多個接口。因此你就可以使用接口來解決它。
      如果基本功能在不斷改變,那么就需要使用抽象類。如果不斷改變基本功能并且使用接口,那么就需要改變所有實現了該接口的類。
      Java8中的默認方法和靜態方法
      Oracle已經開始嘗試向接口中引入默認方法和靜態方法,以此來減少抽象類和接口之間的差異。現在,我們可以為接口提供默認實現的方法了并且不用強制子類來實現它。這類內容我將在下篇博客進行闡述。

    posted @ 2014-07-30 10:13 順其自然EVO 閱讀(193) | 評論 (0)編輯 收藏

    QA和RD如何在早期就開始合作

      有人說若是QA早一點開始加入項目, 應該可以幫助項目質量變好, 可以幫忙厘清需求, 可以縮短測試時間. 聽起來真的好處多多.
      可是真的是這樣嗎? 我想以各位看倌多年的經驗, 應該會覺得不會這么容易. 是的, 是不容易, 但是原因是什么呢?
      就我個人觀感第一個原因是mindset, 是的, 是mindset.
      像我現在在run Agile, 如果大家對Agile有所認識, 應該知道Agile強調就是mindset的轉變, 如果心態沒有轉變成, 要因應變化而積極作調整, 那你在執行的任何practice都因而事倍功半, 最常見的就是便成mini waterfall. 因為我們只是把一個大的, 長的開發時程, 便成一個為期2 weeks 或4 weeks的小型項目. 事實上幫助會有限.
      同樣的, 如果你認為QA早一點進去就會有幫助, 那同樣也是不切實際的. 因為這要work, 需要很多人的mindset都要改變, RD, QA, manager都要做修正.
      在傳統開發流程時, 測試是最后一個階段, 因此QA養成一個習慣, 那就是需求要ready, design要ready, 程序要ready, 否則就無法開始. 因此不打破這個想法, QA早點進去是沒有用的, 因為他會認為這些東西都還沒有好, 他什么事也不能作. 所以還是得等到design or code ready, QA才會開始動作.
      所以QA需要轉換做事的想法, 不要再認為你只需要被動接受RD或是manager給你東西, 你需要真的積極加入, 自己去創造或是找出你要的東西. 也就是說早點跟manager討論需求, 和UI designer討論UI行為的運作, 和RD討論design的細節, error handling的細節等等. QA是可以領導或是驅動項目的進行, 而不是單純的被動接受者.
      在開立測試個案時, 心態上也要和以前不同. 你的重點不是要去逮到RD的小辮子, 去沖高bug的數量. 你應該要做的是和RD一國, 一起去提升軟件的質量. 也就是說事前就要和RD再三確認, 是否你開的這些case, RD已經加以考慮, 不管是細部功能的運作, 或是例外處理的部份, 都要一一確認清楚. 如果這些東西一開始都設計進去, 都考慮進去, 之后就不會
      有冗長的bug fixing時間. 需知道有很多bug通常, 都是因為事前沒有人說要考慮或是要處理, 導致于最后要花更多時間去修復, 甚至還要在那邊討價還價. 若是這些事前能談清楚, 那將會節省之后很多時間的.
      此外若是早點請RD review 過測試個案, 說不定可以知道有些測試個案可以不需要開立, 或是需要再加以補充. 像是有地方, 可能你開的case是在測到3 party或是別的team的code, 但是并沒有打到自己要測的部份, 像這些可能就可以不要測. 或者, 有時候因為QA對于實作細節不了解, 或是缺乏coding skill, 有些個案便會開不到, RD這時候的建議便可以幫助你補足你不夠的部份.
      另外在設計測試自動化的時候, 更是需要和RD早點討論. 一方面可以讓他考慮testaability, 一方面你不會多走一些冤枉路. 有些QA因為怕麻煩RD, 獨立自行去開發測試程序, 或是來作performance test program, 結果事后卻被RD指出, 有容易做到的方法, 或是這樣的行為可能和受測軟件架構不同. 這時候啟不是很冤嗎?
      當然啦, 一個巴掌是拍不響的, 同樣的RD的心態也要轉變. 在設計時不要認為QA聽不懂, 或是無法貢獻意見, 就不找他. 至少他加減聽的狀況下(注1), 當你不完整的文件出來后, 他也比較容易看的懂. 當然啦, 若是他也有coding的基礎, 便可以很快知道你內部運作的行為, 對于之后測試個案的開立, 或是bug trouble shooting, 會有很大的幫助.
      (注1: 之前有post篇 "招募SDET來當QA是必要的嗎? 正確的嗎?" , QA你能加強這篇所說的能力, 否則RD看不起你, 你的工作也有可能被所謂的SDET所取代.)
      另外當QA找RD作test case review時, RD也不要認為這跟你沒有什么關系, 你需要好好看看這些scenario你是否都已經考慮到了, 你可以趁此機會和QA一起brainstorm, 找出是否需求面或是設計面上是否有考慮不足的地方, 我想這時候花時間, 讓之后你程序沒有bug,或是bug較少, 這不是件很劃算的事情嗎?
      最后, 當然是manager也要改變心態, 需知道前面這些事情要發生, 要開花結果, 都是需要時間. 若是你缺乏耐心, 覺得怎么大家前面花的時間變長了, schedule怎么delay了, 因此而責怪, 責罵, 那只會讓這件事情毀掉而已. 這時候你需要的就是穩住, 要信任大家, 也要讓大家信任你是愿意要這改變發生.
      看到這里, 我想大家應該了解, 不是單純讓QA早點加入就好, mindset也是同時要做轉變的.管理, 讓大家能夠真正以起合作.

    posted @ 2014-07-30 09:59 順其自然EVO 閱讀(957) | 評論 (0)編輯 收藏

    Wapiti一款小巧的開源安全測試漏洞檢測工具

    Wapiti 是一套 OpenSource 的站點漏洞檢測工具,比較特殊的是,它并不依賴特征數據庫,也因此掃描的速度相當快,而探測的則是一些共通性問題,或是作者所宣稱的未知漏洞。Wapiti 其實是一只 Python Script,可在多數平臺運行,在網頁開發過程中,用這套工具反復進行測試,以便初步修飾一些問題,算是相當快速而方便,,WapitiMILY: 宋體">能檢測以下漏洞:
      ·文件處理錯誤。
      ·數據庫注入(包括PHP/JSP/ASP SQL注入和XPath注入)。
      ·XSS(跨站腳本)注入。
      ·LDAP注入。
      命令執行檢測(例如,eval(),system(),passtru()等)。
      CRLF注入。
      文件處理瑕疵
      能執行的系統命令
      Database Injection
      XSS Injection
      LDAP Injection
      CRLF (換行字符) Injection
      建議采用 Python 2.5 來執行,已內含所需之 ctypes,我用一個正在開發中的站點測試,很快就找出了兩個當初偷懶而衍生的問提,測試畫面 :

    posted @ 2014-07-30 09:56 順其自然EVO 閱讀(306) | 評論 (0)編輯 收藏

    Lr11、QTP11破解時需要注意的問題

      雖然很早之前就成功裝過這兩個軟件了。但是前陣子重裝了系統再裝這兩個軟件時卻發現我又把破解的方法給忘了。后來從歷史文檔中搜索了好久才得到解決。想想這些還是需要總結,事情多了,難免忘記。也分享給需要的童鞋們。
      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

    posted @ 2014-07-30 09:54 順其自然EVO 閱讀(747) | 評論 (0)編輯 收藏

    僅列出標題
    共394頁: First 上一頁 75 76 77 78 79 80 81 82 83 下一頁 Last 
    <2025年5月>
    27282930123
    45678910
    11121314151617
    18192021222324
    25262728293031
    1234567

    導航

    統計

    常用鏈接

    留言簿(55)

    隨筆分類

    隨筆檔案

    文章分類

    文章檔案

    搜索

    最新評論

    閱讀排行榜

    評論排行榜

    主站蜘蛛池模板: 在线美女免费观看网站h| 国产片AV片永久免费观看| 国产一级高清视频免费看| 亚洲中文字幕乱码熟女在线| 97碰公开在线观看免费视频| 免费在线观看视频a| 在线观看亚洲电影| 亚洲第一永久AV网站久久精品男人的天堂AV | 免费一级毛片无毒不卡| 久久精品国产亚洲AV麻豆不卡 | xxxx日本免费| 亚洲AV无码一区二三区| 免费手机在线看片| 国产卡一卡二卡三免费入口| 免费成人在线视频观看| 亚洲精品成人片在线观看精品字幕| 国产又黄又爽胸又大免费视频| 亚洲成亚洲乱码一二三四区软件| 日韩在线不卡免费视频一区| 亚洲一区在线视频| 四虎影视免费在线| 一级免费黄色大片| 午夜神器成在线人成在线人免费| 蜜芽亚洲av无码一区二区三区 | 久久亚洲精品人成综合网| 无码国产精品一区二区免费虚拟VR| 亚洲国产乱码最新视频| 亚洲国产香蕉人人爽成AV片久久 | 亚洲va精品中文字幕| 在线jlzzjlzz免费播放| 思思久久99热免费精品6| 亚洲第一AAAAA片| 亚洲欧洲免费无码| 人妻巨大乳hd免费看| 亚洲精品福利视频| 国产黄色片在线免费观看| 亚洲18在线天美| 免费成人av电影| 99热这里有免费国产精品| 亚洲色丰满少妇高潮18p| 亚洲精品无码Av人在线观看国产|