Python是时下最热门的编程语言之一了。简洁而富有表达力的语法,两三行代码往往就能解决十来行C代码才能解决的问题;丰富的标准库和第三方库,大大节约了开发时间,使它成为那些对性能没有严苛要求的开发任务的首选;强大而活跃的社区,齐全的文档,也使很多编程的初学者选择了它作为自己的第一门编程语言。甚至有国外的报道称,Python已经成为了美国顶尖大学里最受欢迎的编程入门教学语言。
要学好一门编程语言实属不易,在初学阶段,就纠正一些错误的做法,对今后的深入
学习至关重要。有一位叫Constantine Lignos的博主,他是宾夕法尼亚儿童医院放射研究部门的博士后研究员,他最近撰写了一篇很有意义的
文章,列举了初学Python的学生们最常犯的错误,并对这些错误进行了分类和剖析,其内容提纲挈领,非常值得每个Python初学者学习。
这篇文章给出了一些在Python初学者中很常见的反模式,反模式通常是指那些不符合习惯或者会导致糟糕后果的用法。Lignos把他总结的反模式分成了四大类——迭代、性能、变量的漏洞和编程风格。下面我们逐一来看一个例子,理解这些反模式到底“反”在什么地方。
迭代
当我们需要简单迭代一个数量范围的时候,Python给了我们一个非常好用的函数:range。Lignos观察到有些初学者喜欢用range来迭代列表的下表,像下面这种形式:
for i in range(len(alist)):
print alist[i]
这代码现在还没什么问题,但已经不符合Python的习惯了。但下面的代码就有问题了:
alist = ['her', 'name', 'is', 'rio']
for i in range(0, len(alist) - 1): # 漏掉了最后一个
print i, alist[i]
我们可以看一下Python官方文档range的示例:
>>> range(1, 11)
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
range的右区间是不包含在内的,如果受了直觉或其他编程语言的影响,再减1就不对了。Lignos还列举了其他几种错误的迭代模式,我们只要记住,range应该用在迭代一个数量范围。
性能
Lignos给出了两段代码:
lyrics_list = ['her', 'name', 'is', 'rio']
words = make_wordlist()
for word in words:
if word in lyrics_list: # 线性时间
print word, "is in the lyrics"
和
lyrics_set = set(lyrics_list)
words = make_wordlist()
for word in words:
if word in lyrics_set: # 常数时间
print word, "is in the lyrics"
哪种模式效率更高?Lignos注释已经给出了答案。注释的意思是,判断一个元素是否在一组元素中存在,使用list的算法复杂度是O(n),而使用set的算法复杂度是O(1)。那是否set永远是优于list?在其他情况下,应该用哪个数据结构?Python官方Wiki有一份专门各个数据结构操作的时间复杂度的文档供参考,知道参考这份文档比答案本身更重要。至于为什么,只有Python的源码才能告诉我们。
变量的漏洞
初学者往往会假设一些不该假设的前提,对一些异常流程考虑不周。Lignos也给了一个例子:
for idx, value in enumerate(y):
if value > max_value:
break
processList(y, idx)
这里y如果是空的,那就出问题了,因为idx根本得不到定义,最终我们会得到一个NameError的异常。比较好的做法是给idx一个默认的错误值,在C语言里面我们经常喜欢用-1。下面的代码就考虑得比较全面:
def find_item(item, alist):
# 对Python来说None比-1可能更好点
result = -1
for idx, other_item in enumerate(alist):
if other_item == item:
result = idx
break
return result
代码风格
Python有一份代码风格指导文档PEP 8,这些规则都是有道理的。当初学者不明白为什么的时候,最好的做法就是尽量遵守它,等到有更深入的理解了就会豁然开朗,同事也会明白什么时候可以打破规则。Lignos引用了这份文档的一些例子,比如,如何测试一个变量是否为空,如果测试一个变量是否为None等等。
以上分析了这篇文章中的一些例子,如果希望全面了解,请查看原文。想要学好一门编程语言,学会语法是远远不够的,必须逐步理解语言所依赖的CPU体系结构、编译器/解释器/虚拟机等内容。Lignos这篇文章虽然没有深入剖析Python的实现,但是对于入门者的帮助是非常明显的,当初学者有了一定经验后,对一些问题都可以做深入挖掘,引出Python实现层面的问题。
软件职业生涯总结
产品流程为:产品立项---产品定义--产品设计开发---提交产品---开发人员测试(开发部有一人专测)----产品部验证产品(转下)
1)有BUG转到开发部门进行修复,修改后再次验证,验证通过转到第2点
2)无BUG直接与中间件通讯进行资费测试
项目二:智能视频监控软件测试(C/S B/S 版测试)
产品流程为:产品立项----产品设计开发---提交产品---测试人员根据实现功能进行测试--BUG提交---BUG修复---BUG关闭
测试内部流程: 编写测试方案---编写测试用例--提交新版本执行用例---BUG提交与跟踪---BUG的修复与验证----测试回归测试(回归只针对修改部分进行详细测试,其它未改动部分正常
功能测试)--多个基线回归测试---后期使用手册的编写
项目三:APP应用
产品流程:产品市场调研---产品需求定义---产品设计开发---测试----回归测试----测试报告---上线
测试内部流程:熟悉需求---编写测试用例---执行测试用例---回归测试---编写简洁测试报告---产品上线测试
以上为本人所在公司的一些
工作流程,个人以为都不太完善。因为都是一些小公司很多流程就省略了,都说一些大公司的流程比较规范,各位大侠一起分享哟!
以下为个人对流程的一些想法,请多多指教!
软件生命周期:
产品产项---产品定义---产品需求----需求评审(个人觉得测试很有必要参加这个评审会议)---确定需求---产品设计---产品编码----产品成型----测试---回归测试---测试报告---维护
(产品成型后如若能安排时间与测试人员互动,让测试人员了解开发的一些设计逻辑或业务流程对测试人员那是相当的有帮助,目前所有公司软件的业务流程都是测试人员一个个去问的,想深度发现BUG一定要了解业务流程,否则只能发现一些表面的问题)
IOS应用测试流程一:
第一步:遍历自己模块,查看大的功能点是否已实现
1)未实现 拒绝测试转给开发人员内测
2)已实现 转到正常流程测试,转第二步
1)优先执行正常功能的用例
2)执行异常的用例
3)按模块执行用例,即正常的异常的一起执行
此不知各位觉得哪个好些呢,我们实际操作都是按的3来的,每次时间都觉得很紧的?
第三步:BUG的提交与跟踪
提交的BUG即使告知开发人员,功能BUG直接描述,对于一些涉及到UI的问题截图加附件以便开发人员知道具体的现象。
第四步:提交新的基线测试
1)验证上一轮BUG修复情况,未修复转给开发人员;已修复关闭BUG
2)验证BUG完毕后进行正常功能的验证,时间允许的话执行正常功能用例
第五步:重复第四步,执到所有BUG均已修复,或大部分BUG已修复
第六步:遍历所有模块的正常功能测试(测试环境),提交测试环境测试报告
第七步:生产环境的测试(所有正常功能的测试),提交生产环境测试报告
第八步: 产品上线后的验证测试
OpenAcs从ACS测试演变过来,由于我是实习生,在公司还没呆两天,所以搭建建个平台先练练手,顺便熟悉下公司的业务.
顺便提一下,我实习公司是NetCore.
搭建大体流程以及软件
搭建环境:
Windows7X64,其他环境自测
所需软件(其他版本自测):
jdk-6u45-windows-x64
jboss-4.2.3.GA
mysql-5.5.28-winx64
还有openacs的相关文件,我是从http://sourceforge.net/projects/openacs/files/?source=navbar的虚拟机里面搞出来的:
acs.ear
openacs-ds.xml
openacs-service.xml
openacs-bin-0.4.zip(貌似这些openacs文件csdn上面有的,去找找吧)
安装流程:
2.解压JBOSS到某个目录下,直接进行环境配置
JBOSS_HOME .;D:\jboss-4.2.3.GA
path D:\jboss-4.2.3.GA\bin
运行run.bat(如果上述环境配置成功,直接在CMD下运行run.bat)
访问http://localhost:8080, 能访问说明JDK和JBOSS均安装成功.
3.安装mysql 建立用户 建立数据库 并授予新用户权限 安装好mysql后
在cmd下mysql -u root -p(未设置root密码的,百度mysql root密码设置 记住默认密码为空,直接回车)
我的版本的MySql建立用户时用如下
insert into mysql.user(Host,User,Password)values("localhost","openacs",password("openacs"));
之后刷新数据库
flush privileges;
再quit下,测试用openacs账号是否能成功登入
切换到root进行数据库创建及授权
create database acs ;
flush privileges;
grant select,insert,update,delete,create,drop,index on acs.* to openacs@localhost identified by 'openacs';
4.连接数据库
将mysql-connector-java-5.1.18.jar
复制到$(JBOSS_HOME)/server/default/lib/.如果没有就先下载。
5.配置openacs
复制acs.ear到JBOSS_HOME \server\default\deploy ,不修改。
复制openacs-ds.xml到JBOSS_HOME\server\default\deploy
这里需要修改openacs-ds.xml中数据库连接的用户名和密码,打开该文件,设置用户名和密码为我们之前设置的用户,即openacs,
还要修改地址这里将connection-url>的值设置成jdbc:mysql://localhost/ACS,因为我的数据库在本机。
复制openacs-service.xml到JBOSS_HOME\server\default\deploy\jms,不需要修改。
6.开启服务并测试
运行JBoss控制台run.bat
开启mysql服务
对于一“新新”事物(对测试者而言的:测试内容或对象),根据自己预先设置的测试流程,
测试用例,测试方法,进行一种尝试性的测试。
在测试过程中要注意几点:
1.测试过程,就是一个不断对于自己设计的测试流程,测试用例(TestCase),测试方法的一种检验,同时也要不断思考新的,好的方法或用例;
2.测试过程是强调个人的主观能动性,这个过程强调了,作为一个测试人员,你的意愿,你的行为将在很大程度上影响者这个测试执行力度;
3.不断的积累,由于对于测试的不断深入,也开始逐步的了解测试的内容,这时,就要不断的记录,累积,和重构你的测试,逐渐的区建立一种完整的,全面的测试方案。
就个人当前的水平,认为,对于一般的
软件测试————
黑盒测试,主要进行一下几个方面的测试用例编写,以及执行
(1)功能性测试
(2)文档测试
(4)性能测试
(5)兼容性测试
(6)环境配置测试
…………
当然这只是说了测试应该进行过程中应该考虑的测试方面,在编写测试用例过程中,你要考虑一个重要的因素就是,如何将测试方法,
测试技术体现在测试用例中。通过对测试用例的执行就可以完成我们所预期的测试覆盖率,以及测试的全面的完整。
这里可以重一下三个方面的探索:
1.测试的对象的属性,你测试的对象是什么,你对它的了解程度————越熟悉越好
2.个人的经验,自己的个人经验的积累有时是一种很好的参考方式(如果没有,那慢慢积累)
3.不断地与人交流,沟通的方式,有时可以帮助我们获得意想不到的收获,这就是语言的力量,沟通的魅力
…………
这些只是个人的一点点小感悟吧~~~·
今天,别人问我一个问题:
数据库中的左连接和右连接有什么区别?如果有A,B两张表,A表有3条数据,B表有4条数据,通过左连接和右连接,查询出的数据条数最少是多少条?最多是多少条?
我被这个问题问住了,后来我去问了数据库开发人员,结果结果各种各样:
a 最大12 最小0
b 最大12 最小未知
c 最大未知 最小为3
d 最大12 最小为3
e 不清楚
1、说明
(1)左连接:只要左边表中有记录,数据就能检索出来,而右边有
的记录必要在左边表中有的记录才能被检索出来
(2)右连接:右连接是只要右边表中有记录,数据就能检索出来
2、举例说明
新建两张表,分别为t_left_tab和t_right_tab
将t_left_tab作为左边表,t_right_tab作为右边
左连接:SELECT * FROM t_left_tab a LEFT JOIN t_right_tab b ON a.`id` = b.`id`;
查询结果:
右连接:SELECT * FROM t_right_tab a LEFT JOIN t_left_tab b ON a.`id` = b.`id`;
查询结果:
查询最大条数:SELECT * FROM t_left_tab a LEFT JOIN t_right_tab b ON 1=1;
查询结果:
3、总结
A 数据库左连接和右连接的区别:主表不一样
B 通过左连接和右连接,最小条数为3(记录条数较小的记录数),最大条数为12(3×4)
不小心重用了流
我敢打赌,每人至少都会犯一次这样的错误。就像现有的这些“流”(比如说InputStream),你也只能对它们消费一次。下面的代码是无法
工作的:
IntStream stream = IntStream.of(1, 2);
stream.forEach(System.out::println);
// That was fun! Let's do it again!
stream.forEach(System.out::println);
你会碰到一个这样的错误:
java.lang.IllegalStateException:
stream has already been operated upon or closed
因此使用流的时候应当格外注意。它只能消费一次。
不小心创建了一个”无限"流
你可能一不留神就创建了一个无限流。就拿下面这个例子来说:
IntStream.iterate(0, i -> i + 1)
.forEach(System.out::println);
流的问题就在于它有可能是无限的,如果你的确是这样设计的话。唯一的问题就是,这并不是你真正想要的。因此,你得确保每次都给流提供一个适当的大小限制:
// That's better
IntStream.iterate(0, i -> i + 1)
.limit(10)
.forEach(System.out::println);
不小心创建了一个“隐藏的”无限流
这个话题是说不完的。你可能一不小心就真的创建了一个无限流。比如说下面的这个:
IntStream.iterate(0, i -> ( i + 1 ) % 2)
.distinct()
.limit(10)
.forEach(System.out::println);
这样做的结果是:
我们生成了0和1的交替数列
然后只保留不同的数值,比如说,一个0和一个1
然后再将流的大小限制为10
最后对流进行消费
好吧,这个distinct()操作它并不知道iterate()所调用的这个函数生成的只有两个不同的值。它觉得可能还会有别的值。因此它会不停地从流中消费新的值,而这个limit(10)永远也不会被调用到。不幸的是,你的应用程序会崩掉。
不小心创建了一个”隐藏”的并行无限流
我还是想继续提醒下你,你可能真的一不小心就消费了一个无限流。假设你认为distinct()操作是会并行执行的。那你可能会这么写:
IntStream.iterate(0, i -> ( i + 1 ) % 2)
.parallel()
.distinct()
.limit(10)
.forEach(System.out::println); 现在我们可以知道的是,这段代码会一直执行下去。不过在前面那个例子中,你至少只消耗了机器上的一个CPU。而现在你可能会消耗四个,一个无限流的消费很可能就会消耗掉你整个系统的资源。这可相当不妙。这种情况下你可能得去重启服务器了。看下我的笔记本在最终崩溃前是什么样的:
操作的顺序
为什么我一直在强调你可能一不小心就创建了一个无限流?很简单。因为如果你把上面的这个流的limit()和distinct()操作的顺序掉换一下,一切就都OK了。
IntStream.iterate(0, i -> ( i + 1 ) % 2)
.limit(10)
.distinct()
.forEach(System.out::println);
现在则会输出:
0
1
为什么会这样?因为我们先将无限流的大小限制为10个值,也就是(0 1 0 1 0 1 0 1 0 1),然后再在这个有限流上进行归约,求出它所包含的不同值,(0,1)。
当然了,这个在语义上就是错误的了。因为你实际上想要的是数据集的前10个不同值。没有人会真的要先取10个随机数,然后再求出它们的不同值的。
如果你是来自SQL背景的话,你可能不会想到还有这个区别。就拿SQL Server 2012举例来说,下面的两个SQL语句是一样的:
-- Using TOP
SELECT DISTINCT TOP 10 *
FROM i
ORDER BY ..
-- Using FETCH
SELECT *
FROM i
ORDER BY ..
OFFSET 0 ROWS
FETCH NEXT 10 ROWS ONLY
因此,作为一名SQL用户,你可能并不会注意到流操作顺序的重要性。
还是操作顺序
既然说到了SQL,如果你用的是MySQL或者PostgreSQL,你可能会经常用到LIMIT .. OFFSET子句。SQL里全是这种暗坑,这就是其中之一。正如SQL Server 2012中的语法所说明的那样,OFFSET子名会优先执行。
如果你将MySQL/PostgreSQL方言转化成流的话,得到的结果很可能是错的:
IntStream.iterate(0, i -> i + 1)
.limit(10) // LIMIT
.skip(5) // OFFSET
.forEach(System.out::println);
上面的代码会输出:
5
6
7
8
9
是的,它输出9后就结束了,因为首先生效的是limit(),这样会输出(0 1 2 3 4 5 6 7 8 9)。其次才是skip(),它将流缩减为(5 6 7 8 9)。而这并不是你所想要的。
警惕LIMIT .. OFFSET和OFFSET .. LIMIT的陷阱!
使用过滤器来遍历文件系统
这个问题我们之前已经讲过了。使用过滤器来遍历文件系统是个不错的方式:
Files.walk(Paths.get("."))
.filter(p -> !p.toFile().getName().startsWith("."))
.forEach(System.out::println);
看起来上面的这个流只是遍历了所有的非隐藏目录,也就是不以点号开始的那些目录。不幸的是,你又犯了错误五和错误六了。walk()方法已经生成一个当前目录下的所有子目录的流。虽然是一个惰性流,但是也包含了所有的子路径。现在的这个过滤器可以正确过滤掉所有名字以点号开始的那些目录,也就是说结果流中不会包含.git或者.idea。不过路径可能会是:..git\refs或者..idea\libraries。而这并不是你实际想要的。
你可别为了解决问题而这么写:
Files.walk(Paths.get("."))
.filter(p -> !p.toString().contains(File.separator + "."))
.forEach(System.out::println);
虽然这么写的结果是对的,但是它会去遍历整个子目录结构树,这会递归所有的隐藏目录的子目录。
我猜你又得求助于老的JDK1.0中所提供的File.list()了。不过好消息是, FilenameFilter和FileFilter现在都是函数式接口了。
修改流内部的集合
当遍历列表的时候,你不能在迭代的过程中同时去修改这个列表。这个在Java 8之前就是这样的,不过在Java 8的流中则更为棘手。看下下面这个0到9的列表:
// Of course, we create this list using streams:
List<Integer> list =
IntStream.range(0, 10)
.boxed()
.collect(toCollection(ArrayList::new));
现在,假设下我们在消费流的时候同时去删除元素:
list.stream()
// remove(Object), not remove(int)!
.peek(list::remove)
.forEach(System.out::println);
有趣的是,其中的一些元素中可以的删除的。你得到的输出将会是这样的:
0
2
4
6
8
null
null
null
null
null
java.util.ConcurrentModificationException
如果我们捕获异常后再查看下这个列表,会发现一个很有趣的事情。得到的结果是:
[1, 3, 5, 7, 9]
所有的奇数都这样。这是一个BUG吗?不,这更像是一个特性。如果你看一下JDK的源码,会发现在ArrayList.ArraListSpliterator里面有这么一段注释:
/* * If ArrayLists were immutable, or structurally immutable (no * adds, removes, etc), we could implement their spliterators * with Arrays.spliterator. Instead we detect as much * interference during traversal as practical without * sacrificing much performance. We rely primarily on * modCounts. These are not guaranteed to detect concurrency * violations, and are sometimes overly conservative about * within-thread interference, but detect enough problems to * be worthwhile in practice. To carry this out, we (1) lazily * initialize fence and expectedModCount until the latest * point that we need to commit to the state we are checking * against; thus improving precision. (This doesn't apply to * SubLists, that create spliterators with current non-lazy * values). (2) We perform only a single * ConcurrentModificationException check at the end of forEach * (the most performance-sensitive method). When using forEach * (as opposed to iterators), we can normally only detect * interference after actions, not before. Further * CME-triggering checks apply to all other possible * violations of assumptions for example null or too-small * elementData array given its size(), that could only have * occurred due to interference. This allows the inner loop * of forEach to run without any further checks, and * simplifies lambda-resolution. While this does entail a * number of checks, note that in the common case of * list.stream().forEach(a), no checks or other computation * occur anywhere other than inside forEach itself. The other * less-often-used methods cannot take advantage of most of * these streamlinings. */ |
现在来看下如果我们对这个流排序后会是什么结果:
list.stream()
.sorted()
.peek(list::remove)
.forEach(System.out::println);
输出的结果看起来是我们想要的:
而流消费完后的列表是空的:
[]
也就是说所有的元素都正确地消费掉并删除了。sorted()操作是一个“带状态的中间操作”,这意味着后续的操作不会再操作内部的那个集合了,而是在一个内部的状态上进行操作。现在你可以安全地从列表里删除元素了!
不过,真的是吗这样?我们来试一下带有parallel(), sorted()的删除操作:
list.stream()
.sorted()
.parallel()
.peek(list::remove)
.forEach(System.out::println);
这个会输出 :
现在列表里包含:
[8]
唉呀。居然没有删完所有的元素?!谁能解决这个问题,我免费请他喝酒!
这些行为看起来都是不确定的,我只能建议你在使用流的时候不要去修改它内部的数据集合。这样做是没用的。
忘了去消费流
你觉得下面这个流在做什么?
IntStream.range(1, 5)
.peek(System.out::println)
.peek(i -> {
if (i == 5)
throw new RuntimeException("bang");
});
看完这段代码,你觉得应该会输出(1 2 3 4 5)然后抛出一个异常。不过并不是这样。它什么也不会做。这个流并没有被消费掉,它只是静静的待在那里。
正如别的流API或者DSL那样,你可能会忘了调用这个终止操作。当你使用peek()的时候也是这样的,因为peek有点类似于forEach()。
在jOOQ中也存在这样的情况,如果你忘了去调用 execute()或者fetch():
DSL.using(configuration)
.update(TABLE)
.set(TABLE.COL1, 1)
.set(TABLE.COL2, "abc")
.where(TABLE.ID.eq(3));
杯具。忘了调用execute方法了。
并行流死锁
终于快讲完了~
如果你没有正确地进行同步的话,所有的并发系统都可能碰到死锁。现实中的例子可能不那么明显,不过如果你想自己创造一个场景的话倒是很容易。下面这个parallel()流肯定会造成死锁:
Object[] locks = { new Object(), new Object() }; IntStream .range(1, 5) .parallel() .peek(Unchecked.intConsumer(i -> { synchronized (locks[i % locks.length]) { Thread.sleep(100); synchronized (locks[(i + 1) % locks.length]) { Thread.sleep(50); } } })) .forEach(System.out::println); |
注意这里Unchecked.intConsumer()的使用,它把IntConsumer接口转化成了 org.jooq.lambda.fi.util.function.CheckedIntConsumer,这样你才可以抛出已检查异常。
好吧。这下你的机器倒霉了。这些线程会一直阻塞下去:-)
不过好消息就是,在Java里面要写出一个这种教科书上的死锁可不是那么容易。
想进一步了解的话,可以看下Brian Goetz在StackOverflow上的一个回答。
结论
引入了流和函数式编程之后,我们开始会碰到许多新的难以发现的BUG。这些BUG很难避免,除非你见过并且还时刻保持警惕。你必须去考虑操作的顺序,还得注意流是不是无限的。
流是一个非常强大的工具,但也是一个首先得去熟练掌握的工具。
’ 首先获取WebTable对象,然后通过ChildItem获取指定单元格中的链接对象,并单击该链接:
’ 获取WebTable对象
Set objTable = Browser("请登录 博都网").Page("我的
文章").WebTable("文章标题")
intRow = 2
intCol = 1
’ 通过ChildItem获取单元格中的链接对象
Set objLink = objTable.ChildItem(intRow, intCol, "Link" , 0)
’ 单击链接
objLink.click
_______________________________________________
’获取webtable的行数,并查找指定的内容
numRows = Browser("请登录 博都网_2").Page("我的文章").WebTable("文章标题").RowCount
For i=2 to numRows
title_text =Browser("请登录 博都网_2").Page("我的文章").WebTable("文章标题").GetCellData(i,1)
If trim(title_text) = trim(articleTitle) Then
reporter.ReportEvent 0,"
测试成功","文章列表显示新添加的文章标题"
Exit For
End If
Next
’如果table有很多页,数据不在第一页,怎么查找?
’’’’’’’’’’’Function : 在webTable中查找元素’’’’’’’’’
Function FindedItemInTable(pageObj1,WebTable_Obj1,searchStr,PageNum)
’ Finded :标记是否从table里找到数据
’ currentPage:当前页;numRows:当前页的行数
Dim Finded,currentPage
Finded = false
’从第一页开始查找。
For currentPage = 1 to PageNum
’==================初始化:保证从第一页开始搜索==========================
’ If currentPage = 1 Then
’ pageObj.Link("pageLink").SetTOProperty "text",1
’ pageObj.Link("pageLink").SetTOProperty "href","javascript:forward(1)"
’ If pageObj.Link("text:=1","href:=javascript:forward(1)").Exist(1) Then
’ pageObj.Link("text:=1").Click
’ End If
’ End If
’============================================
If currentPage >1 Then
pageObj.Link("pageLink").SetTOProperty "text",currentPage
pageObj.Link("pageLink").SetTOProperty "href","javascript:forward("¤tPage&")"
pageObj.Link("text:="¤tPage).Click
pageObj.Sync
msgbox pageObj.GetROProperty("url")
End If
’获得当前页的行数,开始逐行搜索
numRows = WebTable_Obj.GetROProperty("rows")
msgbox numRows
For i=2 to numRows
ItemStr =Cstr(WebTable_Obj.GetCellData(i,1))
If trim(ItemStr) = trim(searchStr) Then
reporter.ReportEvent 0,"搜索成功","元素位于第"¤tPage&"页,第"&i&"行"
Finded = true
Exit For
End If
Next
’找到了,退出
If finded = true Then
Exit For
End If
Next
If finded = false Then
msgbox "没有找到数据!"
End If
在
敏捷开发中, 我们都知道要将功能切割, 每次做些小功能, 然后持续交付价值给客户.
因此当你在开发每个小功能时, 你会不断进行以下事情:
1. 从主干 check out 程序代码到分支
2. 开发团队在分支进行开发
3. 小功能开发完后, 将分支程序, merge 回主干
可是通常这样在第四步时, 就会遇到一堆错误. 这是因为小功能还没确认是否正确, 就和整个系统和起来测试, 将导致问题多多. 如果有很多小功能要放进来时, 这种情况就会更恶化.
因此有些团队可能会这样做:
1. 从主干 check out 程序代码到分支
2. 开发团队在分支进行开发
3. 开发完毕在分支进行测试
4. 在分支测试通过, 将分支程序, merge 回主干
5. 在主干再进行测试
这样做之后, 可以让小
功能测试比较稳定后, 再放到主干来. 可是遇到多个小功能同时开发时, 还是会遇到你进来的东西会跟别人不和, 导致整个系统无法运作.
所以下一步你会在这样改进:
1. 从主干 check out 程序代码到分支
2. 开发团队在分支进行开发
3. 开发完毕在分支进行测试
4. 把主干的程序 merge 到分支
5. 把 merge 完后的分支程序进行测试
6. 将 merge 完后的分支程序, 再 merge 回主干
7. 在主干再进行测试
因此你先确认小功能是否运作正常; 然后将主干的程序合并到分支后, 再确认是否正确; 最后合并到主干后, 在做一次确认是否都正常.
看起来到目前为止, 应该考虑的很周到.
可是... 有多少人这样做呢? 似乎很少, 为什么正确的事情, 大家都不做呢?因为这样反复进行的测试
工作, 如果你没有自动化, 你就会没有空, 或者讨厌去做这样的事情, 导致大家就很少去做.
有些人说没问题, 我们会把测试自动化搞好, 这是小事. 于是他们就开始处理测试自动化的问题, 接着你又会发现到:
要能自动产生 build, 否则每次手动要花多时间
测试环境要自动准备好, 没有干净的环境, 测试结果可能会有影响
每个小功能整合到主干后, 有可能之后出问题, 要重新回上个版本, 这个事情若是手动做, 也是件崩溃的事情
……
所以再做下去, 你会发现整件事情没有你想象的单纯, 若是没有落实 continuous integration 或是 continuous delivery, 你永远没有机会达到 agile 所说的, 每个 iteration 持续交付价值给客户. 你所有的, 将会是至少落后一个 iteration 的交付. 因为在 agile 中, 每个 iteration 测试和开发要花的代价不同, 测试的代价是随着 iteration 的进行, 逐渐高升.
David: 老板, agile 不是只是去上上 scrum 课就可以的.
经理: 测试自动化我早就知道了, 所以 agile 根本没有什么好学的啦
David: …
软件生命周期中,需求是整个项目的源头。俗话说良好的开端是成功的一半,但是,不是每个项目都能遵从流程,花太多时间在
需求分析上,而把精力投入到代码的编写中。这可能导致什么问题呢?开发和
测试对需求理解都不充分,开发出来的功能与实际需求不符,测试要么凭自己的经验一步一步发掘潜藏的功能点,把项目往良性的轨道上引,要么就听从开发的脚步,亦步亦趋。那么测试人员要如何在需求不明确或者文档不全的情况下着手测试才能保证软件的质量呢?
一、参考同类型的网站:一般情况下,我们测的系统总会有原型可参考,比如我目前测的订票系统,就可以参照携程,主要功能基本一样,细节可能有出入,携程上操作一遍,然后再看看它提供的帮助,再有可以网上搜索资料,参考下行业的基础知识,这都是收集需求的方法,这样子下来也基本对订票系统也就有个认识了,然后与开发经理确认,再和开发一起把功能定下来,该改的改,该修的修,BUG一点不含糊。弱弱的吐槽下,这个项目的开发居然都不知道有需求说明书,虽然写的基本没太大的价值。
二、根据经验和常识判断:做项目多了就知道,万变不离其宗,系统不一样,思路可以套用,所谓的学以致用,举一反三。我上个项目做的银行测试,我照样可以测现在的订票系统,就在一个字:活。有需求照需求测,没有需求找参照,说个例子吧,订票系统中有个功能是积分,需求上就一句话提到有积分功能,怎么测?难道看到有这个功能就算过了?我后来就参照了淘宝的积分抵扣,下单了积分就被临时冻结了,取消订单又释放出来,但开发在做这个功能的时候也没有跟他们的头沟通,直接是要等支付完成后才扣除积分,这导致一个什么问题呢,可以重复使用积分,如果真上线使用了,说不定会造成不小的经济损失的。类似这样的问题太多太多,你总可以从一些地方获得参照,或者说是灵感,项目测的怎么样,跟测试人员的素养有很大的关系,尤其是在没有规范的流程下。
三、沟通:毋庸置疑,这太重要了,需求不一定在文档中写出来,但道理上开发肯定知道需求的,但一般不会主动和测试沟通,因此,我们作为测试就要主动和开发人员沟通,不仅可以对系统有更深的了解,还可以对项目进度有把握。
四、多和同事讨论
从业
电子商务网站
测试一年多,很久没有进行测试的经验方面的总结了,今天对之前测试的电子商务网站进行了一次总结,总体按照两种模式进行划分总结:1.按照测试类型 2.按照电子商务网站的系统架构
1.按照测试类型来划分
1.兼容性
1.1主要是在浏览器兼容(
360浏览器IE6 IE8浏览器)
12.操作系统,主要体现在
操作系统兼容(xp win2003 win2007)
2.UI测试
2.1检查连接是否正确
2.2是否有文字错误信息
2.2产品价格是否有显示错误。
3.用户体验测试UE
3.1首页产品的展示与分类
3.2搜索结果页,搜索结果的正确性,和结果页面信息的展示
3.3产品详情页。产品介绍页面将对客户的购买行为起到关键的作用,产品图片,文字说明,产品描述就相当于柜台服务员与产品说明书。
4.购物流程及购物规则测试
4.1B2C网站最重要的流程就是购物流程,包括几个重要功能:购物车、配送方式、支付方式、提交订单。这一流程的用户体验的重要性在于让客户能很容易的完成下单的过程
测试产品能否放入购物车中
4.2当某种产品有购物数量限制时,超过这一数值,能否也能放入购物车中
4.3购物车中的购物限制是否正确
4.4积分是否能够兑换正品,或者限购的产品
4.5积分是否能够兑换促销类的产品。
5.支付流程
5.1购物车中的产品能否正常支付
5.2当支付完成,不等页面跳转,直接关闭浏览器,数据传递是否正确
5.3当支付完成,等待页面跳转,跳转到得页面是否正确
5.4网站某个模块间的数据传递是否正确
6.订单流程测试
6.1订单提交完后,对订单的处理流程的验证,以及相应的订单状态核对是否正确。
7.性能,响应速度测试。
7.1并发性测试如秒杀功能、
7.2同时购买同一个限定产品
7.3相同的帐号进行2次积分兑换产品的并发操作。
7.4提交订单时,并发操作时是否存在多个订单。
重点:
在进行测试时,首先需要清楚应用系统的输入流,数据流的情况,在基本功能通过的情况下,才能进行其它的类型的测试。
电子商务网站测试应该关注:
1.业务流和数据流
2.重点在用户体验测试方面吧
3.还有安全和性能方面的。web的cookies测试也是重点。
2.按照电子商务网站的系统架构
1典型系统结构(目前都是采用的三层C/S架构,即1.表示层,2.业务层,和3.数据层(页面与数据库交互的)。层)。
1.表示层(测试检查应用程序的前端是否正确)
1.1内容测试(检查web应用系统提供的信息,正确性,准确性,相关的操作元素是否正确等)。
1.2web站点结构(检查web站点结构上存在的错误,发现无效的连接)
包含三个方面:
1.是否存在空连接地址 2.是否连接到指定的页面3.是否存在连接报错。
1.3用户环境(浏览器兼容操作系统兼容)
2.业务逻辑层(业务逻辑层主要是为了发现业务逻辑中存在的问题)。
2.1性能测试(负载测试是为了测量Web系统在某一负载级别上的性能,以保证Web系统在需求范围内能正常工作).
2.2数据验证。测试用户采集的数据,通常都是以表单的形式。比如说用户注册(必须保证用户的数据一致性,正确性,完整性)才能进行提交。然后到数据库表中去进行验证。
2.3业务测试。测试系统的业务处理过程的正确性,电子商务系统必须在全部的时间里正确处理业务,无一例外。因此,要通过测试确保业务处理的正确性。如(如,购物,加入购物车,结算,提交订单,订单处理流程)的一个过程。
3 数据层。(数据层的测试,主要是指对应系统用于储存和获取信息的数据库管理系统的测试)
3.1响应时间,定量并发操作,对应用系统是否造成响应数据的方面的影响。直接影响用户体验。
3.2数据完整性(主要验证表单数据的提交及存储),也就是数据完整性测试。将提交的数据
与数据库中提交的数据进行比对,确认是否正确。