一曲新词酒一杯,去年天气旧亭台,夕阳西下几时回? 无可奈何花落去,似曾相识燕归来,小园香径独徘徊。

发布新日志

  • 开发出高性能的网站,(三):压缩和其他服务器端的技术

    pele 发布于 2007-01-08 14:04:04

    在第一部分 , 我们讲了代码优化的20个技巧,这些代码优化都是针对开发者源代码的;在第二部分 , 我们谈了缓冲控制。我们在此第三部分中,将来和大家一起看看其他的服务器端的技术,来提升网站的速度,我们先来看看HTTP压缩。

    什么是HTTP压缩?

    HTTP压缩(或叫HTTP内容编码)作为一种网站和网页相关的标准,存在已久了,只是最近几年才引起大家的注意。HTTP压缩的基本概念就是采用标准的gzip压缩或者deflate编码方法,来处理HTTP响应,在网页内容发送到网络上之前对源数据进行压缩。有趣的是,在版本4的IE和NetScape中就早已支持这个技术,但是很少有网站真正使用它。Port80软件公司做的一项调查显示,财富1000强中少于5%的企业网站在服务器端采用了HTTP压缩技术。不过,在具有领导地位的网站,如Google、Amazon、和Yahoo!等,HTTP内容编码技术却是普遍被使用的。考虑到这种技术会给大型的网站们带来带宽上的极大节省,用于突破传统的系统管理员都会积极探索并家以使用HTTP压缩技术。

    我们可以在浏览器发出的Accept请求的头部看到HTTP内容编码的键值。我们来看看Mozilla Firefox浏览器的这个请求,如下,我们特别注意一下AcceptAccept-LanguageAccept-Encoding,和 Accept-Charset的头(header):
    GET / HTTP/1.1
    Host: www.port80software.com
    User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.6) Gecko/20040206 Firefox/0.8
    Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9, text/plain;q=0.8,video/x-mng,image/png,image/jpeg,image/gif;q=0.2,*/*;q=0.1
    Accept-Language: en-us,en;q=0.5
    Accept-Encoding: gzip,deflate
    Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
    Keep-Alive: 300
    Connection: keep-alive

    这些个"Accept"值会被服务器用到,进而决定将适当的内容通过内容协商(Content Negotiation)发回来—这是非常有用的功能,它可以让网站服务器返回不同的语言、字符集、甚至还可以根据使用者的习惯返回不同的技术。关于内容协商的讨论很多,我们这就不再多讲。我们主要来看看和服务器端压缩有关的一些东西。Accept-Encoding表明了浏览器可接受的除了纯文本之外的内容编码的类型,比如gzip压缩还是deflate压缩内容。

    我们下面来看看IE发出的请求headers,我们可以看到类似的Accept-Encoding值:
    GET / HTTP/1.1
    Host: www.google.com
    User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; .NET CLR 1.1.4322)
    Accept: image/gif,image/x-xbitmap,image/jpeg,image/pjpeg, application/vnd.ms-excel,application/vnd.ms-powerpoint,application/msword, application/x-shockwave-flash,*/*
    Accept-Encoding: gzip,deflate
    Accept-Language: en-us
    Connection: keep-alive

    假设当今主流的每种浏览器都支持gzip和deflate编码(那些不支持的也不会发出Accept-Encoding),我们可以简单的修改一下网站服务器,从而返回压缩内容到这些浏览器上,并返回标准(也就是没有压缩的)内容到其他的浏览器上。在下例中,如果我们的浏览器告诉Google它不接受内容编码,我们会取回3,358字节的数据;如果我们发出Accept-Encoding,再加上应答header告诉我们Content-Encoding: gzip,我们则会取回仅仅1,213字节 。用浏览器的查看源代码功能来看源代码,我们看不出什么差异,但如果使用网络跟踪的话,我们就会发现其响应是不同的。

    图一: Google压缩 / 非压缩对比

    图一: Google压缩 / 非压缩对比

    上例中,虽然文件不大,但是效果依然很明显—压缩后比原来小了74%。再加上我们前面两部分谈到的HTML、CSS、和Javascrīpt代码优化,Google在提升网站性能上的成果非常惊人—它的一个网页居然可以放在一个TCP响应包里。

    虽然Google在带宽上的考虑也远远超出其他一般的网站,HTTP内容编码进一步让HTML、CSS、和Javascrīpt等瘦身50%甚至更多。不好的地方是,HTTP内容编码(词语‘压缩’和‘内容编码’在本文中基本上是一个意思) 基本上针对文本内容很有效,对于图像和其他二进制文件的压缩效果就一般了,有时可能根本没有效果,但总的来说,即使有很多二进制文件的时候,整体上可以瘦身15%到30%那么多。

    HTTP内容编码的服务器端支持

    如果你现在认同HTTP压缩的价值,下一个大问题是:你如何来实施?在Apache网站服务器上,可以使用mod_deflate来进行HTTP内容的编码。在微软的IIS上,就有些麻烦了。虽然IIS 5可以支持gzip的编码压缩,但实施起来还是超级麻烦,尤其考虑到因为各种浏览器中细微差异来进行各种细致参数调整的时候。所以在IIS 5上,还是要考虑第三方的采用ISAPI过滤器的压缩插件,比如httpZip 就是最好的一种。IIS 6上集成了压缩功能,也更快更灵活,但是还是配置起来比较复杂。ZipEnable 带给我们第一个专为IIS 6集成压缩细致管理的工具—并且还有浏览器兼容情况监测功能。

    服务器内容编码的实质

    当实施HTTP压缩时,要平衡考虑一些因素;如果将服务器配置成‘输出’的内容压缩方式,虽然可以降低带宽的使用,但同时却增加了CPU的开销。多数情况下,这不是什么大问题,尤其当网站服务器所作工作很少的时候。不过,在网络浏览很繁重的网站服务器上,运行相当多的动态内容就可能会达到CPU工作的极限,这时再进而进行压缩的话,CPU可能会超负荷运行了。通过添加而外的服务器硬件资源,当然可能会减轻这种问题,并让我们享受通过压缩而节省下来的带宽, 但最后带宽和CPU的问题还是要看哪个成本更高。

    说到底,系统管理员和网站开发者是否对HTTP的压缩有兴趣,还是要看最后的效果如何。当我们明显的发现带宽的负载下来了,那么访问者也可能会明显感觉转载网页的速度慢了。因为,压缩产生和解压缩会带来的CPU负载, TTFB (time to first byte)也通常会增加,这样浏览器渲染网页的速度也会降下来,但这还算是很好的平衡,因为数据压缩后传输的包变小了、变少了,提交的速度会变快,这个快速回补偿网页渲染的慢速。然而,对于宽带用户来说,这样的改善可能就不明显了。不过,这两种情况下,对于网站建设者来说,都可以节省一些网络上的投资。当然,如果可感知的反应时间对某网站来说是主要目标,并且网站的访问者很多都还使用拨号上网,那么本文的第二部分钟所讲的缓冲控制就是比较好的性能提高策略。

    最后,HTTP内容编码的另一个潜在问题是和由脚本产生的网页带来的服务器负载有关的,比如PHP和ASP。在此种情况下,主要的问题是,网页内容每次请求可能会被再压缩,这样就会给服务器增加更多的负载(相对压缩静态内容来说)。如果,网站中所有的网页都是在请求时生成的话,那么使用HTTP内容编码就得格外小心了。还好,很多商业上使用的压缩插件,直到如何对内容进行缓冲,但业余(较便宜的)的压缩工具可能就没有这些特性了。

    动态网页:立即生成,还是稍后生成?

    有趣的是,很多的开发者都是在网站被访问时开始动态的生成很多甚至全部他们网站的网页。比如,http://www.domain.com/article.php?id=5就是一个通用的URL,它暗示了某个网页是由数据库查询时或填充模版生成的。这种常用方法的问题是,在很多情况下,在请求时间生成一个网页是pointless的,因为很多时候这个主要的静态的(所谓的静态的动态网页、或脚本网页),其内容很长时间也不变化,这显然对于提高网页装载速度没有什么帮助。实际上,在一个高负荷的网站这种方式会严重的降低服务期的性能。

    要避免不必要的动态网页的生成,有一个方法是,每次有变化时,则预先生成有内容的静态.html网页。如果这些生成的.html网页,还是经过了代码优化(在本文第一部分中过这些描述方法),则更好。这不仅会让服务器交付这些网页更快变得更容易,而且这些技术还会使搜索引擎更友好。

    不幸的是,在很多情况下,简单的生成动态的HTML网页并不太容易,也为很多网页只有在网页被访问时才能够正确的生成动态内容。在这种情况下,你最好是对网页进行‘烘焙’生成快速执行的形式。在ASP .NET的情况下,这种形式则是二进制代码,在服务器端执行的特别快。不好的地方是,在用户访问之前,服务器需要先执行这些网页强制执行这些字节代码。还好,在ASP .NET 2.0中,这些问题将得到改善。在PHP中,一些诸如Zend等的优化软件是不错的投资。

    对于提交静态和动态网页、或者HTML和图像的不同需要来说,考虑一下针对物理服务器或其他的硬件上的加速可能,也是比较明智的选择。为了网页加速而加强硬件方面的投入的另一个方法是专业化—不同的部件进行不同的工作,这样产生出最大的效率。虽然,本文是从代码和网站服务器校对来说明如何提高网站的性能的,我们也不妨讨论讨论其他相关的元素。

    对网站服务器进行涡轮增压

    加速网站要考虑的一个重点是服务器软件和服务器硬件。先谈软件,网站系统管理员不太可能来回因为易用性问题、性能问题、安全问题等在Apache和IIS之间切换来切换去。简言之,网站服务器和其底层的操作系统之间的关系错综复杂、互相影响,再进行系统或服务迁移则更是繁重且有风险的工作。所以,你如果真的考虑放弃一种网站服务器而使用另一种的话,你必须严肃认真的好好考虑这个问题;而如果速度是考虑的第一要素的话,建议你考虑一下Zeus。

    再谈硬件,如果要考虑升级硬件,则先仔细分析一下服务器上的主要任务。在静态网站上,主要的工作是调整网络连接和把文件从磁盘拷贝到网络上。要加速这样类型的网站,你得把注意力放在高速硬盘子系统和高速网络子系统上,另外还得有足够多的内存来处理并发请求。实际上,你可能得给服务器添加大量的内存,从而为经常使用的对象尽可能的增加内存缓冲来减轻磁盘的存取。有趣的是,CPU的速度在这里却不是十分关键。虽然不能否认CPU对网站的整体性能有影响,但瓶颈主要发生在磁盘上。但是,当网站处理动态网页和静态网页差不多一样多的时候,处理器就显得很关键了,但即便如此高速磁盘或双网卡还是更有效一些。另一种情况下,除了要处理动态网页,还要处理其他占用CPU的操作(比如SSL和HTTP压缩等)的时候,CPU就显得十分关键了。换句话说,要加速网站服务时,服务器具体所作的工作决定着什么类型的硬件资源更需要增强。

    当你没有那么多增加服务器硬件或软件的预算时,还有一些物美价廉的解决方法。比如,你可以对服务器的TCP/IP设置进行优化,这样依赖TCP/IP网络的HTTP便可以最优运行。TCP/IP的设置优化里,有一项是TCP的接受窗口,可以把它调整成最适合应用或者最适合网络连接的,或者是针对确保TCP连接的一些参数(如ACK或TCP_NODELAY等)进行调整,根据具体情况设置成使用或不使用。还有一些参数,比如TIME_WAIT时间等,也是可以进行调整的。但要记住,不管怎么调整这些网站服务或操作系统的参数,都必须进行真实的加载试验以验证你的调整会不会反而减慢用户访问的服务或带来新的问题。此外,一定要弄懂这些参数之后,再进行调整。

    通过分工进行加速

    网站加速还可以考虑的一个出发点是,不同的网站内容可能会拥有不同的提交特性。考虑到不同的内容,其特性也不同,我们可以用多个服务器,每个服务器来执行不同的内容处理,这样可能比用服务器池(server farm)中的每一个服务来处理同样的任务要好得多。

    我们来看一个分工进行加速的简单例子。当你的商务网站给购物车或外部网使用SSL加密的时候,你会发现当有多个用户同时访问的时候,SSL加密带来HTTPS段的明显负载会使你的服务器的性能会急剧下降。这种情况下,把流量分配给另一台服务器,其意义就十分明显了。比如,把你的主站点放在www.domain.com上,把结账的处理部分放在shop.domain.com上。这个shop.domain.com就是一个专门处理SSL流量的服务器,可能会用到SSL加速卡。采取分工的方式,可以让你专心处理结账的用户的SSL流量,而不至于像以往SSL的处理会导致服务器整体性能的下降。对于图像和其他重量级的二进制的比如PDF文档或.exe文档,服务器处理其下载可能要花些力气,这些连接通常持续的时间比一般的连接都长,会消耗大量宝贵的TCP/IP资源。进而,对于这些媒体资源(PDF、.exe、图像等)的处理,我们也并不需要把它们和文本资源(HTML、CSS和Javascrīpt等)等同对待处理。在这种情况下,让处理文本资源的服务器有高性能的CPU,让处理媒体资源的服务器有大带宽,是有的放矢的解决之道。

    分工还可以进一步应用到网页的生成上。我们可以考虑把生成网页的工作单独放在一个服务器上,把处理静态内容的工作放在另一个服务器上。现在已然有很多网站是采取这样的模式了,这其中很多网站会使用一个叫做Squid的反向代理(reverse proxy)。在设置过程中,代理服务器专门提供静态的内容,速度很快;而后台的服务器则可以专心处理在访问时才会产生的动态内容。缓冲控制策略和规则,我们在第二部分中谈到过,在这时的设置过程中就显得十分重要了;我们得确保代理服务器的缓冲中储存的内容在共享缓冲中是安全的。

    为了争夺市场而提速

    我们刚才谈的那些东西主要是一些低成本的加速技术,在我们本文即将结束的时候,我们来看看一些需要软硬件成本很高但收益可观的方法。目前市场上提供有一些需要花些钱的独特加速设备,它们可以进行比如网络连接分流、压缩、缓冲、和其他等技术,从而达到加速的目的。 如果你不在乎投资高带宽的话,这些解决方案就十分有效,但是大多数的网站还是更喜欢我们先前介绍过的物美价廉的方法,比如代码优化、缓冲、和HTTP编码等。

    就算你有很多资金,可以投资一个服务器池(server farm)、并添加最高档的加速设备,也使用压缩和缓冲技术等,但你还是会最后达到一个极限。要想进一步再提速,还有最后一招:把内容放在离访问者最近的地方,有可能的话,在那个地方再实施上述各类技术(如压缩、缓冲等)。你肯定注意过,有些网站提供镜像服务器等,这样世界各地的访问者就可以就近访问所需内容。不过,还有比这个更有地理分布意义的方法,并且可以透明的让访问者使用。内容分发网络(Content Distribution Network – CDN),比如Akamai,就可以让我们把重型内容(如图象和其他二进制内容等)搬移到离访问者更近的地方,这样通过内容分发网站在世界各地的边缘缓冲,访问者访问起来就会更快。这种方式带来了性能上极大提高,目前世界级的一些大网站都在使用。虽然这算不上是经济实用的方法,但作为这些方法的最后补充,放在这里以飨读者。
  • 如何从一名测试员转型为测试管理人员

    sunxy5291 发布于 2007-05-10 08:47:22

    如果你是测试员或是高级测试员,有志转向管理发展,那么需要加强以下内容,至少要做到几点:

    1. 测试计划的编写(要结合测试的项目,能以此来控制和确定测试所需人员,设备及时间来管理测试时间)

    2. 要熟悉BUG跟踪工具及软件测试流程.(如: TD, Bugzilla, CQ等)

    3. 要熟悉配置管理工具. (如: CVS, VSS等)

    4. 要熟悉自动化工具.(例如:WinRunner, QTP, Robot, RFT, Automation等,能结合录制完的脚本编写代

    码)

    5. 要熟悉压力及性能测试工具.(例如: LoadRunner, webload, silkperformance等,能结合相关数据,分

    析出性能瓶颈)

    6. 要熟悉或精通一门语言. (例如: Java, C++)

    7. 要熟悉数据库.(例如: Oracle, DB2, SQLServer, MySQL)

    8. 要熟悉主流操作系统. (例如: HP Unix, IBM AIX, Sun Solaris, Red Hat Linux, SuSE Linux,

    Windows)

    9. 能用英文流利的和老外交流以及往来Email.

    10. 语言表达能力强,表达问题清晰明了.

    11. 沟通能力强,能和上级/开发经理很好的达成测试相关/BUG事宜.

    12. 学习技术的能力要强,能快速上手一个新的技术.

    13. 乐于与人交流.

  • NUnit详细用法(转至wyscorpion)

    5itesting 发布于 2007-01-04 00:38:18

    前一段时间,有人问我在.NET里如何进行TDD开发.这个问题促使我想对NUnit做一个详细的介绍.因为我们大家都知道NUnit是在.NET进行TDD的利器.
           如果你已经知道很多关于NUnit的应用,请指出我的不对之处和提出一些建议,使本文更加完善.如果你对NUnit还不是很了解的话,我建议你还是阅读一下.
         本文分为以下部分:

    1. TDD的简介

    首先什么是TDD呢?Kent Beck在他的<<测试驱动开发 >>(Addison-Wesley Professional,2003)一书中,使用下面2个原则来定义TDD:
    ·        除非你有一个失败的自动测试,永远不要写一单行代码.
    ·        阻止重复
           我想第一个原则是显而易见的.在没有失败的自动测试下就不要写代码.因为测试是嵌入在代码必须满足的需求中.如果没有需求,就没有必要实现任何东西.所以这个原则阻止我们去实现那些没有测试和在解决方案中不需要的功能.
    第二个原则说明了在一个程序中,不应该包含重复的代码.如果代码重复,我想这就是不好的软件设计的象征.随着时间的流逝,它会对程序造成不一致的问题,并且使代码变非常混乱 ,因为我们时常不会记得重复代码的位置.如果发现代码重复,我想我们应该立即删除代码重复.其实这就涉及到重构了.在这里我就不多讲了.
    一般来说,测试分为2种类型,一是程序员自己的测试,另外一种是客户的测试.关于客户测试,我推荐一个FIT的框架,非常不错。在这里,我们讲的TDD就是程序员测试.那么什么是程序员测试呢?我认为就是我们常说的单元测试.既然是单元测试,在.NET里势必会用到某些工具,目前最著名恐怕就是我即将介绍的NUnit了,

    2.NUnit的介绍

    NUnit是一个单元测试框架,专门针对于.NET来写的.其实在前面有JUnit(Java),CPPUnit(C++),他们都是xUnit的一员.最初,它是从JUnit而来.现在的版本是2.2.接下来我所用的都是基于这个版本.
    NUnit最初是由James W. Newkirk, Alexei A. Vorontsov 和Philip A. Craig, 后来开发团队逐渐庞大起来.在开发过程中, Kent Beck 和Erich Gamma2位牛人也提供了许多帮助.看来对于NUnit还真是下了一番力气了.J
     NUnit是xUnit家族种的第4个主打产品,完全由C#语言来编写,并且编写时充分利用了许多.NET的特性,比如反射,客户属性等等.
     最重要的一点是它适合于所有.NET语言.
          如果你还没有下载,可以到http://www.nunit.org/去下载.

    2.1 NUnit的介绍

       Ok,下面正式讲解NUnit.在讲解之前,看看几张图片:
         
    图1  NUnit运行的效果

                         图2   NUnit运行的另外一个效果
           从中我们可以非常容易发现,右边是个状态条,图1是红色的,图2是绿色的.为什么会这样呢?因为如果所有测试案例运行成功,就为绿色,反之如果有一个不成功,则为红色,但也有黄色的.左面的工作域内则是我们写的每一个单元测试.
    通过上面的图片,我想你对NUnit有个总的了解了.
    接下来还是分为2个部分,一是NUnit的布局,另外一部分就是它的核心概念.
    首先熟悉一下NUnit GUI的布局.
    让我们更进一步看一下测试运行器窗口的布局。在右边面板的中间,可以看到测试进度条。进度条的颜色反映了测试执行的状态:
    绿色 描述目前所执行的测试都通过
    黄色 意味某些测试忽略,但是这里没有失败
    红色 表示有失败
    底部的状态条表示下面的状态:
    状态.说明了现在运行测试的状态。当所有测试完成时,状态变为Completed.运行测试中,状态是Running: <test-name> (<test-name>是正在运行的测试名称)。
    Test Cases说明加载的程序集中测试案例的总个数。这也是测试树里叶子节点的个数。
    Tests Run 已经完成的测试个数。
    Failures  到目前为止,所有测试中失败的个数.
    Time  显示运行测试时间(以秒计)
    File主菜单有以下内容:
    New Project允许你创建一个新工程。工程是一个测试程序集的集合。这种机制让你组织多个测试程序集,并把他们作为一个组对待。
    Open 加载一个新的测试程序集,或一个以前保存的NUnit工程文件。
    Close关闭现在加载的测试程序集或现在加载的NUnit工程。
    Save 保存现在的Nunit工程到一个文件。如果正工作单个程序集,本菜单项允许你创建一个新的NUnit工程,并把它保存在文件里。
    Save As允许你将现有NUnit工程作为一个文件保存。
    Reload 强制重载现有测试程序集或NUnit工程。NUnit-Gui自动监测现加载的测试程序集的变化。
    当程序集变化时,测试运行器重新加载测试程序集。(当测试正运行时,现在加载的测试程序集不会重新加载。在测试运行之间测试程序集仅可以重新加载。一个忠告:如果测试程序集依赖另外一个程序集,测试运行器不会观察任何依赖的程序集。对测试运行器来说,强制一个重载使全部依赖的程序集变化可见。
    Recent Files  说明5个最近在NUnit中加载的测试程序集或NUnit工程(这个列表在Windows注册表,由每个用户维护,因此如果你共享你的PC,你仅看到你的测试)。最近程序集的数量可以使用Options菜单项修改,可以访问Tool主菜单。
    Exit退出。
     View菜单有以下内容:
    Expand一层层扩展现在树中所选节点
    Collapse 折叠现在树中选择的节点
    Expand All递归扩展树中所选节点后的所有节点
    Collapse All递归折叠树中所选节点后的所有节点
    Expand Fixtures扩展树中所有代表测试fixture的节点。
    Collapse Fixtures 折叠树中所有代表测试fixture的节点。
    Properties 显示树中现所选节点的属性。
    Tools 菜单由这些项:
    Save Results as XML作为一XML文件保存运行测试的结果。
    Options让你定制NUnit的行为。
    现在看看右边,你已经熟悉Run按钮和进度条。这里还有一个紧跟Run按钮的Stop按钮:点击这个按钮会终止执行正运行的测试。进度条下面是一个文本窗口,在它上方,由以下4个标签:
    Errors and Failures 窗口显示失败的测试。在我们的例子里,这个窗口是空。
     Tests Not Run 窗口显示没有得到执行的测试。
    Console.Error 窗口显示运行测试产生的错误消息。这些此消息是应用程序代码使用Console.Error输出流可以输出的。
    Console.Out窗口显示运行测试打印到Console.Error输出流的文本消息。

    2.2 一些常用属性

            接下来,我将讲述这个框架如何使用.同时也涉及到一些非常重要的概念,我想其客户属性是非常重要的.在NUnit里,有以下几种属性:
    • Test Fixture
    • Test
    下面我将对每种属性一一讲解.

    TestFixtureAttribute

        本属性标记一个类包含测试,当然setup和teardown方法可有可无.(关于setup 和teardown方法在后面介绍)
        做为一个测试的类,这个类还有一些限制
    • 必须是Public,否则NUnit看不到它的存在.
    • 它必须有一个缺省的构造函数,否则是NUnit不会构造它.
    • 构造函数应该没有任何副作用,因为NUnit在运行时经常会构造这个类多次,如果要是构造函数要什么副作用的话,那不是乱了.
    举个例子
      
     1  using System;
     2  using NUnit.Framework;
     3  namespace MyTest.Tests
     4{
     5
     6  [TestFixture]
     7  public class PriceFixture
     8  {
     9    // 
    10  }

    11}

    12
     

    TestAttribute

        Test属性用来标记一个类(已经标记为TestFixture)的某个方法是可以测试的.为了和先前的版本向后兼容,头4个字符(“test”)忽略大小写.(参看http://nunit.org/test.html)
    这个测试方法可以定义为:
            
    public void MethodName()
        从上面可以看出,这个方法没有任何参数,其实测试方法必须没有参数.如果我们定义方法不对的话,这个方法不会出现在测试方法列表中.也就是说在NUnit的界面左边的工作域内,看不到这个方法.还有一点就是这个方法不返回任何参数,并且必须为Public.
       例如:
      
     1using System;
     2using NUnit.Framework;
     3
     4namespace MyTest.Tests
     5{
     6  [TestFixture]
     7  public class SuccessTests
     8  {
     9    [Test] public void Test1()
    10    /*  */ }
    11  }

    12}

    13
    14
    一般来说,有了上面两个属性,你可以做基本的事情了.
    
    
    另外,我们再对如何进行比较做一个描述。
    
    
    在NUnit中,用Assert(断言)进行比较,Assert是一个类,它包括以下方法:AreEqual,AreSame,Equals, Fail,Ignore,IsFalse,IsNotNull,具体请参看NUnit的文档。
    
    

    3.如何在.NET中应用NUnit

      我将举个例子,一步一步演示如何去使用NUnit.

    第1步.为测试代码创建一个Visual Studio工程。

    在Microsoft Visual Studio .NET中,让我们开始创建一个新的工程。选择Visual C#工程作为工程类型,Class Library作为模板。将工程命名为NUnitQuickStart.图4-1是一个描述本步骤的Visual Studio .NET。
     
                                图 4-1: 创建第一个NUnit工程

    第2步.增加一个NUnit框架引用

    在Microsoft Visual Studio .NET里创建这个例子时,你需要增加一个nunit.framework.dll引用,如下:
    在Solution Explorer右击引用,然后选择增加引用
       nunit.framework组件,在Add Reference对话框中按Select和OK按钮。
    图4-2 描述了这步:
     
    图 4-2: 增加一个 nunit.framework.dll 引用到工程

    第3步.为工程加一个类.

    为工程加一个NumbersFixture类。这里是这个例子的代码。
     1using System; 
     2using NUnit.Framework; 
     3  
     4namespace NUnitQuickStart 
     5
     6            [TestFixture] 
     7            public class NumersFixture 
     8            
     9                        [Test] 
    10                        public void AddTwoNumbers() 
    11                        
    12                                    int a=1
    13                                    int b=2
    14                                    int sum=a+b; 
    15                                    Assert.AreEqual(sum,3); 
    16                        }
     
    17            }
     
    18}

    19

    第4步.建立你的Visual Studio 工程,使用NUnit-Gui测试

    从程序->NUnit2.2打开NUnit-gui,加载本本工程编译的程序集.
    为了在Visual Studio .NET中自动运行NUnit-Gui,你需要建立NUnit-Gui作为你的启动程序:
    在 Solution Explorer里右击你的NunitQuickStart工程。
    在弹出菜单中选择属性。
    在显示的对话框的左面,点击Configuration Properties夹
    选择出现在Configuration Properties夹下的Debugging。
    在属性框右边的Start Action部分,选择下拉框的Program作为Debug Mode值。
    按Apply按钮
    设置nunit-gui.exe 作为Start Application。,你既可以键入nunit-gui.exe的全路径,也可使用浏览按钮来指向它。
    图4-3 帮助描述本步骤:
      
    图 4-3:将NUnit-Gui 作为工程的测试运行器

    第5步.编译运行测试.

     现在编译solution。成功编译后,开始应用程序。NUnit-Gui测试运行器出现。当你第一次开始NUnit-Gui,它打开时没有测试加载。从File菜单选择Oprn,浏览NUnitQuickStart.dll的路径。当你加载了测试的程序集,测试运行器为加载的程序集的测试产生一个可见的表现。在例子中,测试程序集仅有一个测试,测试程序集的结构如图4-4所示:
     
     图 4-4: 测试程序集的测试在 NUnit-Gui中的视图
    按Run按钮。树的节点变为绿色,而且测试运行器窗口上的进度条变绿,绿色代表成功通过。

    4.其他的一些核心概念

       上面的例子介绍了基本的NUnit特性和功能. TestFixture, Test, 和 Assert是3个最基本的特征,我们可以用这些特性进行程序员测试了.但是有的时候,你觉得这3个远远不够,比如有的时候打开一个数据库连接多次,有没有只让它打开一次的方法呢?如果我想把测试分类,应该怎样实现呢?如果我想忽略某些测试,又应该如何去完成呢?不用担心,NUnit已经有这样的功能了.
    下面我们一一作出回答.

    SetUp/TearDown 属性

    在早期给的test fixture定义里,我们说test fixture的测试是一组常规运行时资源.在测试完成之后,或是在测试执行种,或是释放或清除之前,这些常规运行时资源在一确定的方式上可能需要获取和初始化.NUnit使用2个额外的属性:SetUpTearDown,就支持这种常规的初始化/清除.我们上面的例子来描述这个功能.让我们增加乘法.
     1using System; 
     2using NUnit.Framework; 
     3  
     4namespace NUnitQuickStart 
     5
     6            [TestFixture] 
     7            public class NumersFixture 
     8            
     9                        [Test] 
    10                        public void AddTwoNumbers() 
    11                        
    12                                    int a=1
    13                                    int b=2
    14                                    int sum=a+b; 
    15                                    Assert.AreEqual(sum,3); 
    16                        }
     
    17                        [Test] 
    18                        public void MultiplyTwoNumbers() 
    19                        
    20                                    int a = 1
    21                                    int b = 2
    22                                    int product = a * b; 
    23                                    Assert.AreEqual(2, product); 
    24                        }
     
    25  
    26            }
     
    27}
     
    28
       我们仔细一看,不对,有重复的代码,如何去除重复的代码呢?我们可以提取这些代码到一个独立的方法,然后标志这个方法为SetUp 属性,这样2个测试方法可以共享对操作数的初始化了,这里是改动后的代码: 
     1using System; 
     2using NUnit.Framework; 
     3  
     4namespace NUnitQuickStart 
     5
     6            [TestFixture] 
     7            public class NumersFixture 
     8            
     9                        private int a; 
    10                        private int b; 
    11                        [SetUp] 
    12                        public void InitializeOperands() 
    13                        
    14                                    a = 1
    15                                    b = 2
    16                        }
     
    17  
    18                        [Test] 
    19                        public void AddTwoNumbers() 
    20                        
    21                                    int sum=a+b; 
    22                                    Assert.AreEqual(sum,3); 
    23    &n
  • IP网络的测试方法

    zye2006_10 发布于 2006-12-26 16:35:13

        今天在整理资料的时候,发现了下边的文章,不知道我在什么时候搜集的,具体的作者也不知道,今天贴到这与大家共享。原文如下:

    IP网络的测试方法

    作者:unknown 更新时间: 2005-03-20  
     


     
      随着IP附应用时及和深入,IP网络的建设、维护和故障诊断面临着巨大的挑战:网络的规模越来越大、组成网络的设备越来越复杂、在网络中运行的软件系统越来越庞大、网络承载的业务越来越多.

      网络测试是保证网络高性能、高可靠性和高可用率的基本手段,它在IP网络建设和发展中的重要意义正得到日益广泛的认可。

    网络测试

      网络是一个很复杂的系统,通常人们把网络分为不同的层次予以简化。在网络测试中,我们可以把网络分为3个不同的层次:设备层、系统层和应用层,因此网络测试正是轩对这3个层次来进行的。

      网络设备测试主要包括功能测试、性能测试、一致性和互通性测试等几个方面。网络系统测试包括物理连通性、基本功能和一致性的测试、网络系统的规划验证测试、性能测试、流量测试和模型化等。网络应用测试是测试网络系统支持各种应用的能力。完整的网络测试包含完成上述3个层次的所有测试。

      网络测试主要包括测试方法、测试工具和测试经验等3个方面的内容。无论是测试方法的设计、测试工具的发明和运用还是测试经验的积累,都有很高的技术要求,其中测试方法是核心。网络测试的方法和手段因测试的目的而有所不同。典型的网络设备测试的方法有2种:第一种方法是使用网络测试设备单独对产品进行测试;第二种方法是将设备放在具体的网络环境中,通过分析该产品在网络中的行为对其进行测试,这种网络环境多数是用仿真的方式实现的。测试工具主要有线缆测试仪、协议分析仪和网络智能分析仪等。实际的网络在设备、拓扑、管理维护等各方面千差万别,可能出现的问题也是五花八门的,测试人员除了要掌握必须的网络知识外,还需要有丰富的系统集成和现场测试的经验。

      网络系统的建设一般经历规划、设计、部署、运行和升级五个阶段。网络测试应贯穿其中的每个阶段。由于技术或者经济的原因,实际网络测试的应用和理论上还有较大的差距。无论从经济的角度还是从时间的角度来看,用户都很难自己来完成所有的测试。用户在选购设备时可以参照由设备提供商提供的第三方测试机构对其设备的测试报告,依据测试报告和自身的需求选择设备。在网络设计施工完成之后,应该由施工单位以外的测试机构对网络进行网络系统测试,以检验工程质量。最后在试运行阶段对网络承载业务和应用的能力进行测试,即进行网络的应用测试。但是,我国网络测试起点较低,虽然已经成立,了多家开展网络测试的机构,但至今还没有形成相对比较权威的网络测试机构,我国的网络测试技术和市场都有待发展。

    IP网络的测试技术

      IP网络测试和上述所有的网络测试一样,包括对网络设备层、系统层和应用层的测试。与其它网络测试不同的是:(1)IP网络中的设备与电信网中的设备在性能、安全性和稳定性方面有较大的区别,它们原先更多的是用于计算机互联的设备;(2)IP网络是IP网络测试的目标,它的网络层协议采用IP协议,IP协议并不保证网络数据的可靠性,它采用“尽力而为”的方式转发数据包;(3)IP网络以传输数据业务为主,业务高很高的突发性,IP网络几乎可以承载任何业务,因此网络应用层的测试比较复杂。

    IP网络设备测试

      我们就以太网交换机的测试为例说明具体的网络设备测试。

      首先要分析交换机的物理特性,即对诸如外观(包括颜色、重量、尺寸和包装等)、端口配置、扩展能力等用户可以直接了解的设备信息的测试,主要的测试方法是目测。这些参数和交换机本身的功能和性能没有关系,但是对用户来说则很重要,将直接影响用户对设备的评价。一款颜色:搭配不和谐、尺寸很大的交换机,显然不会成为用户优先选择的目标。

      进一步的测试需要一台带有收发端口的测试仪。测试仪与被测交换机有两种连接方法。

      在第二种连接方式下,如果测试仪(发送)和测试仪(接收)之间没有计算机的控制,则无法完成部分精度要求较高的测试项和在发送与接收之间有时间或逻辑关系要求的测试顶,如流量测试等。

      在测试仪与被测设备连接完成以后,在开始测试之前,还要首先配置被测的交换机,包括对软件和硬件的配置,特别是配置交换机支持的协议并予以激活。

      首先是对交换机进行功能测试,目的是检测设备是否能够完成交换机这类设备所应具备的功能,如帧的转发、过滤、流量控制、VLAN、生成树协议等。

      接着进行性能测试,目的是了解交换机完成各项功能时的性能情况。交换机性能测试的参数包括吞吐量、时延、帧丢失率、处理背靠背数据帧的能力、地址缓冲容量、地址学习速率等。RFC1242和RFC2285分别定义了网络互联设备和LAN交换设备测试的基准术语,RFC2544和RFC2889则分别定义了网络互联设备和LAN交换设备测试的基准方法。这几个RFC是测试网络设备时参考的标准。

      完成上述测试之后,需要进行一致性和互通性测试,以验证交换机是否符合各项规范的要求,包括协议的一致性,确保交换机和其他的网络设备进行互联时不会出现问题。

      对交换机设备的测试最终应提供一份完整的测试报告,测试报告对在这次测试中的测试对象、测试工具、测试环境、测试内容、测试结果等进行详细论述。测试报告中包括对各测试项目的测试结果,应以数字、图形、列表等方式记录下来。完整、客观的设备测试报告是购买设备的重要参考。

    IP网络系统测试

      IP网络系统测试的第一步是了解所测网络的状况,包括网络所属单位的情况、网络设备情况、网络主要应用、使用该网络的人员情况、网络中存在的问题等等。对网络状况的调查可以明确测试的对象、目的、要求等,为制定详细的测试方案做好准备,对网络设备的调查可以为所测网络建立详细的网络文档。网络文档的内容包括网络拓扑结构图,路由器和交换机的生产厂家、型号、内部参数配置,服务器和工作陆的生产厂家、型号、内存、硬盘、网卡的序列号和MAC地址等,IP地址、防火墙和操作系统参数配置等。

      了解了网络基本状况后,就可以根据测试要求拟定详细的测试方案。

      物理连通性、基本功能和一致性的测试是最基本的网络系统测试内容,其中主要是线缆测试,用以查明所测线缆及布线是否符合设计要求和国际标准。如果线缆的安装不符合各类标准,就应该绘出具体的各种类型电缆管脚的连接图。

      模拟和仿真是规划验证测试的两个基本手段。模拟即用软件的方法建立虚拟的网络系统及其运行模型,通过设置配置参数模拟实际环境下的网络运行,并给出对该网络的评价。仿真则是建立真实的试验环境来模拟实际的网络运行。模拟和仿真对大型网络的规划设计很有意义,它可以在网络实际建设之前了解网络的特性,或者发现规划设计中的缺陷,大大降低网络建设的风险。但是模拟和仿真本身需要许多资金和时间,因此在网络建设中各单位会参照具体情况来决定是否要做这项测试。

      性能测试可以分为被动测试和主动测试。被动测试就是用仪表监测网络中的数据,通过分析采集到的数据判断网络性能状况。被动测试在不影响网络正常工作的情况下测试。主动测试通过向网络中发送特定的数据包来分析网络系统的性能。不论是被动测试还是主动测试,都需从网络中采集数据。一个IP网络系统可以分为物理层、数据链路层、网络层和应用层。IP网络系统的性能测试应该分别针对物理层、数据链路层和网络层进行。如以太网,物理层的测试包括碰撞分析、错误统计和是否有随机能量、无格式的帧和信号回波等,数据链路层的测试包括流量分析、错误帧(FCS错误帧、长帧、短帧和延迟碰撞)统计等,网络层的测试包括响应时间测试、网络层协议分析、IP路由分析等。

      流量测试和模型化的工作有利于提高整个网络的运行效率,其中涉及到运用一些很深的数学工具和丰富的网络经验,许多关键技术还有待研究。

    IP网络应用测试

      完成IP网络设备测试和系统测试之后就可以在网络上加载各种应用,各种网络应用的性能水平与网络的类型、网络本身的性能有直接关系。IP网络应用测试是IP网络测试中最高层次的测试内容,主要测试IP网络对应用的支持水平,如网络应用的性能和服务质量的测试等。另外,IP网络应用测试和网络应用本身直接相关,对于不同的网络应用,有不同的测试内容和测试方法。在部署VoIP时,需要测试网络中的交换机和路由器设备能否有效地支持语音流量和语音QoS等,在测试用于视频传输的网络时,需要测试视频传输在IP网络中的性能以及网络用户是否能够得到满意的视频质量等。

     
  • 测试经验总结1:界面测试

    natureyxu 发布于 2006-12-22 12:33:38Top 3 Digest 3

    1.应验证界面显示内容的完整性:

    a)        报表显示时应考虑数据显示宽度的自适应或自动换行。

    b)        所有有数据展现的界面(如统计、查询、编辑录入、打印预览、打印等),必须使测试数据的记录数超过一屏/一页,以验证满屏/页时其窗体是否有横向、纵向滚动条或换页打印,界面显示是否正常;

    2.应验证界面显示内容的一致性:

    a)        如有多个系统展现同一数据源时,应保证其一致性;

    3.应验证界面显示内容的准确性:

    a)        对于报表中的所有字段值都应该有明确的定义,对于无意义的字段值,不应该显示空,应显示“--”或“/”,表示该字段值无意义。

    4.应验证界面显示内容的友好性:

    a)        对统计的数据应按用户习惯进行分类、排序。

    b)        某些重要信息在输入、修改、删除时应有“确认”提示信息;

    c)        界面内容更新后系统应提供刷新功能。

    d)        用户在退出系统后重新登陆时应考虑是否需要自动返回到上次退出系统时的界面;

    5.应验证界面提示信息的指导性:

    a)        在多个业务功能组成的一个业务流程中,如果各个功能之间的执行顺序有一定的制约条件,应通过界面提示用户。

    b)        用户提示信息应具有一定的指导性,在应用程序正在进行关键业务的处理时,应考虑在前台界面提示用户应用程序正在进行的处理,以及相应的处理过程,在处理结束后再提示用户处理完毕。

    c)        在某些数据输入界面,如果要求输入的数据符合某项规则,应在输入界面提供相应的规则描述;当输入数据不符合规则时应提示用户是否继续。

    d)        在对任何配置信息修改后,都应该在用户退出该界面时提示用户保存(如果用户没有主动保存的情况下);

    6.应验证界面显示内容的合理性:

    a)        在对某些查询功能进行测试时,应考虑查询条件的设置的合理性以及查询结果的互补性。如某些后台处理时间不应该作为查询条件。

    b)        界面测试时,应考虑某一界面上按钮先后使用的顺序问题,以免用户对此产生迷惑。例如只能在查询成功后显示执行按钮。

    c)        界面测试时,应验证窗口与窗口之间、字段与字段之间的浏览顺序是否正确;

    7.界面测试时,应考虑用户使用的方便性:

    a)        在某些对数据进行处理的操作界面,应考虑用户可能对数据进行处理的频繁程度和工作量,考虑是否可以进行批量操作。

    8.界面测试时,应考虑界面显示及处理的正确性:

    a)        界面测试时应验证所有窗体中的对象状态是否正常,是否符合相关的业务规则需要。

    b)        应验证各种对象访问方法(Tab 健、鼠标移动和快捷键)是否可正常使用,并且在一个激活界面中快捷键无重复;

    c)        界面测试不光要考虑合理的键盘输入,还应考虑是否可以通过鼠标拷贝粘贴输入。

    d)        对于统计查询功能的查询结果应验证其是否只能通过界面上的查询或刷新按键人工触发,应避免其他形式的触发。

    e)        对界面上的任何对象进行拖拉,然后进行查询、打印,应保证查询打印结果不变;

    9.界面测试时,应考虑数据显示的规范性:

    a)        确保数据精度显示的统一:如单价0元,应显示为0.00元;

    b)        确保时间及日期显示格式的统一;

    c)        确保相同含义属性/字段名的统一;

         d)        对所有可能产生的提示信息界面内容和位置进行验证,确保所有的提示信息界面应居中。
  • [转]系统瓶颈分析举例

    szyszy2000 发布于 2006-12-20 08:43:18

    经验举例1

    交易的响应时间如果很长,远远超过系统性能需求,表示耗费CPU的数据库操作,例如排序,执行aggregate functions(例如summinmaxcount)等较多,可考虑是否有索引以及索引建立的是否合理;尽量使用简单的表联接;水平分割大表格等方法来降低该值。

     

    经验举例2

    分段排除错误。测试工具可以模拟不同的虚拟用户来单独访问Web服务器、应用服务器和数据库服务器,这样,就可以在Web端测出的响应时间减去以上各个分段测出的时间就可以知道瓶颈在哪并着手调优。

     

    经验举例3

    UNIX资源监控(NT操作系统同理)中指标内存页交换速率Paging rate),如果该值偶尔走高,表明当时有线程竞争内存。如果持续很高,则内存可能是瓶颈。也可能是内存访问命中率低。“Swap in rate”“Swap out rate”也有类似的解释。

     

    经验举例4

    UNIX资源监控(NT操作系统同理)中指标CPU占用率CPU utilization),如果该值持续超过95%,表明瓶颈是CPU。可以考虑增加一个处理器或换一个更快的处理器 。合理使用的范围在60%70%

     

    经验举例5

    UNIX资源监控(NT操作系统同理)中指标磁盘交换率Disk rate),如果该参数值一直很高,表明I/O有问题。可考虑更换更快的硬盘系统、重新部署业务逻辑等,另外设置Tempdb in RAM,减低"max async IO""max lazy writer IO"等措施都会降低该值。

     

    经验举例6

    Tuxedo资源监控中指标队列中的字节数Bytes on queue),队列长度应不超过磁盘数的1.5~2倍。要提高性能,可增加磁盘。注意:一个Raid Disk实际有多个磁盘。

     

    经验举例7

    SQLServer资源监控中指标缓存点击率Cache Hit Ratio),该值越高越好。如果持续低于80%,应考虑增加内存。注意该参数值是从SQL Server启动后,就一直累加记数,所以运行经过一段时间后,该值将不能反映系统当前值。

  • 21种故障模型汇总

    c_java 发布于 2006-12-21 18:28:09

    21种故障模型汇总

    l        输入部分

    1.        输入非法的数据

    应用场合:GUI的输入

    如何发现这类问题:

    1)       输入类型

    2)       输入长度

    3)       边界值

    例如:在Word中,插入索引和目录时,栏数要选择数字在0---4之间。

                  软件工程师测试系统中不能将编号输入字符等。

    2.        输入默认值

    应用场合:有默认值的地方

    如何发现这类问题:

    1)       接受软件显示的默认值

    2)       键入空值

    3)       将默认值改为另一个值,这样会使应用程序以不同的值来运行

    4)       将默认值改为另一个值,然后再变为空值

    例如:

    对软件测试工程师管理系统,薪水计算会有默认的月工作天数,默认的编号

    3.        输入特殊字符集应用场合:

    应用场合:需要字符输入的地方

    如何发现这类问题:系统会有一些保留的关键字不允许使用

    例如:NUL、LF等

    4.        输入使缓冲区溢出的数据

    应用场合:需要字符输入的地方

    如何发现这类问题:弄清楚数据域的长度,最大或最小数据输入

    例如:软件测试工程师编号不能够超过9999

    5.        输入产生错误的合法组合

    应用场合:输入值之间存在依赖关系

    如何发现这类问题:输入可能是出现问题的组合值

          例如:软件测试工程师系统中,输入的工龄和出生日期之间的组合

    l        输出部分

    1.        产生同一个输入的各种可能输出

    应用场合:同一输入对应多个输出的情况

    如何发现这类问题:明确一个输入可一产生何种输出。

    例如:网页中打开网页相应的广告也出来多个,退出文件时,会提示先保存,然后退出。

    2.        输出不符合业务规则的无效输出

    应用场合:强制产生不符合业务背景的知识

    如何发现这类问题:

    1)       测试人员应该尽可能多地学习所涉及问题的领域

    2)       有时在列举出无效输出后,也很难知道哪些输入组合能强制这些输出产生。

    例如:月工作天数不能超过32天等。

    3.        输出属性修改后的结果

    应用场合:可修改、可编辑性的功能

    例如:Word文档中艺术字,改变字体,颜色等属性

    4.        屏幕刷新显示

       应用场合:一个对象包含另一个对象,拖动时,刷新

           例如:文件切换,移动对话框

    l        数据结构方面

    1.      数据结构溢出

    应用场合:数组

    测试方法:上溢(输入)、下溢(删除)

    例如:Word制表中的行、列

    2.      数据结构不符合约束

    应用场合:程序内部的数据结构存在结束(程序员在修改没有进行约束)

    例如:软件测试工程师对添加编号范围限定,但在修改时没有对编号约束

    l        其他方面

    1.      操作数与操作符不符

    应用场合:数值计算与图形操作的程序

    例如:计算器中不能将除数为0,VB中错误:c=200+a+b,正确d=200+a+b

    2.      递归调用自身

    应用场合:与其他对象交互的地方

    原因:循环开始或结束缺少检查条件

    例如:添加脚注,却没生成新的

    3.      计算结果溢出

    应用场合:计算结果并存储

    原因:加大了数据(最大或最小)

    例如:两数相加的和超出了整形范围

    4.      数据共享或关联功能计算出错

    应用场合:同时运行多个功能

    原因:共享数据被多个功能使用

    例如:脚注和同一页上两栏文档,对于一个数据,可以进行字体,粗体大小;有关联性质的象月工作天数,月收益与薪水相关,鼠标选取超链接文本、粗体、斜体

     

    l        文件部分

    1.      文件系统超载

    应用场合:磁盘空间:安装测试

                            内存空间:用户操作,打开多个应用程序 (机器极限)

    2.      介质忙或不可用

    应用场合:下载大量文件,运行程序

    3.      介质损坏

    应用场合:磁盘出现灰尘,损坏,做OS软件行业、安全性高的公司(Raid5备份)

    像:OS、驱动程序、公司服务器、

    4.      不合法文件名

    应用场合:保留字8个:\/*?:*<>

    5.      更改文件访问权限

    应用场合:VSS中权限

    6.      文件内容受损(养成备份数据的习惯)

    应用场合:更改文件格式,提示信息

  • 嵌入式软件测试

    Spark.lee 发布于 2006-12-13 20:16:36

    嵌入式软件测试



          嵌入式软件测试/嵌入式测试或叫交叉测试(cross-test)的日的与非嵌入式软件是相同的。但是,在嵌入式系统设计中,软件正越来越多地取代硬件,以降低系统的成本,获得更大的灵活性,这就需要使用更好的测试方法和工具进行嵌入式和实时软件的测试。

          通常嵌入式系统对可靠性的要求比较高。嵌入式系统安全性的失效可能会导致灾难性的后果,即使是非安全性系统,由于大批量生产也会导致严重的经济损失。这就要求对嵌入式系统,包括嵌入式软件进行严格的测试、确认和验证。随着越来越多的领域使用软件和微处理器控制各种嵌入式设备,对门益复杂的嵌入式软件进行快速有效的测试愈加显得重要。

      软件测试的目的是保证软件满足需求规格说明。系统失效是系统没有满足—个或多个正式需求规范中所要求的需求项。嵌入式软件有其特殊的失效判定准则,但是,嵌入式软件测试的日的与非嵌入式软件是相同的。在嵌入式系统设计中,软件正越来越多地取代硬件,以降低系统的成本,获得更大的灵活性,这就需要使用更好的测试方法和工具进行嵌入式和实时软件的测试。


    一、嵌入式软件的测试方法

      一般来说,软件测试有7个基本阶段,即单元或模块测试、集成测试、外部功能测试、回归测试、系统测试、验收测试、安装测试。嵌入式软件测试在4个阶段上进行,即模块测试、集成测试、系统测试、硬件/软件集成测试。前3个阶段适用于任何软件的测试,硬件/软件集成测试阶段是嵌入式软件所特有的,目的是验证嵌入式软件与其所控制的硬件设备能否正确地交互。

          1、白盒测试与黑盒测试

      一般来说,软件测试有两种基本的方式,即白盒测试方法与黑盒测试方法,嵌入式软件测试也不例外。

      白盒测试或基本代码的测试检查程序的内部设计。根据源代码的组织结构查找软件缺陷,一股要求测试人员对软件的结构和作用有详细的了解,白盒测试与代码覆盖率密切相关,可以在白盒测试的同时计算出测试的代码的覆盖率,保证测试的充分性。把 100%的代码都测试到几乎是不可能的,所以要选择最重要的代码进行白盒测试。由于严格的安全性和可靠性的要求,嵌入式软件测试同非嵌入式软件测试相比,通常要求有更高的代码覆盖率。对于嵌入式软件,白盒测试一般不必在目标硬件上进行,更为实际的方式是在开发环境中通过硬件仿真进行,所以选取的测试工具应该支持在宿主环境中的测试。

      黑盒测试在某些情况下也称为功能测试。这类测试方法根据软件的用途和外部特征查找软件缺陷,不需要了解程序的内部结构。黑盒测试最大的优势在于不依赖代码,而是从实际使用的角度进行测试,通过黑盒测试可以发现白盒测试发现不了的问题。因为黑盒测试与需求紧密相关,需求规格说明的质量会直接影响测试的结果,黑盒测试只能限制在需求的范围内进行。在进行嵌入式软件黑盒测试时,要把系统的预期用途作为重要依据,根据需求中对负载、定时、性能的要求,判断软件是否满足这些需求规范。为了保证正确地测试,还须要检验软硬件之间的接口。嵌入式软件黑盒测试的一个重要方面是极限测试。在使用环境中,通常要求嵌入式软件的失效过程要平稳,所以,黑盒测试不仪要检查软件工作过程,也要检查软件换效过程。

          2、目标环境测试和宿主环境测试

      在嵌入式软件测试中,常常要在基于目标的测试和基于宿主的测试之间作出折衷。基于目标的测试消耗较多的经费和时间,而基于宿主的测试代价较小,但毕竟是在模拟环境中进行的。目前的趋势是把更多的测试转移到宿主环境中进行,但是,目标环境的复杂性和独特性不可能完全模拟。

      在两个环境中可以出现不同的软件缺陷,重要的是目标环境和宿主环境的测试内容有所选择。在宿主环境中,可以进行逻辑或界面的测试、以及与硬件无关的测试。在模拟或宿主环境中的测试消耗时间通常相对较少,用调试工具可以更快地完成调试和测试任务。而与定时问题有关的白盒测试、中断测试、硬件接口测试只能在目标环境中进行。在软件测试周期中,基于目标的测试是在较晚的“硬件/软件集成测试”阶段开始的,如果不更早地在模拟环境中进行白盒测试,而是等到“硬件/软件集成测试”阶段进行全部的白盒测试,将耗费更多的财力和人力。

    二、嵌入式软件的测试工具

      用于辅助嵌入式软件测试的工具很多,下面对几类比较有用的有关嵌入式软件的测试工具加以介绍和分析。

          1、内存分析工具

      在嵌入式系统中,内存约束通常是有限的。内存分析工具用来处理在动态内存分配中存在的缺陷。当动态内存被错误地分配后,通常难以再现,可能导致的失效难以追踪,使用内存分析工具可以避免这类缺陷进入功能测试阶段。目前有两类内存分析工具——软件和硬件的。基于软件的内存分析工具可能会对代码的性能造成很大影响,从而严重影响实时操作;基于硬件的内存分析工具价格昂贵,而且只能在工具所限定的运行环境中使用。

          2、性能分析工具

      在嵌入式系统中,程序的性能通常是非常重要的。经常会有这样的要求,在特定时间内处理一个中断,或生成具有特定定时要求的一帧。开发人面临的问题是决定应该对哪一部分代码进行优化来改进性能,常常会花大量的时间去优化那些对性能没有任何影响的代码。性能分析工具会提供有关的数据,说明执行时间是如何消耗的,是什么时候消耗的,以及每个例程所用的时间。根据这些数据,确定哪些例程消耗部分执行时间,从而可以决定如何优化软件,获得更好的时间性能。对于大多数应用来说,大部分执行时间用在相对少量的代码上,费时的代码估计占所有软件总量的5%-20%。性能分析工具不仅能指出哪些例程花费时间,而且与调试工具联合使用可以引导开发人员查看需要优化的特定函数,性能分析工具还可以引导开发人员发现在系统调用中存在的错误以及程序结构上的缺陷。

          3、GUI测试工具

      很多嵌入式应用带有某种形式的图形用户界面进行交互,有些系统性能测试足根掘用户输入响应时间进行的。GUI测试工具可以作为脚本工具有开发环境中运行测试用例,其功能包括对操作的记录和回放、抓取屏幕显示供以后分析和比较、设置和管理测试过程。很多嵌入式设备没有GUI,但常常可以对嵌入式设备进行插装来运行GUI测试脚本,虽然这种方式可能要求对被测代码进行更改,但是节省了功能测试和回归测试的时间。

          4、覆盖分析工具

      在进行白盒测试时,可以使用代码覆盖分析工具追踪哪些代码被执行过。分析过程可以通过插装来完成,插装可以是在测试环境中嵌入硬件,也可以是在可执行代码中加入软件,也可以是二者相结合。测试人员对结果数据加以总结,确定哪些代码被执行过,哪些代码被巡漏了。覆盖分析工具一般会提供有关功能覆盖、分支覆盖、条件覆盖的信息。对于嵌入式软件来说,代码覆盖分析工具可能侵入代码的执行,影响实时代码的运行过程。基于硬件的代码覆盖分析工具的侵入程度要小一些,但是价格一般比较昂贵,而且限制被测代码的数量。
     
    三、嵌入式软件测试策略

           在嵌入式领域目标系统的应用系统日趋复杂,而由于竞争要求产品快速上市,开发技术日新月异,同时硬件发展的日益稳定,而软件故障却日益突出,软件的重要性逐渐引起人们的重视,越来越多的人认识到嵌入式系统的测试势在必行。提到嵌入式软件测试,首先要简单介绍一些软件工程的一些观点,现在,被普遍接受的软件的定义是:软件(software)是计算机系统中与硬件(hardware)相互依存的另一部分,它包括程序(program)、相关数据(data)及其说明文档(document)。其中程序是按照事先设计的功能和性能要求执行的指令序列;数据是是程序能正常操纵信息的数据结构;文档是与程序开发维护和使用有关的各种图文资料。

          对于一般商用软件的测试,嵌入式软件测试有其自身的特点和测试困难。

      由于嵌入式系统的自身特点,如实时性(Real-timing),内存不丰富,I/O通道少,开发工具昂贵,并且与硬件紧密相关CPU种类繁多,等等。嵌入式软件的开发和测试也就与一般商用软件的开发和测试策略有了很大的不同,可以说嵌入式软件是最难测试的一种软件。

      嵌入式软件测试使用有效的测试策略是唯一的出路,它可以使开发的效率最大化,避免目标系统的瓶颈,使用在线仿真器节省昂贵的目标资源。自从出现高级语言,开发环境与最终运行环境通常都是存在差异的,嵌入式系统更是如此。开发环境被认为是主机平台,软件运行环境为目标平台。相应的测试为host-target测试或cross-testing。

      讨论嵌入式软件测试首先就会遇到一个问题:为什么不把所有测试都放在目标上进行呢?因为若所有测试都放在目标平台上有很多不利的因素:

          1)测试软件,可能会造成与开发者争夺时间的瓶颈,避免它只有提供更多的目标环境。
          2)目标环境可能还不可行。
          3)比起主机平台环境,目标环境通常是不精密的和不方便的。
          4)提供给开发者的目标环境和联合开发环境通常是很昂贵的。
          5)开发和测试工作可能会妨碍目标环境已存在持续的应用

          从经济上和开发效率上考虑,软件开发周期中尽可能大的比例在主机系统环境中进行,其中包括测试。

          确定host-target测试环境后,开发测试人员又会遇到以下的问题:

          1)多少开发人员会卷入测试工作(单元测试,软件集成,系统测试)?
          2)多少软件应该测试,测试会花费多长时间?
          3)在主机环境和目标环境有哪些软件工具,价格怎样,适合怎样?
          4)多少目标环境可以提供给开发者,什么时候?
          5)主机和目标机之间的连接怎样?
          6)被测软件下载到目标机有多快?
          7)使用主机与目标环境之间有什么限制(如软件安全标准)?

          任何人或组织进行嵌入式软件的测试都应深入考虑以上问题,结合自身实际情况,选定合理测试策略和方案。

          对于嵌入式软件测试或叫交叉测试(cross-test),在测试的各个阶段有着通用的策略:

          1.单元测试

          所有单元级测试都可以在主机环境上进行,除非少数情况,特别具体指定了单元测试直接在目标环境进行。最大化在主机环境进行软件测试的比例,通过尽可能小的目标单元访问所有目标指定的界面。

          在主机平台上运行测试速度比在目标平台上快的多,当在主机平台完成测试,可以在目标环境上重复作一简单的确认测试,确认测试结果在主机和目标机上没有被他们的不同影响。在目标环境上进行确认测试将确定一些未知的,未预料到的,未说明的主机与目标机的不同。例如,目标编译器可能有bug,但在主机编译器上没有。

          2.集成测试

          软件集成也可在主机环境上完成,在主机平台上模拟目标环境运行,当然在目标环境上重复测试也是必须的,在此级别上的确认测试将确定一些环境上的问题,比如内存定位和分配上的一些错误。
    在主机环境上的集成测试的使用,依赖于目标系统的具体功能有多少。有些嵌入式系统与目标环境耦合的非常紧密,若在主机环境做集成是不切实际的。一个大型软件的开发可以分几个级别的集成。低级别的软件集成在主机平台上完成有很大优势,越往后的集成越依赖于目标环境。

          3.系统测试和确认测试

          所有的系统测试和确认测试必须在目标环境下执行。当然在主机上开发和执行系统测试,然后移植到目标环境重复执行是很方便的。对目标系统的依赖性会妨碍将主机环境上的系统测试移植到目标系统上,况且只有少数开发者会卷入系统测试,所以有时放弃在主机环境上执行系统测试可能更方便。

          确认测试最终的实施舞台必须在目标环境中,系统的确认必须在真实系统之下测试,而不能在主机环境下模拟。这关系到嵌入式软件的最终使用。

          包括恢复测试、安全测试、强度测试、性能测试,已超出了软件测试的范畴,本文暂不讨论。

          使用有效的cross-test测试策略可极大的提高嵌入式软件开发测试的水平和效率,当然正确的测试工具使用也是必不可少的:

         总结一下,应用以上测试工具进行.Cross-test时的策略:

          A)使用测试工具的插装功能(主机环境)执行静态测试分析,并且为动态覆盖测试准备好一插装好的软件代码。
          B)使用源码在主机环境执行功能测试,修正软件的错误和测试脚本中的错误。
          C)使用插装后的软件代码执行覆盖率测试,添加测试用例或修正软件的错误,保证达到所要求的覆盖率目标。
          D)在目标环境下重复(B),确认软件在目标环境中执行测试的正确性。
          E)若测试需要达到极端的完整性,最好在目标系统上重复(C),确定软件的覆盖率没有改变。

          通常在主机环境执行多数的测试,只是在最终确定测试结果和最后的系统测试才移植到目标环境,这样可以避免发生访问目标系统资源上的瓶颈,也可以减少在昂贵资源如在线仿真器上的费用。另外,若目标系统的硬件由于某种原因而不能使用时,最后的确认测试可以推迟直到目标硬件可用,这为嵌入式软件的开发测试提供了弹性。设计软件的可移植性是成功进行cross-test的先决条件,它通常可以提高软件的质量,并且度软件的维护大有益处。以上所提到的测试工具,都可以通过各自的方式提供测试在主机与目标之间的移植,从而使嵌入式软件的测试得以方便的执行。

          使用有效的cross-test测试策略可极大的提高嵌入式软件开发测试的水平和效率,提高嵌入式软件的质量。

    附录:
    1). HOST-TARGET的连接方法简介:


    图1-- 直接连接


    图2 -- 通过仿真器连接


    图3 -- 使用介质进行间接连接


    图4 -- 使用PROM等传递被测软件


    图5 -- 测试的交互界面


    图6 -- 无交互界面的连接

    四、结论

      嵌入式系统在人类生活中发挥着重要的作用,包括飞行控制器这样的控制系统,以及洗衣机这样的家用电器。日前,嵌入式系统中软件的比重越来越大,也越来越复杂,保证嵌入式软件的可靠性正面临严峻的挑战。

      大多数软件测试方法都可以直接或间接地用于嵌入式软件的测试,但是由于操作系统的实时和嵌入式特性,嵌入式软件测试也面临一些特殊的问题。虽然日前已经有一些针对嵌入式软件的测试和调试工具,但是在有些方面仍存在不足,包括许多任务操作系统的并发、非侵入式的测试和凋试、嵌入式系统的软件抽象等。对于嵌入式软件测试技术的研究人选测试工具有待开发,仍须要做很多进一步的工作。


     
  • 程序代码模块的内聚与耦合和测试的关系

    xiaonan 发布于 2006-12-11 15:49:39

         首先我们引出内聚与耦合的两个概念.内聚(Cohesion)是一个模块内部各成分之间相关联程度的度量。耦合(Coupling)是模块之间依赖程度的度量。内聚和耦合是密切相关的,与其它模块存在强耦合的模块通常意味着弱内聚,而强内聚的模块通常意味着与其它模块之间存在弱耦合。模块设计追求高内聚,低耦合。


        内聚按强度从低到高有以下几种类型:


    (1)偶然内聚。如果一个模块的各成分之间毫无关系,则称为偶然内聚。
    (2)逻辑内聚。几个逻辑上相关的功能被放在同一模块中,则称为逻辑内聚。如一个模块读取各种不同类型外设的输入。尽管逻辑内聚比偶然内聚合理一些,但逻辑内聚的模块各成分在功能上并无关系,即使局部功能的修改有时也会影响全局,因此这类模块的修改也比较困难。
    (3)时间内聚。如果一个模块完成的功能必须在同一时间内执行(如系统初始化),但这些功能只是因为时间因素关联在一起,则称为时间内聚。
    (4)过程内聚。如果一个模块内部的处理成分是相关的,而且这些处理必须以特定的次序执行,则称为过程内聚。
    (5)通信内聚。如果一个模块的所有成分都操作同一数据集或生成同一数据集,则称为通信内聚。
    (6)顺序内聚。如果一个模块的各个成分和同一个功能密切相关,而且一个成分的输出作为另一个成分的输入,则称为顺序内聚。
    (7)功能内聚。模块的所有成分对于完成单一的功能都是必须的,则称为功能内聚

        耦合的强度依赖于以下几个因素:


    (1)一个模块对另一个模块的调用;
    (2)一个模块向另一个模块传递的数据量;
    (3)一个模块施加到另一个模块的控制的多少;
    (4)模块之间接口的复杂程度。


        耦合按从强到弱的顺序可分为以下几种类型:


    (1)内容耦合。当一个模块直接修改或操作另一个模块的数据,或者直接转入另一个模块时,就发生了内容耦合。此时,被修改的模块完全依赖于修改它的模块。
    (2)公共耦合。两个以上的模块共同引用一个全局数据项就称为公共耦合。
    (3)控制耦合。一个模块在界面上传递一个信号(如开关值、标志量等)控制另一个模块,接收信号的模块的动作根据信号值进行调整,称为控制耦合。
    (4)标记耦合。模块间通过参数传递复杂的内部数据结构,称为标记耦合。此数据结构的变化将使相关的模块发生变化。
    (5)数据耦合。模块间通过参数传递基本类型的数据,称为数据耦合。
    (6)非直接耦合。模块间没有信息传

         然后我们通过测试的角度来看一下这两个度量在测试中的意义:

    高内聚的模块,互相之间依赖性比较少,某个模块一但出现问题的时候,能把错误控制在一个模块内.不会因为一个模块出了问题,而影响了其他功能模块.保证了其他功能的完整性,不会影响用户的其他操作,能让给用户带来的麻烦降低到最低限度.程序员也能快速的定位出问题所在的模块,进行问题修改.模块之间没有太多的牵连,更便于程序员的问题修改.在问题修改后,也不会给其他功能模块带来太多的影响.测试人员在做单元或集成测试的时候,能够更容易把里面的程序模块之间的逻辑理清楚,能设计出高覆盖率的用例把程序代码后模块接口都能完全覆盖测试到.也能让测试人员在做回归测试时,降低一定的风险.

    耦合性高的模块,相互之间依赖性比较多,模块与模块之间的关系也比较复杂.就容易出现,一个程序模块出现了问题,导致其他功能模块都不能正常运行.而且由于关系的复杂性,给程序员定位及修改问题带来一定的不便.也影响了用户的所有操作,给用户带来更大损失.而且由于模块之间的复杂关系,即便程序员修改了一个问题后,也不能保证不会带来更多的缺陷.给测试人员做单元和集成测试带来了太多的不便.需要花大量的时间去理清内部的逻辑.在这样的情况下,可能也会给测试工作带来遗漏的地方,留下隐患.测试人员也要花更多的时间进行回归测试,来保证系统程序符合规定要求了.无形中给项目带来了更多的风险.

     

  • 排错的基本方法

    luncoo 发布于 2006-12-11 14:23:59

    排错(即调试)与成功的测试形影相随。测试成功的标志是发现了错误。根据错误迹象确定错误的原因和准确位置,并加以改正的主要依靠排错技术。

    1. 排错过程

      如下图所示,排错过程开始于一个测试用例的执行,若测试结果与期望结果有出入,即出现了错误征兆,排错过程首先要找出错误原因,然后对错误进行修正。因此排错过程有两种可能,一是找到了错误原因并纠正了错误,另一种可能是错误原因不明,排错人员只得做某种推测,然后再设计测试用例证实这种推测,若一次推测失败,再做第二次推测,直到发现并纠正了错误。

       排错是一个相当艰苦的过程,究其原因除了开发人员心理方面的障碍外,还因为隐藏在程序中的错误具有下列特殊的性质:
      (1) 错误的外部征兆远离引起错误的内部原因,对于高度耦合的程序结构此类现象更为严重;
      (2) 纠正一个错误造成了另一错误现象(暂时)的消失;
      (3) 某些错误征兆只是假象;
      (4) 因操作人员一时疏忽造成的某些错误征兆不易追踪;
      (5) 错误是由于风时而不是程序引起的;
      (6) 输入条件难以精确地再构造(例如,某些实时应用的输入次序不确定);
      (7) 错误征兆时有时无,此现象对嵌入式系统尤其普遍;
      (8) 错误是由于把任务分布在若干台不同处理机上运行而造成的。

      在软件排错过程中,可能遇到大大小小、形形色色的问题,随着问题的增多,排错人员的压力也随之增大,过分地紧张致使开发人员在排除一个问题的同时又引入更多的新问题。

      尽管排错不是一门好学的技术(有时人们更愿意称之为艺术),但还是有若干行之有效的方法和策略,下面介绍几种排错方法。

    2. 排错方法

      无论采用哪种排错方法,目标只有一个,即发现并排除引起错误的原因,这要求排错人员能把直观想象与系统评估很好的结合起来。
      常用的排错策略分为三类:
      ① 原始类(brute force)
      ② 回溯类(backtracking)
      ③ 排除类(cause eliminations)

      原始类排错方法是最常用也是最低效的方法,只有在万般无奈的情况下才使用它,主要思想是"通过计算机找错"。例如输出存储器、寄存器的内容,在程序安排若干输出语句等,凭借大量的现场信息,从中找到出错的线索,虽然最终也能成功,但难免要耗费大量的时间和精力。

      回溯法能成功地用于程序的排错。方法是从出现错误征兆处开始,人工地沿控制流程往回追踪,直至发现出错的根源,不幸的是程序变大后,可能的回溯路线显著增加,以致人工进行完全回溯到望而不可及。

      排除法基于归纳和演绎原理,采用"分治"的概念,首先惧与错误出现有关有所有数据,假想一个错误原因,用这些数据证明或反驳它;或者一次列出所有可能的原因,通过测试一一排除。只要某次测试结果说明某种假设已呈现倪端,则立即精化数据,乘胜追击。

      上述每一类方法均可辅以排错工具。目前,调试编译器、动态调试器("追踪器")、测试用例自动生成器、存储器映象及交叉访问示图等到一系列工具已广为使用。然而,无论什么工具也替代不了一个开发人员在对完整的设计文档和清晰的源代码进行认真审阅和推敲之后所起的作用。此外,不应荒废排错过程中最有价值的一个资源,那就是开发小组中其他成员的评价和忠告,正所谓"当事者迷,旁观者清"。

      前面多次提到,修改一处老问题可能引入几处新问题,有时程序越改越乱,但若能做到每次纠错前都扪心自问三个问题,情况将大为改观:
      ① 导致这个错误的原因在程序其他部分还可能存在吗?
      ② 本次修改可能对程序中相关的逻辑和数据造成什么影响?引起什么问题?
      ③ 上次遇到的类似问题是如何排除的?

  • [论坛] Quality Center 9.0 ,不同以往……

    songfun 发布于 2006-11-27 18:10:20

    QC9.0出来也有大半年了,相关的文章却不多。呵呵,没有了License,Mercury阵营的fans少了不少。
    言归正传,先贴几张图,看图作文


    QC9.0.JPG: 这是9.0系列的登录首页,和8.2有什么不同,看出来了吗?新版的9.0已经统一成Mercury官方网站的style(包括QTP/LR),不知道下一个版本是否会打上HP的logo?

    QC9.0A.JPG这是QC 9.0最大的外在变化之一,我认为这是一个不小的进步(虽然只是一个细节)。很多朋友看到这个界面就晕了——甚至不知道如何登录系统了。呵呵,我不知道QC9.0的设计师是不是读过《Don't make me think(别让我思考)》才做的改变。为什么这么说呢?以前的QC/TD的控制是什么风格?是:提供所有项目供用户选择,不管这个用户能否进入。而现在的风格则是:提供可进入的项目供用户选择。理念是什么呢?两点:第一,吸收了EPM思想,只允许用户可以选择自己所能登录的Project,至于其他项目,这个用户根本就不应该知道!第二,扭转用户思维模式:先log in自己的身份,再提供属于自己的任务。这两点可能大家不是很明白,我举个例子:假设现在你的公司是跨国企业,通过外网架设QC系统,那么你公司所承接的项目有可能多多少少大大小小都会有“成千上万”个项目,那么你作为一名工程师,可能所涉及的项目任务仅仅只是其中之一,那么你想想:首先你要在一大堆的Domain里选择属于自己的域,然后再在一大堆的Project里选择属于自己的项目……哇,下拉菜单那么多东西,你找晕了没?你是不是还没登录进系统,就已经开始烦了?你是不是在想:这么多东西又不都是要我做的。所以……现在改用了传统的email、BBS式的登录形式:我,只要先输入自己的账号、密码,登录进“my space(我的空间)”,就行了。属于我的项目任务无非就是这两三个,而且在不同的项目间切换都不用重登录(relogin)了——简洁、方便、赏心悦目、爽。
    说了这么多,大家现在应该明白了吧——很多网友一直在问:“为什么输入系统管理员的用户名密码,下面的Domain和Project还是无法选择”,一直在问“用户名密码必须是什么?”。答案很简单,刚安装好的QC 9.0只有一个自带的DEFAULT域的QualityCenter_Demo项目,想进这个项目“先睹为快”,首先必须用这个项目所属的成员才能登录。现在你用alex_qc登录(密码为空),然后点Authenticate,是不是发现下面的domain和project有“东西”了?呵呵。


    小结:改变都是有原因的,决不是哪天大师头脑发热兴之所致而作出的。
    推荐:大家有空去china-pub 、dearbook 、当当之类的地方可以找找Don't make me think这本书,一个做Web测试的人必读的经典之作。

    QC9.0pdf.JPG :这就是QC 9.0的帮助文档之一,所有的QC 9.0的资料大家请查阅我另一个帖子http://bbs.51testing.com/thread-48974-1-1.html

    QC9.0Welcome.JPG :进入QualityCenter_Demo后的页面后,Mercury对您的光临表示欢迎(“Welcome to Mercury Quality Center”)

    QC9.0Doc.JPG :这是QC文档库(以前叫Books Online,现在模仿微软改名了,呵),有点类似MSDN一样的,其实这里提供你的都是文件资料,也就是上面提到的QC 9.0的所有资料,包括pdf和chm文件。

    QC9.0Help.JPG :这个是QC的联机帮助(Online Help),也就是用户指南。

    QC9.0ProjectAdmin.JPG 这也是QC 9.0最大的外在变化之一,呵呵。之前的QC/TD提供给我们的实际上是三个不同的登录入口(entrance):SiteAdministrator(站点管理,用以管理QC的配置)、Project Customization(项目定制,用以对各自项目的个性化管理设置)、Quality Center(项目成员真正的工作区,通俗的说,就是我们测试工程师写测试用例和提交BUG的地方)。现在的QC 9.0只提供两个入口,即:SiteAdministrator和Quality Center——它把“项目定制”整合到项目工作区里去了。
    道理很简单:进行项目定制的人也应该是项目成员(项目成员才了解自己的项目需要定制些什么),所以对于这个人(Project Admin)来说,无论工作区还是项目定制区都只要有统一的portal就行了,不必在两个不同的地方登录来登录去(现在只需要点右上方的“RETURN”就可以返回了)。比如QualityCenter_Demo库中的alex_qc这个用户就是这么一个角色(tdadmin)。而这里admin用户默认的只有viewer的角色了,原先默认的guest用户也取消了。


    QC9.0Site.JPG :这就是前面说的站点管理员的工作区,图中是添加新用户。顺便提醒一下,在QC 中SiteAdministrator的登录地址 http://IP:PORT/sabin/SiteAdmin.htm 不是http://IP:PORT/qcbin/SiteAdmin.htm,可别到时候连错了还以为自己站点管理页面登录不了了。

    QC9.0Site1.JPG :字符集设置地方。其中有段描述:The character set used by Quality Center to send e-mails to users. By default, the value is set to UTF-8.翻译过来就是:Quality Center 用来将电子邮件发送到用户的字符集。默认情况下,该值设置为UTF-8

    QC9.0Site2.JPG :这是QC 9.0多出的一个功能。用来监视Licenses的,对我们这些玩家来说真是一个鸡肋(没什么用处的东西)。图中的Defect License和Full Licenses大家可以对应以前Project Custmization里的Customize Module Access模块,指的是“QC许可证”和“缺陷许可证”,只拥有后者的其实就是少花点钱买了QC的缺陷管理部分,把它当作缺陷管理系统来用。

    QC9.0AddDefect.JPG :为什么要把这张图截出来呢?因为我要严厉批评一下Mercury的人!!为什么?因为mercury内部的文档编辑工作人员对工作不负责任——SetFieldApp这个TD自带的函数在升级为QC后,已经更名为SetFieldProperties了!!QC 8.2已经错了一次了,在QC 9.0居然一错再错(至少帮助文档里的说明和workflow里的内容已经对应不上了)!他们的文档编辑人员为何视而不见?在我看来,一个做测试工具的厂商犯这样的错误是非常不应该的,我不禁开始怀疑他们内部的变更管理是如何做的??细节决定成败,希望我这篇文章能引起Mercury公司的重视(其他错误还有,我就不一一列举了)。

    QC9.0BugFunc.JPG :这张图的内容大家仔细看看,看到了么?这是数据库表结构的描述!真是宝物啊!呵呵,不错,Database Reference和Custom Test Type Guide 是QC 9.0 里多出的两个文档。至于具体内容,在我那篇资料贴里也有,大家自己去down吧。

    QC9.0Chat.JPG:其实在Site Connections里多出了一个标签页,可以给当前连接着的用户发消息(send message),当向用户发消息后,这个用户过一会儿就会收到一个像msn弹出窗口一样的消息框来,呵呵,有意思吧?

    QC9.0Checker.JPG :大家看出它和QC 8.2 的checker的区别了吗?

    QC9.0Migration.JPG :迁移工具。

    其实QC 9.0 还有一些新的特性,比如说需求(现在被设计成很像Defect模块了,呵呵),还有缺陷的Linked Entities,……时间有限,本人已经犯困了 ,呵呵,下一期介绍吧,如果读者还有兴趣的话。

    注:
    文中的图片对应的QC是Quality Center 9.0 Starter Edition,也就是 所谓的Quality Center 9.0 的精简版(abridged entry-level version)——其实主要就是精简了BTO模块而已,所以Quality Center 9.0 Starter其实就是真正意义上的TD 9.0 。

    [ 本帖最后由 songfun 于 2006-11-18 23:10 编辑 ]

    QC9.0.JPG
  • 在TD的测试用例中显示测试用例编号的方法

    g_win 发布于 2006-11-29 22:42:34

     

    就像Defect里每个bug都有一个ID一样,其实TD为每个测试用例(Test Case)也赋予了一个编号,默认是不显示的,如果希望在界面上让它显示出来,可以这么做:

    步骤1

    创建一个Object,类型为Number,命名之(随意,如TEST_ID)

    步骤2

    进入Customize->Set Up Workflow->scrīpt Editor,找到Test Plan module scrīpt->TestPlan_Test_moveto

    步骤3

    在TestPlan_Test_moveto这个过程内插入如下代码:

    if TestPlan_Fields.Field("Test_ID").Value <> Test_Fields.Field

    ("TS_TEST_ID").Value then

    Test_Fields.Field("Test_ID").Value = Test_Fields.Field("TS_TEST_ID").Value

    end if

    或者只插入下面这句:

    Test_Fields.Field("TS_USER_01").Value = Test_Fields.Field("TS_TEST_ID").Value

    步骤4

    保存,退出,5min之后打开测试用例查看其编号

  • 性能测试脚本自动建立测试datapool

    Spark.lee 发布于 2006-12-06 20:44:15

    性能测试脚本自动建立测试datapool

    二.性能测试脚本自动建立测试datapool

    robottools->session record options, 设置Generator页面generaluse datapool选项,才能在产生datapool.

    一般录制性能测试脚本,遵循如下步骤:

    第一.         设置录制Session选项

    第二.         启动录制对话

    第三.         启动客户端程序

    第四.         产生脚本

    第五.         关闭客户端程序

    第六.         停止录制

     

    设置选项:

    1.录制方法(method: api, network,proxy,custom四种选项设置

       通过分析要测试的软件架构来选择录制的方法,下边的表格提供参考:

     

     

    Situation

    Api

    NetWork

    Proxy

    The client application access secure data from a Web server

    Required

     

     

    The Client Application access data from a web server

    Recommended

    Fist Alternate

    Second alternate

    The client application accesses objects on a DCOM Server

    Required

     

     

    The client application access an oracle8 database or oracle arrays.

    Required

     

     

    The client application access an Oracle database.(For network and proxy recording, supply the name of the oracle database,)

    Recommended

    First alternate

    Second alternate

    The client application is not installed on the local computer

     

    Recommended

    Alternate

    The client application is not running on Windows NT4, Windows XP, or Windows 2000

     

    Recommended

    Alternate

    You want to record traffic from multiple client applications that reside on different commputers

     

    Recommended

    Alternate

    You want to record traffic between multiple,specific clent and server computers.

     

     

    Recommended

    Neither the client nor the serer computer is on same network segment  as  the  local computer

     

     

    Required

    An Ethernet controls network traffic, and neither the client nor the server application is installed on the computer

     

     

    required

    The client application accesses a TUXEDO Server

    Recommended

    Alternate

     

    On-the-wire recording support is lacking

    Recommended

     

    Alternate

    Api recording is not functioning properly

     

    Recommended

    Alternate

    FDDI,ATM,or other hight-speed networks are used

     

     

    Recommended

    备注:上图参考rational robot users guide

          NetWork录制方式必须安装Rational网络驱动才能生效

          安装方法:

    1     开始-〉设置-〉网络和拨号连接

    2     点本地连接

    3     点安装,选择协议,点添加

    4     点选者从磁盘安装

    5     打开安装目录C:\Program Files\Rational\Rational Test\driverw2k(安装rational的目录),选者inf文件,确定

    6     选择ethernet 或者 token ring都可以

     

    2.Generator Filtering

      Filtering

    1.设置autoFiltering使robot生成脚本时自动选择可用协议,下边协议选者列表可用

     特别说明DCOM是一种独占的协议,不能和其他协议共存

           如果后台是SQLSERVER,选择SQLSERVER,如果后台是ORACLE,选择ORACLE协议。根据软件实现方式,选择不同的协议。

               (2). 设置Manual Protocol Filtering使robot生成脚本时手工选择协议

    3.Generator Per Protocol

      只能对在generator Filtering选择的部分协议(http,iiop,Oracle,Tuxedo,dcom)起作用。
  • ftp测试脚本(转)

    charmer 发布于 2006-12-11 12:07:49

    ftp测试脚本

    前几天为了测试ftp的性能,好不容易调好了一个脚本,可是loadrunner出了点问题,把我的脚本弄得打不开了,真是郁闷,因为这个脚本现在看起来虽然没什么,可是在当时不了解ftp的情况下去做,可真是费了九牛二虎之力,因此这次一调好了就吸取教训,存起来先:)
     
    action.c:
     
    #include "lrs.h"
    extern char *strtok(char *string, const char *delimiters );
    Action()
    {
     char *BufVal;
     char *token;
     char hostport[255];
     char portinfo[8];
     char PortNum[255];
        char cVal[8];
     char cHighVal[8];
     
     char cLowVal[8];
        int iVal,iLength,i,j;
        lrs_create_socket("socket0", "TCP", "LocalHost=0", "RemoteHost=192.168.0.100:21",  LrsLastArg);
     lrs_send("socket0","vpnbuf0",LrsLastArg);
        lrs_receive("socket0", "buf0", LrsLastArg);
        lrs_send("socket0", "buf1", LrsLastArg);
        lrs_receive("socket0", "buf2", LrsLastArg);
        lrs_send("socket0", "buf3", LrsLastArg);
        lrs_receive("socket0", "buf4", LrsLastArg);
    //send PASV
        lrs_send("socket0", "pasvbuf0", LrsLastArg);
     lrs_receive("socket0", "pasvbuf1", LrsLastArg);
    //  Get the Local Port of the Local Host on socket0*/
     lrs_get_last_received_buffer("socket0",&BufVal,&iLength);
     lr_output_message("The buffer is:%s", BufVal);
     strcpy(PortNum,BufVal);
    // lr_output_message("PortNum Conetnt %s", PortNum);
     
     for(i=0,j=40;i<3;i++,j++)
     {
      cHighVal[i]=PortNum[j];
     }
     lr_output_message("cHignVal conent %s",cHighVal);
     j=44;
     token=strtok(lr_eval_string(&PortNum[j]),")");
     if(token != NULL)
      strcpy(cLowVal,token);
     lr_output_message("cLowVal is:%s",cLowVal);
     iVal=atoi(cHighVal)*256+atoi(cLowVal);
     lr_output_message("cHighVal*256 id %d",atoi(cHighVal)*256);
     lr_output_message("iVal %d",iVal);
    // lr_output_message("cVal %s",itoa(iVal,cVal,10));
     itoa(iVal,cVal,10);
     lr_output_message("cVal %s",cVal);
    //set the propority's value
     lr_save_string(cVal,"portinfo");
     //strcpy(&portinfo,&cVal);
     //lr_output_message("portinfo is :%s",portinfo);
     lrs_free_buffer(BufVal);
    //  creat data socket
     lrs_create_socket("socket1", "TCP", "LocalHost=0", "RemoteHost=192.168.0.100:21",  LrsLastArg);
        lrs_send("socket1", "vpnbuf1", LrsLastArg);
        lrs_receive("socket1", "vpnbuf2", LrsLastArg);
        lrs_send("socket0", "buf7", LrsLastArg);
        lrs_receive("socket0", "buf8", LrsLastArg);
    //    lrs_accept_connection("socket1", "socket2");
    //    lrs_close_socket("socket1");
        lrs_send("socket1", "buf9", LrsLastArg);
        lrs_close_socket("socket1");
        lrs_receive("socket0", "buf10", LrsLastArg);
        lrs_send("socket0", "buf11", LrsLastArg);
        lrs_receive("socket0", "buf12", LrsLastArg);
        lrs_close_socket("socket0");
        return 0;
    }
     
    data.ws:
     

    send  vpnbuf0 81
     "CONNECT 35622ebb TAURUS/1.0\r\n"
        "COOKIE: TaurusSessionID=w1mO0LX84O7EHLnQwBmw6g==\r\n\r\n"
    recv  buf0 43
     "CONNECT OK TAURUS/1.0\r\n"
     "220 (vsFTPd 1.1.3)\r\n"
    send  buf1 11
     "USER libh\r\n"
    recv  buf2 34
     "331 Please specify the password.\r\n"
    send  buf3 11
     "PASS 1111\r\n"
    recv  buf4 33
     "230 Login successful. Have fun.\r\n"
    send  pasvbuf0 6
     "PASV\r\n"
    recv  pasvbuf1 49
     "227 Entering Passive Mode (192,168,0,16,113,74)\r\n"
    send  vpnbuf1 87
     "CONNECT 35622ebb:<portinfo> TAURUS/1.0\r\n"
     "COOKIE: TaurusSessionID=w1mO0LX84O7EHLnQwBmw6g==\r\n\r\n"
    recv  vpnbuf2 23
     "CONNECT OK TAURUS/1.0\r\n"
    send  buf7 15
     "STOR 100k.doc\r\n"
    recv  buf8 22
     "150 Ok to send data.\r\n"
    send  buf9 106496
     "…………"
    recv  buf10 22
     "226 File receive OK.\r\n"
    send  buf11 6
     "QUIT\r\n"
    recv  buf12 14
     "221 Goodbye.\r\n"

    -1
  • 利用LR测试程序基类的性能

    newideaway 发布于 2006-12-08 14:11:48

    在VUSERCLASS中写方法,在METHOD中写参数调用,等于参数化了。

    VuserClass代码如下 :

    using System;
    using System.Collections;
    using System.Diagnostics;
    using System.EnterpriseServices;
    using System.Runtime.InteropServices;
    using System.Runtime.Remoting;
    using System.Runtime.Remoting.Channels;
    using System.Runtime.Remoting.Channels.Tcp;
    using System.Data;
    using System.Web;
    using System.Reflection;

    using Common;
    using Proxy;
    using DAT;
    using ShangXin;
    using ShangXin.Data;
    using ShangXinInterface;
    namespace LRQueryCustomer
    {
     ///


     /// Summary descrīption for VuserClass.
     ///

     [ClassInterface(ClassInterfaceType.AutoDual)]
     public class VuserClass
     { 
      public static string ifCanCreatePuDongOrderWithBiz = "";
      Proxy.LoginProxy loginProxy;
      Method methodLR = new Method();
      LoadRunner.LrApi lr;

      public VuserClass()
      {
       // LoadRunner Standard API Interface ::             DO NOT REMOVE!!!
       lr = new LoadRunner.LrApi();
      }

      // ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
      public int Initialize()
      {
      try
       {
       RemotingConfiguration.Configure(@"D:\Escalade liunx\CODE_20051020100023\Code\OAERP\ShangXin.OAERP.App\bin\Debug\App.exe.config");

       lr.start_transaction("init");
       lr.start_transaction("GetLoginByLoginNameAndPassword");
       loginProxy = new LoginProxy();
       LoginData loginData = loginProxy.GetLoginByLoginNameAndPassword("administrator","");
       lr.end_transaction("GetLoginByLoginNameAndPassword",lr.PASS);

       Proxy.EmployeeProxy employeeProxy = new EmployeeProxy();
       CertificationInfo CI = new CertificationInfo();
       Common.CertificationInfo ci= PersonalCertificationInfo.GetCertificationInfo();

       CI.LoginName = SXConvert.ToString(loginData.Rows[0][LoginData.LOGINNAME]);
       CI.Password = SXConvert.ToString(loginData.Rows[0][LoginData.PASSWORD]);
       CI.EmployeeId = SXConvert.ToInt32(loginData.Rows[0][LoginData.EMPLOYEEID]);
       CI.EmployeeName = employeeProxy.GetEmployeeDataByPKID(SXConvert.ToInt32(loginData.Rows[0][LoginData.EMPLOYEEID])).Rows[0][EmployeeData.NAME].ToString();
       CI.RecordCount = SXConvert.ToInt32(loginData.Rows[0][LoginData.RECORDCOUNT]);

       lr.start_transaction("GetAllAuthsByLoginId");
       int loginID = SXConvert.ToInt32(loginData.Rows[0][LoginData.PKID]);
       AuthData ad = loginProxy.GetAllAuthsByLoginId(loginID);
       methodLR.SetAuth(ad,CI);
       lr.end_transaction("GetAllAuthsByLoginId",lr.PASS);

       lr.start_transaction("GetGroupsByLoginID");
       GroupData groupData = loginProxy.GetGroupsByLoginID(loginID);
       methodLR.GetGroupAuth(groupData,CI);
       lr.end_transaction("GetGroupsByLoginID",lr.PASS);

       lr.start_transaction("AddToCurrentEmployee");
       if (AuthenticationManager.CheckAuthentication(CI,EnumAuthority.AuthLoginSystem))
       {
        methodLR.AddToCurrentEmployee(ci,CI);
        lr.end_transaction("AddToCurrentEmployee",lr.PASS);
        DataRow BizDR = new SystemParameterValueProxy().GetSystemParameterValueByParentCode(SystemParametersCode.CANCREATEPUDONGORDERWITHBIZ,Bools.TRUE).Rows[0];
        ifCanCreatePuDongOrderWithBiz = BizDR[SystemParameterValueData.OTHER1].ToString();
       }
       else
       {
        lr.end_transaction("AddToCurrentEmployee",lr.FAIL);
       }
      
       lr.end_transaction("init",lr.PASS);
      }
      catch(Exception ex)
      {
       string error = ex.Message;
      }
       return lr.PASS;

      }

      // ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
      public int Actions()
      {
       
       #region 查询客户
       //条件为空查询
       try
       {
        lr.start_transaction("GetCustomerCountByCondition");
        object[] param = {methodLR.GetQueryCustomerConditions()};

        Object ōbj = System.Activator.CreateInstance(typeof(Proxy.CustomerProxy));
        MethodInfo method = obj.GetType().GetMethod("GetCustomerCountByCondition");
        DataTable dt = (DataTable)method.Invoke(obj,param);
        int count = Convert.ToInt32(dt.Rows[0][0]);

        if(count>0)
         lr.end_transaction("GetCustomerCountByCondition",lr.PASS);
        else
         lr.end_transaction("GetCustomerCountByCondition",lr.FAIL);
       }
       catch(Exception ex)
       {
        string error = ex.Message;
       }

       //按客户编号查询
       try
       {
        lr.start_transaction("GetCustomerCountByCode");
        object[] param1 = {methodLR.GetQueryCustomerByCodeConditions()};

        Object obj1 = System.Activator.CreateInstance(typeof(Proxy.CustomerProxy));
        MethodInfo method1 = obj1.GetType().GetMethod("GetCustomerCountByCondition");
        DataTable dt1 = (DataTable)method1.Invoke(obj1,param1);
        int count1 = Convert.ToInt32(dt1.Rows[0][0]);

        if(count1>0)
         lr.end_transaction("GetCustomerCountByCode",lr.PASS);
        else
         lr.end_transaction("GetCustomerCountByCode",lr.FAIL);
       }
       catch(Exception ex)
       {
        string error = ex.Message;
       }

       //按客户名称查询
       try
       {
        lr.start_transaction("GetCustomerCountByCustomerName");
        object[] param2 = {methodLR.GetQueryCustomerByCustomerName()};

        Object obj2 = System.Activator.CreateInstance(typeof(Proxy.CustomerProxy));
        MethodInfo method2 = obj2.GetType().GetMethod("GetCustomerCountByCondition");
        DataTable dt2 = (DataTable)method2.Invoke(obj2,param2);
        int count2 = Convert.ToInt32(dt2.Rows[0][0]);

        if(count2>0)
         lr.end_transaction("GetCustomerCountByCustomerName",lr.PASS);
        else
         lr.end_transaction("GetCustomerCountByCustomerName",lr.FAIL);
       }
       catch(Exception ex)
       {
        string error = ex.Message;
       }

       //按客户类型查询
       try
       {
        lr.start_transaction("GetCustomerCountByCustomerTypeId");
        object[] param3 = {methodLR.GetQueryCustomerByCustomerTypeId()};

        Object obj3 = System.Activator.CreateInstance(typeof(Proxy.CustomerProxy));
        MethodInfo method3 = obj3.GetType().GetMethod("GetCustomerCountByCondition");
        DataTable dt3 = (DataTable)method3.Invoke(obj3,param3);
        int count3 = Convert.ToInt32(dt3.Rows[0][0]);

        if(count3>0)
         lr.end_transaction("GetCustomerCountByCustomerTypeId",lr.PASS);
        else
         lr.end_transaction("GetCustomerCountByCustomerTypeId",lr.FAIL);
       }
       catch(Exception ex)
       {
        string error = ex.Message;
       }

       //按销售员查询
       try
       {
        lr.start_transaction("GetCustomerCountBySalesManId");
        object[] param4 = {methodLR.GetQueryCustomerBySalesManId()};

        Object obj4 = System.Activator.CreateInstance(typeof(Proxy.CustomerProxy));
        MethodInfo method4 = obj4.GetType().GetMethod("GetCustomerCountByCondition");
        DataTable dt4 = (DataTable)method4.Invoke(obj4,param4);
        int count4 = Convert.ToInt32(dt4.Rows[0][0]);

        if(count4>0)
         lr.end_transaction("GetCustomerCountBySalesManId",lr.PASS);
        else
         lr.end_transaction("GetCustomerCountBySalesManId",lr.FAIL);
       }
       catch(Exception ex)
       {
        string error = ex.Message;
       }

       //按话务员查询
       try
       {
        lr.start_transaction("GetCustomerCountByTelephonistId");
        object[] param5 = {methodLR.GetQueryCustomerByTelephonistId()};

        Object obj5 = System.Activator.CreateInstance(typeof(Proxy.CustomerProxy));
        MethodInfo method5 = obj5.GetType().GetMethod("GetCustomerCountByCondition");
        DataTable dt5 = (DataTable)method5.Invoke(obj5,param5);
        int count5 = Convert.ToInt32(dt5.Rows[0][0]);

        if(count5>0)
         lr.end_transaction("GetCustomerCountByTelephonistId",lr.PASS);
        else
         lr.end_transaction("GetCustomerCountByTelephonistId",lr.FAIL);
       }
       catch(Exception ex)
       {
        string error = ex.Message;
       }

       //按客户电话查询
       try
       {
        lr.start_transaction("GetQueryCustomerByTelephone");
        object[] param6 = {methodLR.GetQueryCustomerByTelephone()};

        Object obj6 = System.Activator.CreateInstance(typeof(Proxy.CustomerProxy));
        MethodInfo method6 = obj6.GetType().GetMethod("GetCustomerCountByCondition");
        DataTable dt6 = (DataTable)method6.Invoke(obj6,param6);
        int count6 = Convert.ToInt32(dt6.Rows[0][0]);

        if(count6>0)
         lr.end_transaction("GetQueryCustomerByTelephone",lr.PASS);
        else
         lr.end_transaction("GetQueryCustomerByTelephone",lr.FAIL);
       }
       catch(Exception ex)
       {
        string error = ex.Message;
       }

       //按客户地址查询
       try
       {
        lr.start_transaction("GetQueryCustomerByAddress");
        object[] param7 = {methodLR.GetQueryCustomerByAddress()};

        Object obj7 = System.Activator.CreateInstance(typeof(Proxy.CustomerProxy));
        MethodInfo method7 = obj7.GetType().GetMethod("GetCustomerCountByCondition");
        DataTable dt7 = (DataTable)method7.Invoke(obj7,param7);
        int count7 = Convert.ToInt32(dt7.Rows[0][0]);

        if(count7>0)
         lr.end_transaction("GetQueryCustomerByAddress",lr.PASS);
        else
         lr.end_transaction("GetQueryCustomerByAddress",lr.FAIL);
       }
       catch(Exception ex)
       {
        string error = ex.Message;
       }

       //综合查询
       try
       {
        lr.start_transaction("GetQueryCustomerByAll");
        object[] param8 = {methodLR.GetQueryCustomerByAll()};

        Object obj8 = System.Activator.CreateInstance(typeof(Proxy.CustomerProxy));
        MethodInfo method8 = obj8.GetType().GetMethod("GetCustomerCountByCondition");
        DataTable dt8 = (DataTable)method8.Invoke(obj8,param8);
        int count8 = Convert.ToInt32(dt8.Rows[0][0]);

        if(count8>0)
         lr.end_transaction("GetQueryCustomerByAll",lr.PASS);
        else
         lr.end_transaction("GetQueryCustomerByAll",lr.FAIL);
       }
       catch(Exception ex)
       {
        string error = ex.Message;
       }

       #endregion      
     
       return lr.PASS;
      }

      // ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
      public int Terminate()
      {
       // TO DO: Add virtual user's termination routines

       return lr.PASS;
      }

     }
    }

    Method代码如下:

    using System;
    using Common;
    using Proxy;
    using DAT;
    using ShangXin;
    using ShangXin.Data;
    using ShangXinInterface;
    using System.Collections;
    using System.Data;

    namespace LRQueryCustomer
    {
     ///


     /// Method 的摘要说明。
     ///

     public class Method
     { 
      Proxy.LoginProxy loginProxy = new LoginProxy();

      public Method()
      {
       //
       // TODO: 在此处添加构造函数逻辑
       //
      }
      public void SetAuth(AuthData auth,CertificationInfo ci)
      {
       for(int i=0;i   {
        ci.AddAuthority((EnumAuthority)(Enum.Parse(typeof(EnumAuthority),auth.Rows[i]["PKID"].ToString())));
       }
      }

      
      public void GetGroupAuth(GroupData groupData,CertificationInfo ci)
      {
       try
       {
        //判断组记录是否为空
        if (groupData.Rows.Count > 0)
        {
         for(int i=0; i     {
          //根据组,获取对应的权限信息,并加载到该用户的权限列表中
          int groupID = SXConvert.ToInt32(groupData.Rows[i][GroupData.PKID]);
          AuthData Groupad = loginProxy.GetGroupAuthByGroupId(groupID);
         
          //加载权限
          if (Groupad.Rows.Count > 0)
          {
           SetAuth(Groupad,ci);
           ci.AddGroup(groupID);
          }

          int parentGroupID = loginProxy.GetParentGroupByGroupID(groupID);

          if (parentGroupID > 0)
          {
           GroupData subGroupData = loginProxy.GetGroupByPKID(parentGroupID);

           //循环加载
           GetGroupAuth(subGroupData,ci);
          }
         }
        }
       }
       catch(Exception exp)
       {
        throw new Exception(exp.Message);
       }
      }
       public void AddToCurrentEmployee(CertificationInfo ci,CertificationInfo CI)
       {
        //先清空当前用户的权限列表
        ci.ClearAuthority();

        //增加基本属性与权限列表
        ci.LoginName = CI.LoginName;
        ci.Password = CI.Password;
        ci.EmployeeId = CI.EmployeeId;
        ci.EmployeeName = CI.EmployeeName;
        ci.RecordCount = CI.RecordCount;

        for(int i=0;i    {
         ci.AddAuthority((EnumAuthority)CI.Authority[i]);
        }

        for(int i=0;i    {
         ci.AddGroup((int)CI.Group[i]);
        }
       }

      #region 查询客户
      //条件为空查询
      public Hashtable GetQueryCustomerConditions()
      {
       Hashtable conditions = new Hashtable();
       int parentId = -1;//上一级公司
       int customerTypeId = -1;//客户类型
       string code = "";//客户编码
       int salesManId = -1;//销售员
       int employeeId = 64;//
       string customerName = "";//客户名称
       int telePhinstId = -1;//话务员   
       string telephone="";//客户电话
       string address="";//客户地址
       
       conditions.Add("PARENTID",parentId);
       conditions.Add("CUSTOMERTYPEID",customerTypeId);
       conditions.Add("CODE",code);
       conditions.Add("SALESMANID",salesManId);
       conditions.Add("EMPLOYEEID",employeeId);
       conditions.Add("CUSTOMERNAME",customerName);
       conditions.Add("TELEPHINSTID",telePhinstId);
       conditions.Add("TELEPHONE",telephone);
       conditions.Add("ADDRESS",address);

       return conditions;

      }

      //按客户编号查询
      public Hashtable GetQueryCustomerByCodeConditions()
      {
       Hashtable conditions = new Hashtable();
       int parentId = -1;//上一级公司
       int customerTypeId = -1;//客户类型
       string code = "31450";//客户编码
       int salesManId = -1;//销售员
       int employeeId = 64;//
       string customerName = "";//客户名称
       int telePhinstId = -1;//话务员   
       string telephone="";//客户电话
       string address="";//客户地址
       
       conditions.Add("PARENTID",parentId);
       conditions.Add("CUSTOMERTYPEID",customerTypeId);
       conditions.Add("CODE",code);
       conditions.Add("SALESMANID",salesManId);
       conditions.Add("EMPLOYEEID",employeeId);
       conditions.Add("CUSTOMERNAME",customerName);
       conditions.Add("TELEPHINSTID",telePhinstId);
       conditions.Add("TELEPHONE",telephone);
       conditions.Add("ADDRESS",address);

       return conditions;

      }

      //按客户名称查询
      public Hashtable GetQueryCustomerByCustomerName()
      {
       Hashtable conditions = new Hashtable();
       int parentId = -1;//上一级公司
       int customerTypeId = -1;//客户类型
       string code = "";//客户编码
       int salesManId = -1;//销售员
       int employeeId = 64;//
       string customerName = "一本万利公司第31450分公司";//客户名称
       int telePhinstId = -1;//话务员   
       string telephone="";//客户电话
       string address="";//客户地址
       
       conditions.Add("PARENTID",parentId);
       conditions.Add("CUSTOMERTYPEID",customerTypeId);
       conditions.Add("CODE",code);
       conditions.Add("SALESMANID",salesManId);
       conditions.Add("EMPLOYEEID",employeeId);
       conditions.Add("CUSTOMERNAME",customerName);
       conditions.Add("TELEPHINSTID",telePhinstId);
       conditions.Add("TELEPHONE",telephone);
       conditions.Add("ADDRESS",address);

       return conditions;

      }

      //按客户类型查询
      public Hashtable GetQueryCustomerByCustomerTypeId()
      {
       Hashtable conditions = new Hashtable();
       int parentId = -1;//上一级公司
       int customerTypeId = 2;//客户类型
       string code = "";//客户编码
       int salesManId = -1;//销售员
       int employeeId = 64;//
       string customerName = "";//客户名称
       string telePhinstId ="" ;//话务员   
       int telephone=-1;//客户电话
       string address="";//客户地址
       
       conditions.Add("PARENTID",parentId);
       conditions.Add("CUSTOMERTYPEID",customerTypeId);
       conditions.Add("CODE",code);
       conditions.Add("SALESMANID",salesManId);
       conditions.Add("EMPLOYEEID",employeeId);
       conditions.Add("CUSTOMERNAME",customerName);
       conditions.Add("TELEPHINSTID",telePhinstId);
       conditions.Add("TELEPHONE",telephone);
       conditions.Add("ADDRESS",address);
       
       return conditions;

      }

      //按销售员查询
      public Hashtable GetQueryCustomerBySalesManId()
      {
       Hashtable conditions = new Hashtable();
       int parentId = -1;//上一级公司
       int customerTypeId = -1;//客户类型
       string code = "";//客户编码
       int salesManId = 314;//销售员
       int employeeId = 529;//
       string customerName = "";//客户名称
       int telePhinstId = -1;//话务员   
       string telephone="";//客户电话
       string address="";//客户地址
       
       conditions.Add("PARENTID",parentId);
       conditions.Add("CUSTOMERTYPEID",customerTypeId);
       conditions.Add("CODE",code);
       conditions.Add("SALESMANID",salesManId);
       conditions.Add("EMPLOYEEID",employeeId);
       conditions.Add("CUSTOMERNAME",customerName);
       conditions.Add("TELEPHINSTID",telePhinstId);
       conditions.Add("TELEPHONE",telephone);
       conditions.Add("ADDRESS",address);

       return conditions;

      }

      //按话务员查询
      public Hashtable GetQueryCustomerByTelephonistId()
      {
       Hashtable conditions = new Hashtable();
       int parentId = -1;//上一级公司
       int customerTypeId = -1;//客户类型
       string code = "";//客户编码
       int salesManId = -1;//销售员
       int employeeId = 64;//
       string customerName = "";//客户名称
       int telePhinstId = 23;//话务员   
       string telephone="";//客户电话
       string address="";//客户地址
       
       conditions.Add("PARENTID",parentId);
       conditions.Add("CUSTOMERTYPEID",customerTypeId);
       conditions.Add("CODE",code);
       conditions.Add("SALESMANID",salesManId);
       conditions.Add("EMPLOYEEID",employeeId);
       conditions.Add("CUSTOMERNAME",customerName);
       conditions.Add("TELEPHINSTID",telePhinstId);
       conditions.Add("TELEPHONE",telephone);
       conditions.Add("ADDRESS",address);

       return conditions;

      }

      //按客户电话
      public Hashtable GetQueryCustomerByTelephone()
      {
       Hashtable conditions = new Hashtable();
       int parentId = -1;//上一级公司
       int customerTypeId = -1;//客户类型
       string code = "";//客户编码
       int salesManId = -1;//销售员
       int employeeId = 64;//
       string customerName = "";//客户名称
       int telePhinstId = 23;//话务员   
       string telephone="88888888";//客户电话
       string address="";//客户地址
       
       conditions.Add("PARENTID",parentId);
       conditions.Add("CUSTOMERTYPEID",customerTypeId);
       conditions.Add("CODE",code);
       conditions.Add("SALESMANID",salesManId);
       conditions.Add("EMPLOYEEID",employeeId);
       conditions.Add("CUSTOMERNAME",customerName);
       conditions.Add("TELEPHINSTID",telePhinstId);
       conditions.Add("TELEPHONE",telephone);
       conditions.Add("ADDRESS",address);

       return conditions;

      }

      //按客户地址
      public Hashtable GetQueryCustomerByAddress()
      {
       Hashtable conditions = new Hashtable();
       int parentId = -1;//上一级公司
       int customerTypeId = -1;//客户类型
       string code = "";//客户编码
       int salesManId = -1;//销售员
       int employeeId = 64;//
       string customerName = "";//客户名称
       int telePhinstId = 23;//话务员   
       string telephone="";//客户电话
       string address="天堂区幸福大道第31450号";//客户地址
       
       conditions.Add("PARENTID",parentId);
       conditions.Add("CUSTOMERTYPEID",customerTypeId);
       conditions.Add("CODE",code);
       conditions.Add("SALESMANID",salesManId);
       conditions.Add("EMPLOYEEID",employeeId);
       conditions.Add("CUSTOMERNAME",customerName);
       conditions.Add("TELEPHINSTID",telePhinstId);
       conditions.Add("TELEPHONE",telephone);
       conditions.Add("ADDRESS",address);

       return conditions;

      }
      //综合查询
      public Hashtable GetQueryCustomerByAll()
      {
       Hashtable conditions = new Hashtable();
       int parentId = -1;//上一级公司
       int customerTypeId = -1;//客户类型
       string code = "005305";//客户编码
       int salesManId =-1 ;//销售员
       int employeeId = 64;//
       string customerName = "一本万利公司第120分公司";//客户名称
       int telePhinstId =-1;//话务员   
       string telephone="88888888";//客户电话
       string address="天堂区幸福大道第120号";//客户地址
       
       conditions.Add("PARENTID",parentId);
       conditions.Add("CUSTOMERTYPEID",customerTypeId);
       conditions.Add("CODE",code);
       conditions.Add("SALESMANID",salesManId);
       conditions.Add("EMPLOYEEID",employeeId);
       conditions.Add("CUSTOMERNAME",customerName);
       conditions.Add("TELEPHINSTID",telePhinstId);
       conditions.Add("TELEPHONE",telephone);
       conditions.Add("ADDRESS",address);

       return conditions;

      }
      #endregion
     }
    }


  • 在EXCEL里面添加超联接(hyperlink)的方法

    风过无息 发布于 2006-12-12 10:35:58

    Sub ReportInformation(filename)
    ' create the Excel object
    Set ExcelObj = CreateObject("Excel.Application")
    ' add a new Workbooks and a new Sheet
    ExcelObj.Workbooks.Add
    Set NewSheet = ExcelObj.Sheets.Item(1)
    NewSheet.Name = "Page Information"
     ' customize the Sheet layout
    NewSheet.Cells(1,1).Value = "Tom"
    NewSheet.Cells(2,1).Value = "Sohu"
    NewSheet.Hyperlinks.Add NewSheet.Cells(1,1), "http://www.tom.com/"
    NewSheet.Hyperlinks.Add NewSheet.Cells(2,1), "http://www.sohu.com/"
     ' save the Excel file
          ExcelObj.ActiveWorkbook.SaveAs filename
          ' close the application and clean the object
          ExcelObj.Quit
          Set ExcelObj = Nothing
       End Sub
    call ReportInformation("c:\test.xls")

     


     

  • [论坛] 把我收集的性能资料做成一个程序给大家

    fish_yy 发布于 2006-12-16 22:34:15

    把我收集的性能资料做成一个程序给大家,方便大家写测试总结使用

    performance.exe
    (2005-12-05 09:07:54, Size: 329 kB, Downloads: 549)

  • IE7.0访问QC的办法

    baoju 发布于 2006-12-22 15:17:37

    近日好多朋友装了IE7.0,但是装了之后不能访问QC了,给他们带来了好多麻烦,下面的方法可以解决这个问题

    1)登录QC服务器

    2)找到start_a.htm文件 

      这个文件隐藏得比较深,还是在QC目录下搜索这个文件会比较快点.

    3)编辑这个文件

      打开这个文件,找到变量fMSIE3456,在后面加一句脚本:(ua.lastIndexOf('MSIE 7.0') != -1)

    4)OK,可以告诉朋友们,用IE7.0也可以访问QC了

  • 为标准对象创建客户化GUI检查

    gp_jl 发布于 2007-01-06 23:25:22

    默认情况下,WR为标准对象提供一些基本检查功能。但是,当现存的check功能不是很好满足自己需要的时候,就需要手动添加一些GUI检查点来完善。为标准对象添加属性检查的基本步骤如下:
    1 为该检查创建捕获功能,以便确定预期结果和实际结果;
    2 为该检查创建比较功能,以便确定预期结果和实际结果;
    3 注册使用该捕获功能和比较功能的检查;
    4 将此新检查同标准对象类连接;
    5 设置默认检查;
    下面为计算器的按钮添加一个 Size 属性的检查。给出一段简单代码,并对一些基本语法给予注释。

    脚本1:(完成了捕获功能和比较功能)
    #Test Type:Compiled Module

    ######################################################################
    #Descrīption: User-Define function using to capture size of a object
    ######################################################################

    function size_capture(object, inout size)
    {
        auto height,width;
        obj_get_info(object, "height", height);  
        obj_get_info(object, "width", width);
        size = height * width;
        return 0;
    }


    ######################################################################
    #Descrīption: User-Define function using to compare the actualResult and
    #the ExpectedResult,and return E_OK when successed,or else
    #return E_DIFF.
    #Note: E_DIFF is the error code for "GUI verification missmatch found."You may use #the defualt_compare_func to do this.
    ######################################################################

    public function com_result (in expected, in actual){
     if (expected == actual)
      return (E_OK);
     else
      return (E_DIFF);
    }

     

    脚本2:

    load("C:\\size");

    #注册使用该捕获功能和比较功能的检查
    #Syntax: gui_ver_add_check(check_name, capture_function,comparison_funcion[,display_function][,type])
    #  use the display_function parameter only If you use the gui_ver_add_class #function to creating a GUI
    #checkpoint dialog box with a custom user interface. Type: 1 for window, 0 for any #other GUI object
    gui_ver_add_check ("Size","size_capture","com_result","",0);


    #将此新检查同标准对象类连接
    #
    #Syntax: gui_ver_add_check_to_class ( class, property_check_name );
    #class is the name of either the MSW_class or the standard class with which
    #the check is associated.
    #property_check_name is the name of the property check you defined using the
    #gui_ver_add_check TSL function. The new property check will appear at
    #the bottom of the list of properties displayed for the class in the GUI
    #checkpoint dialog boxes.
    #Note: you can associate the same property check with more than one class by #repeating this function for each GUI objcet class.
    gui_ver_add_check_to_class("push_button","Size");


    #设置默认检查。
    #
    #Syntax: gui_ver_set_default_checks ( class, check_name1...check_namen );
    #. class is the name of either the MSW_class or the standard class for which
    #you want to set the default checks.
    #. check_name1-n are the names of the property checks to be set as defaults.
    #Note: use this function to define a custom property check as a default property #check, and it overwrites all previous default property #checks.So you must include #all the property checks that you want to set as defaults for the GUI object #class.Additonally, Using it to #define additional standard property checks as #default property checks.
    #Note that when you define more than one default property check, separate the #default property checks with spaces.
    gui_ver_set_default_checks ("push_button","Size");

    运行环境:WinXP+SP2,WR7.6
    参考文件:《WinRunner Customization Guide》

  • 关于Loadrunner监视RedHatLinux9

    yang119345 发布于 2007-01-07 14:05:42

    以下引用网络上高手关于lr监视rh9的文章:),哪里来的我也忘记了。

    ----------------------------------------------------------------------

    一、在服务器上安装rstatd守护进程
    安装步骤:
    1. 从网上下载rstatd
    2. 将该文件放到/home/user目录下
    3. chmod 777 rpc.rstatd----改变该文件读写的权限,拥有所有权限。
    4. chmod 777 configure ---同上
    5. ./configure ---配置
    6. make ---编译
    7. make install ---安装
    8. rpc.rstatd ---启动rstatd进程

    二、在lr中配置
         从LR里面add measurement, 填写linux机器的IP,出现所有unix/linux的计数器,包括cpu的,mem的,disk,network的。介绍几个常用的:
    average load :在过去的1分钟,的平均负载
    cpu utilization: cpu的使用率
    disk traffic: disk传输率
    paging rate: 每秒从磁盘读到物理内存,或者从物理内存写到页面文件的内存页数
    Swap-in rate: 每秒交换到内存的进程数
    Swap-out rate: 每秒从内存交换出来的进程


    补充一些常见的问题及处理方法:
    1、在执行配置或安装命令过程中出现“拒绝的权限”的提示;
    答:是由于文件的权限引起的,应该给当前用户所有文件的“777”权限,即完全控制权限。

    2、安装好后从LoadRunner中看不到信息,但是没有报错;
    答:可能是返回的信息值比较小,所以在图中几乎看不到,例如:如果没有运行程序的话,CPU的使用率接近于0,所以在监视图中看不到变化。也有可能是采样的频率过大,可以在图表中设置没1秒获取一次信息,这样界面就刷新的比较及时了。

    3、监视一段时间后LoadRunner中提示有错误发生不能继续监视到信息;
    答:可能是由于CPU长时间处于高负荷状态,而导致系统自动关闭了该服务。可以在LoadRunner中重新加一次计数器,并且设置取样的时间稍长一点,就会避免这种情况。

    4、以前用LoadRunner监视都是成功的,但是再次监视不到信息;
    答:有可能是由于系统重新启动,而没有打开rstatd守护进程。可以手工重新打开一次,使用命令“rpc.rstatd”,另外可以使用“rpcinfo -p”命令来查看当前系统是否已经启动了rstatd守护进程。

    ----------------------------------------------------------------------

    以上是网络上牛人的文章,非常经典。但是我就是不行,后来终于搞定了。总结了下,要注意的有两点,就是不要用redhat9光盘自带的rstatd还有就是防火墙。

    首先察看下系统默认是不是安装了rstatd,如果安装了,干掉他(怒,先切换root权限)

     #rpm -qf /sbin/rpc.statd

    用这个命令察看下改命令属于哪个软件包,如果安装了会显示nfs-utils-XXXX-xxxxx,如果没有安装,则会提示你没有找到/sbin/rpc.statd命令。

    下面我们来干掉这家伙

    #rpm -e nfs-utils

    okey干掉了,如果你不放心可以重复前面步骤。

    下面就是去下载rstatd包然后编译安装了,我的包是http://heanet.dl.sourceforge.net/sourceforge/rstatd下载的最新版本。方法详见上面高人写的,也可以看软件包里面的INSTALL文档:),安装好了,运行下,然后rpcinfo -p看看rpc运行状况,以下是我的系统内显示的结果(注意后面四项rstatd,如果没有这几个说明没成功,找下原因)

       程序 版本 协议   端口
        100000    2   tcp    111  portmapper
        100000    2   udp    111  portmapper
        100024    1   udp   1024  status
        100024    1   tcp   1024  status
        391002    2   tcp   1025  sgi_fam
        100001    5   udp    733  rstatd
        100001    3   udp    733  rstatd
        100001    2   udp    733  rstatd
        100001    1   udp    733  rstatd

    干掉防火墙(当然最好的方法就是自己配置防火墙,可以蘑菇不会这玩意儿,痛心疾首,只好暂时干掉他- -!)

    #service --status-all | grep iptables #如果输入以后没有啥显示,说明防火墙关着,如果发现有3~4个项存在,那进行下一步

    #service iptables stop #停止所有iptables相关的咚咚,当然你也可以用lokkit搞定(貌似redhat9中这玩意儿有bug)

我的栏目

数据统计

  • 访问量: 24168
  • 日志数: 35
  • 图片数: 3
  • 建立时间: 2007-05-08
  • 更新时间: 2008-05-26

RSS订阅

Open Toolbar