qileilove

blog已经转移至github,大家请访问 http://qaseven.github.io/

软件测试工程师如何参与代码检视

 测试人员参与代码检视的收益:

  a)测试人员可以在早期就能够熟知系统,提前发现代码中的BUG;

  b)提高测试工程师阅读代码和写代码能力;

  c)测试工程师的经验积累后,可以避免一些很常见,很普通低级的错误;

  d)测试取得话语权,促进开发与测试的更多平等沟通;

  e)对于开发新员工的代码,收益会加倍(新员工容易犯业务上的低级错误)。

  第零步:(比如新参与某系统的测试)

  a)学习业务知识;

  b)学习系统整体架构;

  c)开发同学讲解设计架构、代码结构;

  d)熟悉使用系统,找到阅读代码下手点:从日志、业务入口、SqlMap等;

  e)从流程或者数据流阅读下去,然后一步步深入到代码中。

  第一步:(code review准备)

  a)当需求已经确定,测试同学需要参与方案的讨论;

  b)开发开始编写代码,测试进行测试用例设计;

  c)当开发某个类或者功能或者方法已经基本稳定,让开发串讲实现方式(避免出现实现方式不合理);

  d)测试同学可以开始review开发的代码,同时补充或者去重复测试用例(story或者sdv测试用例)。

  第二步:(code review进行)

  a)对代码的规范进行检视;

  b)对代码的可测试性进行检视;

  c)从业务场景的角度去review代码,如果实现存在的问题,那基本上可以发现;

  d)对于发现的问题,建议分时段给开发,否则会打乱开发的写代码节奏;

  e)如果你还不懂设计、不懂架构,那么可以从是否浪费CPU、是否浪费内存的角度看代码。

  第三步:(code review后)

  a)转测试后,应该不出现实现方案不合理的情况;

  b)有结果统计,这个迭代review的记录数,可以横向比较;

  c)迭代总结,开发与测试一起进行经验教训分享。

posted @ 2012-05-03 10:05 顺其自然EVO 阅读(609) | 评论 (0)编辑 收藏

为功能测试正名,写给广大软件测试同行

 首先,恭祝51testing生日快乐,感谢您一直是所有软件测试人员成长的摇篮和精神支柱!

  从刚刚接触软件测试工作,到现在已经有十年之久。

  十年前,毕业后就进入了一家软件公司,经理分配的任务就是测试软件,发现有错的地方就告诉他。这是我对软件测试工作的第一印象,就是看系统是否报错,各个功能按钮是否能够正确响应。

  两年后,由于公司经营不善,我被公司裁员。其理由是:技术总监认为没有必要设置软件测试工程师的岗位。因此,竞岗时,我无岗可竟。

  恰巧,当时中国软件评测中心跟省科技厅联合培训,于是,我就第一次真正认识到什么是软件测试。同时,也知道了51testing和测试时代这些软件测试交流的专业平台。

  之后,我就入职另一家软件公司。当时,入职的职务是测试员,但公司的测试基本上和我第一次接触软件测试工作时的水平差不多。于是,我就从最基础的缺陷管理测试用例、测试流程、测试规范、测试标准等等,一步步的将公司的测试规范建立起来,而我也因此成为了公司的测试部门经理。从测试员到测试部门经理,在这短短的八个月的时间里,可以说我取得的每一次进步,掌握的每一种知识,均来源于51testing和QQ群。可以说,如果没有这些交流平台,就不可能有系统化学习的机会。

  为了挑战质量要求更高的项目,我离开了家乡,加入了现在的团队。这是一支由150多名技术人员组成的团队,其中,测试人员有20人。我入职的职务是测试组长,两周后任质量经理,八个月后任测试总监。从各种测试标准的制定到测试规范的建立,再到测试体系的建立;从团队战略发展方向到团队梯队建设再到测试知识库的筹建;从技术培训到团队成员职业规划;三年来 ,我和这支团队一起成长。

  从业近十年,参与的项目十余个,其中千万级以上的项目三个。项目涉及MIS、HIS、GIS、OA、三维应用、BI、SOA、应用系统集成等诸多方向,其测试类型包括:功能测试性能测试、接口测试。

  说了这么多,大家一定以为我写跑题了,其实不然。之所以描述自己的成长经历,是想告诉大家我的体会来源于我的经历。

  无论是在各种测试论坛上,各个测试QQ群中,还是在招聘时的应聘人员。我经常会遇到这种问题,相信大多数同行们也都遇到过。

  1、做功能测试有前途么?
  2、做黑盒测试有前途么?
  3、我现在只能做功能测试,但我今后一定要做自动化测试、性能测试!
  4、你们公司上自动化了么?
  5、我会QTP、LR。

  广大测试同行在投简历时,也一定常见这种职位要求:要求熟练掌握LR、QTP、QC、TD、Bugzilla等工具之一。

  咱们先说说后面这种情况。一个公司在选择测试人员时,竟然将这些测试工具作为选人标准之一。我想问这样招聘的公司几个问题:

  1、贵公司的产品适合上自动化么?
  2、贵公司的管理水平达到上自动化的要求了么?
  3、LR能完全支撑贵公司产品的性能测试么?
  4、贵公司一年能有多少项目需要做性能测试?
  5、缺陷管理工具每个公司用的都不太一样,其缺陷管理流程是需要结合公司管理流程的,这种工具即便人家不会或没用过此类工具,你就不能培训么?很难么?

  除非公司是在招聘对口的技术人才,否则,我只能认为这家公司的测试水平真的不咋的。甚至于写招聘要求的人,压根就不懂测试。

  咱们再来说说关于同行们对功能测试的误解。好多人都认为功能测试门槛低,没技术含量,好像提起来做功能测试就低人一等似得。如果你有这种意识,我只能说:兄弟,多干几年吧。我承认功能测试的入门门槛是低,但我不认为功能测试没技术含量。我甚至认为一个能把功能测试做好的测试人员,才是一个合格的质量负责人!

  大家先别拍我,让我们来谈一下什么是软件测试。既然大家都是同行,相信大家都认同:“通过测试手段,在用户使用前,发现缺陷。”以及“软件中不可能没有缺陷。”这两句话。不错,测试是不能被穷举的,因此,我们也不可能发现软件中所有的缺陷。因此,我们是不是能够换句话说:“软件测试是通过一系列的测试手段,证明软件具备一定的质量。”

  如果你认同这一观点,那么我们再来谈谈功能测试的范围。

  狭义的功能测试:这点点,那点点,看看能不能正确打开页面、程序会不会崩溃、功能是不是能够正确实现等等。这也就是大家之所以认为功能测试没技术含量的根本原因。因为这种测试也得叫它功能测试。

  广义的功能测试:各种业务逻辑是否已正确实现?各种业务约束是否正确实现?各类特殊的数据是否能够正确处理?软件的容错机制如何?数据的完整性、唯一性、正确性是否已经通过测试?系统及数据的安全性是否已通过测试?软件的易用性是否满足用户的需要?等等等等。功能测试所包含的范围实在是太大。

  我没有贬低其他测试专业的意思,但有几点是需要说明的。随着各行各业对信息化建设的需求日益提高,其软件的规模和复杂度正在翻倍的提高。而功能测试的难度也随软件的复杂度逐日提高。而相对来说,白盒测试依旧关注的是单个函数的代码逻辑以及异常的处理;自动化测试仅仅提高的是测试执行效率,测试质量依然是要靠功能测试用例来保障;性能测试其实关注的是业务发生的场景和如何生成负载和收集监控指标,其变化就是需要监控的对象更多,影响因素更为复杂。

  面对规模庞大,业务逻辑愈来愈复杂的系统,保障其软件质量,就需要广大功能测试人员具备更强的逻辑分析、设计能力;掌握更多的业务知识及技术。同时,由于当前国内绝大多数企业对测试工作的认知程度不足,大多数测试工作都是在时间紧、任务重的条件下完成的。那么,在有限的条件下,如何能够提高软件产品的质量,即:如何证明软件具备了一定的质量。将是所有软件质量负责人首先考虑的问题。

  在这种前提下,我认为,一个好的功能测试工程师应具备丰富的行业知识(专家级),极强的逻辑分析能力,精于业务架构、测试架构、数据库、测试用例的设计。公司的产品质量,取决于测试负责人的组织能力及团队成员的技术能力,因此,功能测试工程师在测试团队中不可或缺。

  无论是白盒测试、自动化测试、性能测试,都是不同的测试手段,但功能测试却是产品质量的根本保障! 请广大正在从事功能测试的同行们,正视自己的工作,认真对待,不要气馁或轻视自己的工作。将自己手头上的事做好,人生的每一段经历都将是你的财富!

posted @ 2012-05-03 10:04 顺其自然EVO 阅读(443) | 评论 (0)编辑 收藏

谁能成为软件测试架构师

 软件测试架构师不是一种头衔,而是一种角色,更重要的是一种能力—对团队的影响力。软件测试工程师不是在某一天突然成为一个软件架构师的,虽然他可能会在某一天被某某经理宣布为测试架构师。任何一个人成为软件架构师,都是一个渐进的过程,不断积累经验,不断提高自己的技术水平和业务能力,才能逐渐胜任测试架构师的角色。

  1、对软件测试架构师的要求

  测试架构师是测试团队的技术带头人,在系统非功能特性的测试、自动化测试框架等方面发挥着主导作用,对开发团队具有很好的沟通能力和影响力。测试架构师虽然不管理团队,不通过管理权利来驱动下属执行某项工作,但通过沟通与组织协作,让成员愿意采纳其建议,配合其完成某项工作,从而实现个人价值,为公司做出应有的贡献。

  测试架构师应具有较强的抽象能力和创新能力,通过一个全局的观点、宏观的视角来理解软件系统作为一个整体是如何工作的,可以将具体问题抽象为一个模型,从而解决一类问题,并通过不断创新,找到解决问题的新方法,推广新的测试技术。同时,测试架构师作为测试技术带头人,就是为测试团队树立技术标杆,提供技术指导、做出技术决策,具体工作有:

  了解国内外技术趋势,具有很好的前瞻性,做好测试团队的技术规划,确保团队朝着正确的技术方向持续前进。

  结合公司具体情况,引入和推广适合公司需要的新技术。

  了解业界的普遍实践,对测试自动化或测试工具所涉及的技术提供很好的支持,或引导团队向正确的方向努力,不断地改进自动化测试工作。

  具有丰富的技术经验,对技术可行性一般能做出正确的评估。

  具有一线的测试经验,可以融会贯通,使技术真正能为测试服务。

  业务基础扎实,能很好理解客户需求,确定测试需求,能为各种技术困难的测试项目提供有效的解决方案。

  2、软件测试架构师的主要职责

  测试架构师的主要职责如下。

  审查系统架构、系统构件/组件及其接口关系等设计是否合理。

  确保系统的可测试性。

  需要以全局的视角来把握软件测试项目,不仅要关注功能性需求,而且要关注非功能性测试。

  制订软件系统的测试策略和方法,特别是在系统的性能、安全性、稳定性、可靠性等方面的测试方法、技术线路和质量标准。

  构建复杂的系统测试环境,并分析、解决测试中出现的较深的技术问题(Troubleshooting),帮助做好缺陷的隔离。

  对系统(性能、安全性、稳定性、可靠性)测试做出分析、评估,并提出为改善系统性能、可靠性而进行的设计修改、代码重构的具体建议。

  设计测试自动化的技术框架,主持重要的测试工具的研究、评估、设计。

  参与系统部署的设计。

  参与新技术的评估和引进。

  帮助改进测试流程、提高测试效率。

  测试架构师通过上述技术活动,帮助测试项目、测试团队提高测试效率和质量。为了更好地理解测试架构师的职责,在这里,将测试架构师和软件开发架构师、测试经理进行比较,如表2-1、表2-2所示。

  表2-1  测试架构师和软件开发架构师的比较

 

测试架构师

软件开发架构师

目标

①提高测试的效率和质量
②提高系统的可测试性,特别是非功能特性的可测试性

满足系统非功能特性的要求,如完成高性能、高可靠性和高安全性的系统设计

主要工作

设计测试平台

设计软件产品的系统架构

关注

产品的测试过程

产品的研发过程

影响范围

测试社区、开发团队

开发社区、测试团队

技术范围

①系统架构设计模式、自动化技术、建模技术、测试用例设计技术等
②测试模型、方法、技术、工具等各方面创新

①系统架构设计模式
②面向对象的需求分析、设计和编程等全面技术能力
③熟练使用软件开发平台(如Eclipse)
④资深的编程技术及其他开发技术

能力

①足够的技术前瞻能力
②足够的影响力
③掌握软件系统架构设计知识
④深刻理解测试流程
⑤精通测试技术、方法

①掌握软件系统架构设计知识
②掌握一类以上软件开发技术
③精通一种以上开发语言

  表2-2  测试架构师和测试经理的比较

 

测试架构师

测试经理

目标

①提高测试的效率和质量
②系统的可测试性,特别是非功能特性的可测试性

①提高测试的效率和质量
②不断改进测试流程
③加强团队建设,满足组织发展的需求

主要工作

①设计测试平台,包括自动化测试框架
②确定测试方法和测试策略

①测试团队的管理
②测试项目的管理,主要包括计划、人员安排和进度监控等

责任、决策范围

①测试方法的有效性
②技术解决方案,如自动化测试框架的设计与实施
③测试技术规范,包括测试覆盖率衡量
④从技术角度来帮助团队理解架构
⑤团队的技术发展之路

①使客户的价值最大化
②团队的能力和绩效
③项目的结果,特别是软件产品质量评估结果
④事先定义的质量标准
⑤和其他团队的沟通、协调
⑥非技术问题,包括测试资源调度、项目里程碑确定等

关注

技术储备、技术问题和发展趋势

测试过程和测试人员的管理

工作方式

教练式技术指导

疏通、引导等各种技术管理方式

能力

①足够的技术前瞻能力
②足够的影响力
③掌握软件系统架构设计知识
④深刻理解测试流程
⑤精通测试技术、方法

①团队管理能力,包括沟通能力
②项目管理能力,包括问题分析和解决能力
③测试专业知识,以及其他软件工程知识

  3、软件测试架构师的成果

  软件测试架构师的成果如下。

  对软件产品需求定义、系统设计和代码编程等可测试性做出详细要求的规范性文档。

  对系统设计、单元测试等的验收标准。

  软件新系统的测试方案,包括采用的测试策略、测试技术、测试工具等。

  自动化测试框架,包括自动化系统架构设计、测试脚本语言等。

  得到改善的测试流程,特别是关系到技术层面的单元测试流程、自动化测试实施流程等。

  测试过程性能、测试阶段性成果质量、产品质量等的度量体系。

  软件组织的测试技术规划,包括测试技术发展趋势、新技术引入研究和评估报告、未来待解决的关键测试技术列表等。

  特定的或专项的测试方法或技术的研究成果。

  不同级别测试工程师所要掌握的各种技术能力集合及其技术等级评估办法。

  提升测试效率的各种有效测试方法、实践指导等文章。

posted @ 2012-05-03 10:00 顺其自然EVO 阅读(442) | 评论 (0)编辑 收藏

单元测试之误解与困境

认为单元测试影响开发进度,一是借口,拒绝对单元测试相关知识进行学习(单元测试,代码重构,版本管理是开发人员的必备);二是单元测试是“先苦后甜”,刚开始搭建环境,引入额外工作,看似“影响进度”,但长远来看,由于程序质量提升、代码返工减少、后期维护工作量缩小、项目风险降低,从而在整体上赢了回来。

  误解一:影响开发进度

  一旦编码完成,开发人员总是会迫切希望进行软件的集成工作,这样他们就能够看到系统实际运行效果。这在外表上看来好像加快进度,而像单元测试这样的活动被看作是影响进度原因之一,推迟了对整个系统进行集成测试的时间。

  在实践中,这种开发步骤常常会导致这样的结果:软件甚至无法运行。更进一步的结果是大量的时间将被花费在跟踪那些包含在独立单元里的简单Bug上面,在个别情况下,这些Bug也许是琐碎和微不足道的,但是总的来说,它们会导致推迟软件产品交付的时间,而且也无法确保它能够可靠运行。

  在实际工作中,进行了完整计划的单元测试和编写实际的代码所花费的精力大致上是相同的。一旦完成了这些单元测试工作,很多Bug将被纠正,开发人员能够进行更高效的系统集成工作。这才是真实意义上的进步,所以说完整计划下的单元测试是对时间的更高效利用。

  误解二:增加开发成本

  如果不重视程序中那些未被发现的Bug可能带来的后果。这种后果的严重程度可以从一个Bug引起的用户使用不便到系统崩溃。这种后果可能常常会被软件的开发人员所忽视,这种情况会长期损害软件开发商的声誉,并且会对未来的市场产生负面影响。相反地,一个可靠的软件系统的良好的声誉将有助于一个软件开发商获取未来的市场。

  很多研究成果表明,无论什么时候作出修改都要进行完整的回归测试,在生命周期中尽早地对软件产品进行测试将使效率和质量得到最好的保证。Bug发现得越晚,修改它所需的费用就越高,因此从经济角度来看,应该尽可能早地查找和修改Bug。而单元测试就是一个在早期抓住Bug的机会。

  相比后阶段的测试,单元测试的创建更简单,且维护更容易,同时可以更方便地进行重构。从全程的费用来考虑,相比起那些复杂且旷日持久的集成测试,或是不稳定的软件系统来说,单元测试所需的费用是很低的。

  误解三:我是个编程高手,无须进行单元测试

  在每个开发团队中都至少有一个这样的开发人员,他非常擅长于编程,他开发的软件总是在第一时间就可以正常运行,因此不需要进行测试。你是否经常听到这样的借口?在现实世界里,每个人都会犯错误。即使某个开发人员可以抱着这种态度在很少的一些简单程序中应付过去,但真正的软件系统是非常复杂的。真正的软件系统不可以寄希望于没有进行广泛的测试和Bug修改过程就可以正常工作。编码不是一个可以一次性通过的过程。在现实世界中,软件产品必须进行维护以对功能需求的改变作出及时响应,并且要对最初的开发工作遗留下来的Bug进行修改。你希望依靠那些原始作者进行修改吗?这些制造出未经测试的代码的资深工程师们还会继续在其他地方制造这样的代码。在开发人员做出修改后进行可重复的单元测试,可以避免产生那些令人不快的负作用。

  误解四:测试人员会测出所有Bug

  一旦软件可以运行了,开发人员又要面对这样的问题:在考虑软件全局复杂性的前提下对每个单元进行全面的测试。这是一件非常困难的事情,甚至在创造一种单元调用的测试条件时,要全面考虑单元被调用时的各种入口参数。在软件集成阶段,对单元功能全面测试的复杂程度远远超过独立进行的单元测试过程。

  最后的结果是测试将无法达到它所应该有的全面性。一些缺陷将被遗漏,并且很多Bug将被忽略过去。让我们类比一下,假设我们要清理一台电脑主机中的灰尘,如果没有把主机中各个部件(显卡、内存等)拆开,无论你用什么工具,一些灰尘还会隐藏在主机的某些角落无法清理。但我们换个角度想想,如果把主机每个部件一一拆开,这些死角中的灰尘就容易被发现和接触到了,并且每一部件的灰尘都可以毫不费力地进行清理。

  单元测试之困境

  测试在软件开发过程中一直都是备受关注的,测试不仅仅局限于软件开发中的一个阶段,它已经开始贯穿于整个软件开发过程。大家普遍认识到,如果测试能在开发阶段进行有效执行,程序的Bug就会被及早发现,其质量就能得到有效的保证,从而减少软件开发总成本。但是,相对于测试这个词的流行程度而言,大家对单元测试的认知普遍存在一些偏差,特别是一些程序员很容易陷入一些误区,导致了测试并没有在他们所在的开发项目中起到有效的作用。下面对一些比较具有代表性的误区困境进行剖析,并对于测试背后所蕴含的一些设计思考进行阐述,希望能够起到抛砖引玉的作用。

  误区、困境一:使用System.out.print跟踪和运行程序就够了

  这个误区可以说是程序员的一种通病,认为使用System.out.print就可以确保编写代码的正确性,无须编写测试用例,他们觉得编写用例是在“浪费时间”。使用System.out.print输出结果,以肉眼观察这种刀耕火种的方式进行测试,不仅效率低下,而且容易出错。

  误区、困境二:存在太多无法测试的东西

  在编码的时候,确实存在一些看起来比较难测试的代码,但是并非无法测试。并且在大多数情况下,还是由于被测试的代码在设计时没有考虑到可测试性的问题。编写程序不仅与第三方一些框架耦合过紧,而且过于依赖其运行环境,从而表现出被测试的代码本身很难测试。

  误区、困境三:测试代码可以随意写

  编写测试代码时抱着一种随意的态度,没有弄清测试的真正意图。编写测试代码只是为了应付任务而已,先编写程序实现代码,然后才去编写一些单元测试。表现出来的结果是测试过于简单,只走形式和花架,将大量Bug传递给系统测试人员。

  误区、困境四:不关心测试环境

  手工搭建测试环境,测试数据,造成维护困难,占据了大量时间,严重影响效率。对测试产生的“垃圾”不清除,不处理。造成测试不能重复进行,导致脆弱的测试,需要维护好测试环境,做一个“低碳环保”的测试者。

  误区、困境五:测试环境依赖性大

  测试环境依赖性大,没有有效隔离测试目标及其依赖环境,一是使测试不聚焦;二是常因依赖环境的影响造成失败;三是因依赖环境太厚重从而降低测试的效率(如依赖数据库或依赖网络资源,如邮件系统、Web服务)。

posted @ 2012-05-03 09:53 顺其自然EVO 阅读(205) | 评论 (0)编辑 收藏

我们该如何设计数据库

  数据库该如何设计,一直以来都是一个仁者见仁智者见智的问题。

  对于某一种数据库设计,并不能简单的用好与不好来区分。或许真的应了那句话,没有最好,只有最适合。讨论某种数据库设计的时候,应该在某种特定的需求环境下讨论。

  下面来讨论一下在项目中经常碰到的用户的联系方式储存的问题。

  我在这里套用之前网络上流行“普通——文艺——二逼”的分类方式来描述我下文中提及的三种数据库设计思路,并且通过查询数据(对数据增删改,三种设计要付出的代码成本都差不多)和数据库面临需求变动两个方面来思考这三种设计各有怎样的优劣。

  普通青年:

  或许我们都这样设计过数据库

  学生表 tb_Student:

Namevarchar(100)名字
Telphonevarchar(200)联系电话
Emailvarchar(200)你懂的
Faxvarchar(200)传真

  这应该是最容易想到的一种思路,简单、明了。

  比如说我要查询某个人的联系方式,那么我只用一条语句就能实现:

select Name,Telphone,Email,Fax from 表 where 条件

  在查询的时候,这种数据库设计十分清晰,没有任何思维的难度,没有任何逻辑的挑战。但是当面临需求变动的时候,那将会是一场灾难。

  比如现在要新增一类用户:校长。那么我们要如何处理?

  答案是:再加一张表 tb_Headmaster。

  事实上,再加一张表其实修改并不大,因为我们完全不需要修改学生表的存储逻辑,换句话说,这种设计是遵循了开闭原则的。

  但如果学生要添加一种联系方式HomePhone的时候,灾难发生了,怎么办?

  在tb_Student中加一列HomePhone?这意味着至少要修改整个Model层(或者说DAL层),这种改动是十分巨大的,而且容易造成错误。

  或者再建一张表tb_Student2,来储存HomePhone,然后以ID来关联两张表?按改动规模来说,这种改动相对简单而且不容易出错,但是在今后的维护中会增加逻辑成本。当你一而再再而三的以这样的方式来应对需求变动的时候,你的程序将变得不可理解。

  文艺青年:

UserRoleint对应用户类型(None = 0, Student = 1, Teacher = 2, Headmaster = 4)
OwnerIDint对应用户ID
ContactMethodint联系方式(None = 0, Email = 1, HomePhone = 8, WorkPhone = 16,MobilePhone = 32,Fax=64)
ContactInfovarchar(255)联系信息

  这种是一个多对多关系。当我们要查询某个用户对应的联系方式的时候,那是一场逻辑上的浩劫:

select ContactInfo from 表 where UserRole=某种用户类型 and OwnerID=某用户ID

  这种写法是一次性取出某个用户所有的联系方式,包括Email,HomePhone,WorkPhone等,之后我们可以在程序中判断ContactMethod的类型,将具体的联系方式加以区分。你可以简单的想到用switch-case的写法,类似这样:

  1. var contact = 上面的SQL语句取出来的用户所有的联系方式;  
  2.             foreach (var item in contact)  
  3.             {  
  4.                 switch (item.ContactMethod)  
  5.                 {  
  6.                     case ContactMethod.WorkPhone:  
  7.                         txtWorkPhone.Text = item.ContactInfo;break;  
  8.                     case ContactMethod.Email:  
  9.                         txtEmail.Text = item.ContactInfo;  
  10.                         break;  
  11.                     case ContactMethod.Fax:  
  12.                         txtFax.Text = item.ContactInfo;  
  13.                         break;  
  14.                     case ContactMethod.OtherPhone:  
  15.                         txtOtherPhone.Text = item.ContactInfo;  
  16.                         break;  
  17.                     case ContactMethod.MobilePhone:  
  18.                         txtMobilePhone.Text = item.ContactInfo;  
  19.                         break;  
  20.                 }  
  21.             }









当然你也可以尝试下面这种写法,我个人认为这种写法更优雅

  1. var contact = 上面的SQL语句取出来的用户所有的联系方式;              
  2. txtWorkPhone.Text = (from a in contact  
  3.                      where a.ContactMethod == ContactMethod.Work_Phone  
  4.                      select a.ContactInfo).ToString();  
  5. //后面以此类推,你懂的

  注意,请不要试图使用类似下面这类语句来查询某用户的联系方式:

  1. select ContactInfo from 表 where UserRole=某种用户类型 and OwnerID=某用户ID and ContactMethod=1    //取出某用户的Email  
  2. select ContactInfo from 表 where UserRole=某种用户类型 and OwnerID=某用户ID and ContactMethod=8    //取出某用户的HomePhone

  相信我,这种做法非常愚蠢:每当你要取出这个用户的一种联系方式,就要和数据库建立一次连接,打开/关闭一次数据库;这种做法代价是十分巨大的,即使有数据库连接池,即使有数据库缓存,都应该避免这种愚蠢的做法.

  唔,用了那么多的代码,终于查出了某个用户的联系信息了。反正我个人觉得这种设计方式在查询的时候,是逻辑上的浩劫。什么?你说你很享受?好吧,看来是我脑容量不够……

  不过当我们面临需求变动的时候,那就非常愉快了。

  什么,要加一类用户?简单,UserRole加一个枚举就好了。

  什么,要加一种联系方式?ContactMethod加一个枚举就OK。

  使用了这种表设计的时候,相信你会微笑着面对需求变动的

  二逼青年

  昨天和同事也探讨了下这个问题,按他的说法就是:哪个表要联系方式,我就扔个字段进去,存json

Contactvarchar(8000)用于储存json

  举例来说,有这么一个用户:

ID:1Name:张三Telphone:1234Email:123@123.comFax:5678

  那么数据库中就这样存:

  [{"ID":1,"Name":"张三","Telphone":"1234","Email":"123@123.com","Fax":"5678"}]

  当我听到这种设计思路的时候,虎躯微微一震:靠,这都行。按这种设计,我整张表都放进一个json里面一股脑的存进去就算了。不过震惊之后仔细想一想,其实这种设计也是有可取之处

  首先,从查询来说,和普通青年一样,只需一句SQL:

select Contact from 表 where 条件

  查询之后,就可以通过json处理函数将想要的数据取出来,在此就不赘述了

  那么当面临需求变动的时候会发生什么:

  加一类用户的时候,要添加一张表。也是符合开闭原则,原有代码没有改动。

  加一种联系方式,只用存json的时候多存一点东西。

  不过这种设计如果要更新某条数据的话要稍微麻烦一点:先查询一条数据,重组json之后再Update。

  写了那么多,希望已经将想要表达的问题表达清楚了。不足之处望海涵,也欢迎留言斧正。

  PS:真的是一个规律,一个月才能写出一篇博客……

  再PS:就快能回家了,高兴,开心。

posted @ 2012-05-03 09:45 顺其自然EVO 阅读(406) | 评论 (0)编辑 收藏

maven环境快速搭建

   最近,开发中要用到maven,所以对maven进行了简单的学习。因为有个maven高手在身边,所以,很快就上手了,我这里算是自我总结吧。关于maven是什么东东,请参考其它文章。

----------------准备工作-------------

Jdk  1.5以上java开发环境。

Eclipse IDE 一个。

Maven 3.0.3下载地址: http://maven.apache.org/docs/3.0.3/release-notes.html

----//快速搭建步骤

 

第一步:配置maven环境

 

将下载文件解压,然后设置maven环境

如果你配置过jdk的话,这里对你应该不难。如我的maven环境为:F:\maven\apache-maven-3.0.3

我的电脑-----属性----高级-----环境变量-----环境变量-----新建

变量名:M2_HOME

变量值:F:\maven\apache-maven-3.0.3

找到path 

在环境变量值尾部加入:;%M2_HOME%\bin;---前面注意分号

当然,你也可以直接在path 路径下加入:;F:\maven\apache-maven-3.0.3\bin  只是上面的方式更优雅一点。

我新建立

打开命令提示符(开始---运行---cmd,检查我们的java环境和maven环境是否有误。

 

 

第二步:修改仓库位置

 

修改我们仓库地址,仓库用于存放我们项目所依赖的所有jar包。

我的仓库路径:F:\maven\repo----这个路径是我自己创建,你可以将路径创建在任何位置。

我们打开…\apache-maven-3.0.3\conf\目录下的setting.xml文件,设置成我们创建的仓库路径

下面我们用一个命令验证一下。打开命令提示符,输入:mvn help:system 

该命令会打印出所有的java系统属性和环境变量。这些信息对我们日常的编程工作很有帮且。

如果运行的过程中没有错误,打开我们仓库(F:\maven\repo)会发现里面多了一些文件。这些文件就是我们从maven的中央仓库下载到本地仓库的。

 

第三步:创建maven项目

创建一个我们自己的项目。

我们通过maven命令行方式创建一个项目

mvn archetype:create -DgroupId=com.chongshi.test -DartifactId=hello -DpackageName=com.chongshi.test -Dversion=1.0

 

因为是第一次构建项目,所有依赖的jar包都要从maven的中央仓库下载,所以需要时间等待。等以后我们的本地仓库中积累了我们常用的jar包后,我们的开发将变得非常规范和方便。^_^!!

 

借助下载jar包的时间,我们来了解一下pom.xml文件。

 

复制代码
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation
="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.chongshi.test</groupId>
<artifactId>hello</artifactId>
<version>1.0</version>
<packaging>jar</packaging>

<name>hello</name>
<url>http://maven.apache.org</url>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
复制代码

Ø project:pom.xml文件中的顶层元素; 
Ø modelVersion:指明POM使用的对象模型的版本。这个值很少改动。
Ø groupId:指明创建项目的组织或者小组的唯一标识。GroupId是项目的关键标识,典型的,此标识以组织的完全限定名来定义。比如,org.apache.maven.plugins是所有Maven插件项目指定的groupId。 

Ø artifactId: 指明此项目产生的主要产品的基本名称。项目的主要产品通常为一个JAR文件。第二,象源代码包通常使用artifactId作为最后名称的一部分。典型的 产品名称使用这个格式: <artifactId>- <version>. <extension>(比 如:myapp-1.0.jar)。 

Ø version:项目产品的版本号。Maven帮助你管理版本,可以经常看到SNAPSHOT这个版本,表明项目处于开发阶段。 

Ø name:项目的显示名称,通常用于maven产生的文档中。 

Ø url:指定项目站点,通常用于maven产生的文档中。 

Ø description:描述此项目,通常用于maven产生的文档中。

 

对于一个项目中只有下面的一部分是是我们需要关注的:

<groupId>com.chongshi.test</groupId>

 <artifactId>hello</artifactId>

 <version>1.0</version>

 

第四步:编译项目代码

我们的项目已经创建完成。但我们点开目录发现,它并不是我们eclipse所需要的项目目录格式。我们需要把它构建成我们eclipse可以导入的项目。

在命令提示符下进入到我们的创建的项目目录(F:\maven\hello)下,执行:mvn clean compile

Clean 告诉maven清理输入出目录target/,compile告诉maven编译项目主代码。

不要急,我们又需要一段时间来下载,相关jar包。^_^!第一次用maven要学会淡定。

 

项目是编译完了,但项目的目录结构还不是我们想要的eclipse的项目结构,是不能导入到eclipse中的。所以,还需要执行一个命令:mvn eclipse:eclipse

 

命令执行完成后就得我们需要的项目目录了。

 

第五步:导入eclipse工具

 

打开的我们的eclipse工具。

先配置maven仓库路径

Window----Perferences-----java-----Build Path-----Classpath Variables

New一个变量的类路径。

Name :M2_REPO   注意这个名字必须要大写。

Path :F:/maven/repo  点击“Folder…”找到有本地仓库的位置。

 

下面,可以导入我的hello项目了。Eclipse如何导入项目,我这里就不说了,如果你是个java开发人员的话。

 

第六步:包的更新与下载

 

打开项目发现我们junit 是3.8.1的,有点老了。那我想换成4.7的,如何通过maven的方式更换呢。其实,很简单,打开我们项目下的的pom.xml文件。

复制代码
……
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>
……
复制代码

更改变,junit的版本号,然后重新执行:mvn eclipse:eclipse  

Maven 中央仓库地址:http://search.maven.org

 

假如,我们想下载一个struts jar包。在搜索框内搜索strruts ,会要列出中央仓库中的所有struts版本。

列表的格式与我们pom.xml配置文件的格式是对应的。

 

 

 

 

我们在pom.xml中加入:

<groupId>stuts</groupId>

 <artifactId>struts-scription</artifactId>

 <version>1.0.1</version>

然后更新项目就可从中央仓库下载我们想要的任意jar包(必须是开源的包)

 

继续学习内容:

1. 如何创建一个web项目?

  如果想让maven应用到项目开发中,这个是必须的。

2. 如何使用jeety?

  maven提供的一个容器,类似于tomcat

3. 如何创建一个代理仓库?

http://www.theserverside.com/news/1364121/Setting-Up-a-Maven-Repository
一个团队要真正用好Maven,repository私服是免不了的,否则team mate们多好奇的目光,都会全部消耗在漫长的jar下载中。

    以前介绍过一个搭建私服的简朴但很笨的办法--将雷锋同志下载好的.m2/repository目录整个上传到任意的Web服务器中。其实世上多的是专业的maven私服搭建器,见TheServiceSide的文章--Setting Up a Maven  Repository

    我用的是Artifactory,忽然间觉得,Java的服务应用啥时候终于变得这么好用了,什么都不用装,不用配,双击artifactory.bat,自己就会启动Jetty Web服务器,典型的拆箱即用。

    10分钟入门到开动

  • 双击artifactory.bat,启动默认配置的Artifactory服务。
  • http://localhost:8081/artifactory/,用admin/password登录进管理界面看看。
  • 修改自己项目的pom.xml,增加私服定义
        <repositories>
            
    <repository>
                
    <id>artifactory</id>
                
    <name>your local artifactory</name>
                
    <url>http://localhost:8081/artifactory/repo</url>
            </
    repository>
        </
    repositories>

        
    <pluginRepositories>
            
    <pluginRepository>
                
    <id>artifactory</id>
                
    <name>your local artifactory</name>
                
    <url>http://localhost:8081/artifactory/plugins-releases</url>
                
    <snapshots>
                    
    <enabled>false</enabled>
                </
    snapshots>
            </
    pluginRepository>
        </
    pluginRepositories>
  • 正常运行项目的maven命令,向artifactory索求jar。如果artifactory已经下载了就会直接返回给你,还没有的 就会去那几个repo官方站下载。进入到artifactory的管理界面,不断刷新Browse the repository,会看到仓库不断丰富的情况。

另外,官网服务器上还没有的一些jar,也可以通过管理界面很方便的deploy到Artifactory中。

最后你团队里的artifactory服务器不能直接连外网,需要使用HTTP代理服务器的话,见http://www.jfrog.org/sites/artifactory/latest/configuration.html

建议那些愿意为开源作实事的站点,在带宽充裕的服务器上,用Artifactory搭一个jar仓库吧,SpringSide就可以直接连上去下载jar了:)


  如果是团队开发,这个很有必要,我们不可能每次都到中央仓库拿包,那样很慢,如果开发人员A已经下了某包,开发人员B还要下;创建代理仓库,A第一次下的包会存入代理仓库中,B要用时直接从代理仓库取就行。

本文更新一个命令。

第一节中提示创建一个项目用archetype:create  create是个被废弃或不被推荐使用的插件,在以后创建项目中请尽量使用archetype:generate

创建一个项目,如下:

mvn archetype:generate -DgroupId=com.chongshi.test -DartifactId=hello 

-DpackageName=com.chongshi.test -Dversion=1.0

第一节抛出了三个问题,本文将解决前两个

1. 如何创建一个web项目

2. 如何使用jettey容器运行

如果对maven的基本配置不了解的,请先阅读第一篇文章:

http://www.cnblogs.com/fnng/archive/2011/12/02/2272610.html

-----//创建一个文本项目

 

1. 如何创建一个web项目。

其实非常简单,只是比普通项目多加一个参数DarchetypeArtifactId ,命令如下:

mvn archetype:generate -DgroupId=com.chongshi.test -DartifactId=mywebapps  -DarchetypeArtifactId=maven-archetype-webapp -Dversion=1.0 

定位到创建项目的目录下构建成eclipse项目:

 F:\mywebapp>mvn eclipse:eclipse 

 

通过maven构建后就是我们eclipse所需要的项目目录如下。

由于本文不是讲解struts2项目的创建与配置,所以就不写struts2的配置过程,但为了不影响后面内容的讲解,所以这里提供一个基于struts2web项目

例子介绍:http://www.mkyong.com/struts2/struts-2-hello-world-example/

项目源码下载:

http://www.mkyong.com/wp-content/uploads/2010/08/Struts2-Hello-World-Example.zip

我解压到了本地D盘根目录下,由于已经是maven 的项目格式了,所以不需要对该项目运行mvn  eclipse:eclipse命令进行构建。直接导入到eclipse工具中。项目目录结构如下:

 

 

----//如何使用Jetty容器

2. 如何使用jetty容器运行项目。

使用添加jetty 运行项目。

 Jettytomcat一样也属于web容器。Jettymaven中做为一个插件。我们要在pom.xml中添加jetty的插件,才能调用jetty

<project>…</project>标签之间,插入如下信息:

 

复制代码
<build>
<finalName>Struts2Example</finalName>
<plugins>
<plugin>
<groupId>org.mortbay.jetty</groupId>
<artifactId>maven-jetty-plugin</artifactId>
<version>6.1.10</version>
<configuration>
<scanIntervalSeconds>10</scanIntervalSeconds>
<stopKey>foo</stopKey>
<stopPort>9999</stopPort>
</configuration>
<executions>
<execution>
<id>start-jetty</id>
<phase>pre-integration-test</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<scanIntervalSeconds>0</scanIntervalSeconds>
<daemon>true</daemon>
</configuration>
</execution>
<execution>
<id>stop-jetty</id>
<phase>post-integration-test</phase>
<goals>
<goal>stop</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
复制代码

 

在项目目录下执行mvn jetty:jetty   

 

D:\java\Administrator\workspace3\Struts2Example>mvn jetty:jetty

在慢长的等待插件下载过程中(公司网速不给力),我上了一次厕所、倒了一次水。在群里和朋友乱侃了一通。^_^!!

等插件下载完成后就可以运行jetty了,运行命令:mvn jetty:run 

这时就可以在浏览器中访问我们的jsp页面了。

 

重启Jetty,按键盘ctrl+c  根据提示按 y


posted @ 2012-05-02 15:19 顺其自然EVO 阅读(6147) | 评论 (0)编辑 收藏

使用JMeter创建FTP测试计划

FTP服务主要提供上传和下载功能。有时间需要我们测试服务器上传和下载的性能。在这里我通过JMeter做一个FTP测试计划的例子。

当然,JMeter官方网站的用户手册也有例子,但由于版本较早,我也算是对自己学习的一个总结,所以再整理一个。

* 本人使用的是JMeter2.4版本。

* 测试的服务器是IP:124.205.228.54  (由于找不到FTP站点,所以在“主机屋网站http://www.zhujiwu.com申请了一个免费的FTP空间”)

1.创建一个线程组

2.线程组--->添加--->配置元件--->FTP请求缺省值。

3.线程组--->添加--->Sampler--->FTP请求

说明:

IP    为你FTP服务的IP

Remote file 为你FTP服务器上的一个文件。

local file  为本地你存放到本机上的路径。

选择   get(RETR)  为下载方式。

填写你的FTP服务器的用户名密码。

3.按照第二步的方式再添加一个“FTP请求”。

说明:

IP    为你FTP服务的IP

Remote file 为你要上传到FTP服务器上的文件路径及文件名

local file  为本地你要上传的文件的路径。

选择   put(RETR)  为上传方式。

填写你的FTP服务器的用户名密码。

4.添加一个监控器:线程组--->添加--->监控器--->Spline Visualizer

一个FTP计划创建成功 :)

参考资料:

http://jakarta.apache.org/jmeter/usermanual/build-ftp-test-plan.html

http://www.51testing.com/?uid-23852-action-viewspace-itemid-16560

分类: jmeter

posted @ 2012-05-02 14:50 顺其自然EVO 阅读(418) | 评论 (0)编辑 收藏

Eclipse连接MySQL数据库(傻瓜篇)

  本来不想写这么简单人文章,在百度上搜索我这个标题,完全符合标题的一大堆。但我按照那些文章捣鼓了很久,就是不行。

我的环境:MySQL:mysql-essential-5.1.51-win32

     jdbc驱动:我已经上传到csdn上一个:http://download.csdn.net/source/3451945

     Eclipse:任意版本,免费的,可以百度的到。

1。MySQL安装,不会的朋友可以看连接:http://www.duote.com/tech/1/2430_1.html

    下面来创建一个数据: 

复制代码
mysql>CREATE DATABASE test;   //创建一个数据库

mysql
>use test; //指定test为当前要操作的数据库

mysql
>CREATE TABLE user (name VARCHAR(20),password VARCHAR(20)); //创建一个表user,设置两个字段。

mysql
>INSERT INTO user VALUES('huzhiheng','123456'); //插入一条数据到表中
复制代码

  

2。打开Eclipse,创建一个项目(my),

操作:右键点击my--->build Path--->add external Archiver...选择jdbc驱动,点击确定。

我的项目列表:



3。驱动已经导入,下面我们来写一个程序验证一下

复制代码
import java.sql.*;
public class MysqlJdbc {
public static void main(String args[]) {
try {
Class.forName(
"com.mysql.jdbc.Driver"); //加载MYSQL JDBC驱动程序
//Class.forName("org.gjt.mm.mysql.Driver");
System.out.println("Success loading Mysql Driver!");
}
catch (Exception e) {
System.out.print(
"Error loading Mysql Driver!");
e.printStackTrace();
}
try {
Connection connect
= DriverManager.getConnection(
"jdbc:mysql://localhost:3306/test","root","198876");
//连接URL为 jdbc:mysql//服务器地址/数据库名 ,后面的2个参数分别是登陆用户名和密码

System.out.println(
"Success connect Mysql server!");
Statement stmt
= connect.createStatement();
ResultSet rs
= stmt.executeQuery("select * from user");
//user 为你表的名称
while (rs.next()) {
System.out.println(rs.getString(
"name"));
}
}
catch (Exception e) {
System.out.print(
"get data error!");
e.printStackTrace();
}
}
}
复制代码

点击运行程序:  

Success loading Mysql Driver!

Success connect Mysql server!

huzhiheng  

出现上面结果,说明你连接数据库成功。

4。可以查看到MySQL里面的内容,那我们是不是想往MySQL中插入数据呢。
下面的例子,往MySQL的user表中插入100条数据
复制代码
import java.sql.*;

public class Myjproject {
public static void main(String args[])
{
try {
Class.forName(
"com.mysql.jdbc.Driver"); //加载MYSQL JDBC驱动程序
//Class.forName("org.gjt.mm.mysql.Driver");
System.out.println("Success loading Mysql Driver!");
}
catch (Exception e) {
System.out.print(
"Error loading Mysql Driver!");
e.printStackTrace();
}
try {
Connection connect
= DriverManager.getConnection( "jdbc:mysql://localhost:3306/test","root","198876");

int num=100;
PreparedStatement Statement
=connect.prepareStatement("INSERT INTO user VALUES(?,?)");
for(int i=0;i<num;i++) //定义个100次的循环,往表里插入一百条信息。
{
Statement.setString(
1,"chongshi"+i);
Statement.setString(
2,"bo"+i);
Statement.executeUpdate();
}

// } catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
// System.out.println("An error has occurred:"+e.toString());
// e.printStackTrace();
}catch(SQLException e)
{
}
}
}
复制代码

 

5.下面我们打开MySQL数据库进行查看 

mysql> show tatabases;  //查看所数据库
mysql> use  test;    //使test为当前要操作的数据库
mysql> show tables; //查看当前数据库的所有表
mysql> select *from user;  //查看当前表(user)的所有信息


注意:如果不能正常连接你的数据库,请检查你代码中,驱动、用户名、密码、表等信息是否对应无误,不要把别人的代码直接复制过来,看也不看就用。

分类: Java

posted @ 2012-05-02 14:48 顺其自然EVO 阅读(524) | 评论 (0)编辑 收藏

一种简单的数据库性能测试方法

   这两天接到一个任务,要测试一个服务器的性能,客户要求向数据库内 1000/s(每插入一千条数据)  的处理能力,当时脑子赌赛了,想的是用LR来进行,由于LR接触不深,只知道LR实现参数化的时候可以调用数据库里面的数据,往里面大批量的插入数据,以 前没试过。

    翻阅了一下资料,找一到了一篇《一种特殊的数据库性能测试》,大概思路是:通过编写一java程序来循环插入数据,编写一个批处理文件来调用java程 序。再通过LR的system()函数来调用批处理文件来进行压力测试。但是对于我这种菜鸟来说,好多细节不懂。比如那个批处理就让我很为难。呵呵。

  其实,通过jmeter很简单就可以完成,可以参考我以前的一篇文章《jmeter创建数据库(MySql)测试》。

  前提条件:一个数据库:test   数据库下面有一张表:user   表中有两个字段:username、passworld 。

  要求:往数据库内大批量插入数据,1000/s  

其实和之前的方法一样,为了简单,我还是把截图贴出来吧。

1.

创建一个测试计划,将我们所使用的数据库驱动包导入。

2.

添加一个线程组,并设置我们的虚拟用户数、启动时间、和循环次数

3.

创建一个线程,并在线程下面,创建一个JDBC Connection Configuration ,设置相关信息。

4.

创建一个JDBC Request.我们需要对数据库做插入操作。(详细设置,看截图上的说明)

5.

添加监听器,我们这里选择添加“图形结果”和“查看结果树”,点击菜单栏上的“启动”--->运行。

查看我们的运行结果。

在测试的过程中,通过数据库命令,可以查看当前数据库插入了多少数据

分类: jmeter

posted @ 2012-05-02 14:35 顺其自然EVO 阅读(705) | 评论 (0)编辑 收藏

LR有的JMeter也有之三“集合点”

继续上两篇的文章内容和思路进行。(文思如尿崩,谁与我争锋----韩寒)哈哈!

 

集合点:简单来理解一下,虽然我们的“性能测试”理解为“多用户并发测试”,但真正的并发是不存在的,为了更真实的实现并发这感念,我们可以在需要压力的地方设置集合点,

还拿那个用户和密码的地方,每到输入用户名和密码登录的地方,所有的虚拟用户都相互之间等一等,然后,一起访问。(红军排长说:等一等!大家一起冲啊!这样给敌人的压力是很大的。嘻嘻!)

1.

接着之前创建的脚本,右键点击 step1---->定时器---->Synchronizing Timer

这样子就添加了一个“集合点”,下面来设置一下集合点。

 

2.

我们添加完之后的列表是这个样子的,

 发现了没,我们集合点的位置不对,应该在登录的前面才对。怎么弄呢?

点击“synchronizing Timer”拖动到“登录”面前的位置,不要松鼠标的左键,再点击鼠标的右键,选择在“之前插入”---有点难噢,慢慢来!

         

OK!!

posted @ 2012-05-02 14:34 顺其自然EVO 阅读(455) | 评论 (0)编辑 收藏

仅列出标题
共394页: First 上一页 331 332 333 334 335 336 337 338 339 下一页 Last 
<2024年11月>
272829303112
3456789
10111213141516
17181920212223
24252627282930
1234567

导航

统计

常用链接

留言簿(55)

随笔分类

随笔档案

文章分类

文章档案

搜索

最新评论

阅读排行榜

评论排行榜