第一步就是安装
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() 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 href="http://www.google.com/search?q=cheese">cheese</a>> cheese=driver.find_element_by_link_text("cheese") By Partial Link Text <a href="http://www.google.com/search?q=cheese">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("//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
一、安装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 "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
出现提示:
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;
鉴于这个系列写的内容是希望帮助“大多数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年前多了很多。当然,还要感谢开源世界带来的众多框架、组件,让自动化的门槛不断降低。
就像传统的关键字驱动已经远去一样,新的关键字驱动未来会如何,大家讨论吧。:-)
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的接口
使用
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>
摘要
要学习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功能语句 |
修饰 | 不能用bstract , final , native ,static , or synchronized | 能
|
返回类型 | 没有返回值,没有void
| 有返回值,或者void |
命名 | 和类名相同;通常为名词,大写开头 | 通常代表一个动词的意思,小写开头 |
this | 指向同一个类中另外一个构造器,在第一行 | 指向当前类的一个实例,不能用于静态方法 |
super | 调用父类的构造器,在第一行 | 调用父类中一个重载的方法 |
继承 | 构造器不能被继承 | 方法可以被继承 |
编译器自动加入一个缺省的构造器 | 自动加入(如果没有) | 不支持 |
编译器自动加入一个缺省的调用到超类的构造器 | 自动加入(如果没有) | 不支持
|
很多常见的
面试题都会出诸如抽象类和接口有什么区别,什么情况下会使用抽象类和什么情况你会使用接口这样的问题。本文我们将仔细讨论这些话题。
在讨论它们之间的不同点之前,我们先看看抽象类、接口各自的特性。
抽象类
抽象类是用来捕捉子类的通用特性的 。它不能被实例化,只能被用作子类的超类。抽象类是被用来创建继承层级里子类的模板。以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已经开始尝试向接口中引入默认方法和静态方法,以此来减少抽象类和接口之间的差异。现在,我们可以为接口提供默认实现的方法了并且不用强制子类来实现它。这类内容我将在下篇博客进行阐述。
有人说若是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也是同时要做转变的.管理, 让大家能够真正以起合作.
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,我用一个正在开发中的站点测试,很快就找出了两个当初偷懒而衍生的问提,测试画面 :
虽然很早之前就成功装过这两个软件了。但是前阵子重装了系统再装这两个软件时却发现我又把破解的方法给忘了。后来从历史文档中搜索了好久才得到解决。想想这些还是需要总结,事情多了,难免忘记。也分享给需要的童鞋们。
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