发布新日志

  • 【zz】专访微软测试经理:软件测试的未来在哪里

    2009-03-10 14:27:37


    通过上一篇关于微软测试工程师李和恒的采访,我们大概了解了软 件测试工程在工作中思考问题的方式。每个人都寻求能在一个公司做出更多的成绩,争取晋升的机会。但想得到晋升的机会,我们需要思考问题能力和方式的提升和 一些转变。于是我们走访了微软测试经理Francis Zhou,请他来谈谈从测试经理角度是如何看待软件测试工作。

            Francis Zhou 毕业于加州大学系统圣克鲁斯分校。他于2000年加入微软,曾在总部先后担任软件开发测试工程师及测试组长等职务,参与了Windows XP及Windows Presentation Foundation的开发。2005年初他正式加入微软亚洲工程院并先后参与了TTS, Microsoft Speech Server, ActiveSync, GamesUX等项目的开发及测试。他现任测试经理,主管游戏平台及移动平台多媒体软件的测试开发。

            Bug还没出现以前就将其杜绝,这才是软件测试的未来

            机遇总是光临有准备的头脑,Francis走上测试之路虽然有些偶然,但他在解决问题的特质才是真正的关键。他是从大学一毕业就加入微软做了一名软 件开发测试工程师(SDET),最初做这行是测试这行选中了他,然后他回过头来又选中了测试。在大学时对软件工业的认识很薄浅,以为除了开发工程师就是管 理人员。后来微软来他们学校招聘,他第一次听到还有SDET这么个职位,是荐于纯开发与纯测试之间的。当时没怎么在意,后来到微软面试时才知道面的是 SDET。因为一直很向往加入微软,所以不管三七二十一就答应了。加入微软之后他有一次偶然碰到了那位到他们学校招聘的人力咨询师,就问她为什么推荐自己 做SDET,才知道是自己回答她问题的时候很注重对细节的描述,而且喜欢把问题拆开来了解决,而这些都是一个SDET的基础素质,所以说最初是测试选中了 自己。进入微软后做了一段时间后有很多其他的职位可以选择,但在测试行业中总是有着解决不完的难题。软件工程本身就是一个很新的课题,而软件测试工程则是 近十几年才开始被重视的,里面有很多需要完善解决的东西。

            Francis说:“我觉得在这个领域有很好的发展前景。现在软件测试大多数还只是停留在找bug阶段,而如果真的要做好产品的话要在bug还没出 现以前就将其杜绝,这才是软件测试的未来。因为我对软件测试这个行业很看好,就留了下来,所以可以说我回过头来又选中了测试。”

            至今没有碰到新的理念能完全否定以前的认识

            每个人对事物的认识都不是不断变化的,通过学习知识和项目经验的积累。有些时候人们会产生一些顿悟,对一个事物有了全新的理解。谈到是否在软件测试 方面有过这样的顿悟,Francis认为至今还没有碰到一个新的理念能完全否定以前对测试理念的认识,因为他对测试的认识是慢慢积累而后拓展到新的领域 的。

            刚进微软不久,Francis从一位资深工程师那里学到了自动化测试的几种常用模式,使自己写的自动化测试程序更加规范化。它可以在一个框架中重复 利用,更有效率地组建自动化测试案例,这个认识在以后诸多产品测试计划中都起到了很重要的作用。第二个认识是在听了一个演讲后领悟的。那次演讲的主题是怎 么样提高测试的效率,如何从找缺陷转换到防止缺陷的产生,使Francis对测试团队的作用提高到了一个新的层面,从单单在产品里找bug,到了如何与开 发团队合作把整个团队的工程质量水平提高上来,也就是做到从Bug detection到Bug prevention的转变。

            Francis说:“从那以后,我开始更加强调测试团队在产品设计以及开发初期的介入,使很多bug还在设计期间就被找出来并且该掉,不仅提高了测试的效率同时也提高了产品的质量。”

            9个月测试了近1500个游戏,总部测试组都无法做出的成绩

            想要成为测试经理,我们必须要先了解一下测试经理主要的职责是什么。在微软一个测试经理主要负责制定一个产品或者一组重要功能的测试计划,然后按照 计划带领一个测试团队去完成对该产品的测试任务。测试计划里除了要规划哪些功能要测、哪些不要测之外,还要详细解释各种测试方法在该产品测试中的应用方 法,以及设置其优先级。制定测试计划也就是对该产品从测试角度进行分析,然后根据现状做出取舍,以便在有限的时间和资源内对产品质量做出最有效的评估的一 个战略规划。而后在计划实施阶段,测试经理也要帮助建立起一些必要的工程流程,以保证计划的实施。然后在一个产品最终阶段,测试经理通常会担当质量把门人 的角色,保证严重的缺陷都能够得到修复。

            从一个测试工程师到测试经理,Francis作过了很多项目。谈到说他觉得最满意的项目,Francis说:“这个比较难说,因为每个项目都有它做 得好的和可以改进的地方。如果一定要举个例子,那么我会选择Windows Vista里对Games Explorer的测试。我们是在Vista发布前九个月时从总部那边把这个项目接手过来的。做过测试的人都知道,一个项目的结尾阶段是对测试组最具考验 的时候。我们接手该产品的测试任务以后,只花了1个多月的时间就把所有事都接管过来了,而且做得比以前更好、更有效。而后的几个月中我们又找到并修改了 Games Explorer中以前没有找的很多缺陷,而且测试了近1500个游戏在Vista上的兼容性。自豪地说,我们组在这9个月里做出了总部测试组都无法做出 的成绩。能在这么短的时间内取得这样的成绩,除了归功于他们在先进自动化技术上的投入,主要还是靠着我们建立的过硬的队伍。”

            最近,Francis主要在做下一版Windows Mobile里的几个核心组件,以及一个在桌面平台上与Xbox Live有关的用户端软件。这两个项目对于Windows Mobile和Xbox Live平台都是至关重要的,期待会有更出色的成绩。

    通过上一篇关于微软测试经理Francis Zhou的采访,我们大概了解了他在软件测试方面的一些成长故事。在任何一家公司,你光遇到一个好的经理还是不够的,还需要好的“战友”和成长环境。于是 我们继续请微软测试经理Francis Zhou来谈谈微软在软件测试方面如何招募人才和培养人才的。

            测试经理必须保证“招进来的人要比自己聪明”

            除了技术类的工作以外,测试经理另外一部份重点工作是组建及维持测试团队。微软的成功有很大一部分来自于对人才的重视,而这种重视在招聘期间就可以 体现出来。微软每一个正式员工都是经过了很严格的面试才成为正式员工的,而测试经理作为资深的管理人员,招聘与面试自然是少不了的。

            Francis说:“我们组的每一个测试人员我基本上都曾经面试过,而且如果对方是测试组长或者经理我一定会参加,因为对方很可能会成为自己的接班 人,而我必须保证我招进来的人要比他聪明。仅仅把一个员工招进微软是不够的,做为领导我们有责任为员工创造环境而使他们能最大化的开发他们的潜能,其中也 包括对员工事业路线的辅导。”

            提到微软亚洲工程院在培养测试人员上的特点的时候,Francis指出微软就是给个人贡献者和职业管理人员都创造成功的机会,让他们得其所好。其实 这也是微软多年来一直提倡的内部管理理念。除此之外,他们也强调对测试的重视,以及将测试人员与开发人员同等对待的理念。这些政策使由测试、开发、PM组 成的团队能更有效的在一起工作,从而间接的提高了产品质量。

            最宝贵的经验和教训在人才招聘和管理上

            一个团队的成功离不开他的管理者,但是不是所有人都能成为管理者。成为管理人员之后,很多最宝贵的经验和教训会来自在人才的招聘及管理上。在这点 上,Francis的失败给他带来的经验比成功宝贵的多。第一次教训是在刚刚做测试组长后不久,有一个以前做手工测试做的很出色的员工调到了他的组里。因 为他们测试的是API,因此他向开发测试软件转行。这次转行做的很费力,他个人和其他组员都受到了一些负面影响,而开始他以为是个时间可以解决的问题,只 要他在上面花更多的时间和精力就能解决的。但后来经过一年的失败他最终还是换到了另外一个更适合他特长的团队。

            Francis说:“这次教训使我从反面认识到为一个信息工人找到适合他而且他喜欢做的工作是多么的重要。打那儿以后我更加注重员工的兴趣以及特长,为他们创造能最大发挥他们潜能的工作环境。”

            IT企业长久发展必须能为个人贡献者和职业管理人员都创造成功的机会

            在任何一家企业,根据每个人的不同特质安排不同的工作,不一定每个人都要成为管理者,技术路线和管理路线都应该是可行的。Francis刚成为测试 经理以后,寻找能接替他工作的测试组长时发生了一件事情。那时有一个员工工作很努力,技术上也很强,所以顺理成章就叫他做了组长。但之后不久就发现他在带 头和管理团队方面并没有想象的那么在行,他自己为这责任所产生的压力导致病倒,而组员也为此变得有些群龙无首。最后通过大家的努力这个项目按时完成。

            Francis说:“从这一次我体会到了一个好的技术人员不一定就会是一个好的管理者,而一个IT企业要长久发展就必须能为个人贡献者 (individual contributor)和职业管理人员都创造成功的机会。从那儿以后我加强了为个人贡献者创造机会,能让他们不必成为管理人员也能成功。”

            采访后记

            采访之后闲谈,记者得知在微软,测试是一个必须的环节,不是可有可无的。而且在招测试开发人员时所用的标准是跟开发人员一样、甚至更高的标准,因为 要一个出色的测试人员同时也是一个出色的开发人员。其次是对软件开发流程的重视,在开始测试前有测试设计文档,而在软件生命周期的每一步都明确知道测试队 伍的任务。这两点是国内软件企业应该研究以及学习的,当然也要根据自己公司的实际情况去适当改革、引进。

            说到最喜欢的书,Francis认为”Lessons Learned in Software Testing“是一本作者们把在现实工作中所积累的经验提炼后写出来的有效参考书,里面有很多值得引荐的经验教训,比起那些光谈理论的软件测试书籍实用 的多,是本好书。
  • Traceability from Use Cases to Test Cases

    2009-01-30 10:59:28

    Traceability from Use Cases to Test Cases

    developerWorks

     

     


     


     


    Level: Introductory

    Peter Zielczynski (PZielczynski@tact.com), Director of Technology Solutions, The A Consulting Team, Inc.

    10 Feb 2006
    Updated 04 May 2006

    The article illustrates a formal method of deriving functional test cases from use cases, including how to create a use case, derive all scenarios, and create reasonable test cases, as well as use IBM® Rational® RequisitePro for traceability from use cases to scenarios and test cases.

    Overview of the requirements types

    A requirement is defined as "a condition or capability to which a system must conform".

    It can be:

    • A capability needed by a customer or user to solve a problem or achieve an objective
    • A capability that must be met or possessed by a system to satisfy a contract, standard, specification, regulation, or other formally imposed document
    • A restriction imposed by a stakeholder

    Figure 1 shows the requirements pyramid with the different levels of requirements.


    Figure 1. The requirements pyramid
    Requirement Pyramid

    On the top level are stakeholder needs. Usually, a project contains five to fifteen of these high-level needs. On the lower levels are features, use cases, and supplementary specifications. On different levels of these requirements are different details. The lower the level, the more detailed the requirement is. For example, a need can be: "Data should be persistent". The feature can refine this requirement to be: "System should use a relational database". On the supplementary specification level, the requirement will be even more specific: "System should use Oracle 9i database". The further down, the more detailed the requirement.

    Traceability between requirements

    Traceability is a technique that provides a relationship between different levels of requirements in the system. This technique helps you determine the origin of any requirement. Figure 2 illustrates how requirements are traced from the top level down. Every need usually maps to a couple of features, and then features map to use cases and supplementary requirements.


    Figure 2. Traceability requirements pyramid
    Requirement Pyramid

    Use cases describe functional requirements, and supplementary specifications describe non-functional items. In addition, every use case maps to many scenarios. Mapping use cases to scenarios, then, is a one to many relationship. Scenarios map to test cases also in a one to many relationship. Between needs and features, on the other hand, there is many to many mapping.

    Traceability plays several important roles:

    • Verify that an implementation fulfills all requirements: Everything that the customer requested was implemented
    • Verify that the application does only what was requested: Don't implement something that the customer never asked for
    • Help with change management: When some requirements change, we want to know which test cases should be redone to test this change

    A traceability item is a project element that needs to be traced from another element. In terms of IBM Rational RequisitePro it's everything that is represented by an instance of the requirement type. Some examples of requirement types in RequisitePro are stakeholder needs, features, use cases, actors, and glossary terms.

    In RequisitePro there is a convenient way of showing traceability in special views. Figure 3 shows an example of mapping features to use cases.


    Figure 3. Traceability in RequisitePro
    Traceability

    There is some question as to which direction the arrows should go: whether from lower level to higher level or from higher to lower level. Even the two examples in RequisitePro use different guidelines. The answer is that it doesn't matter, as long as you use them consistently across the project.

    Actors and use cases

    An actor is someone or something that interacts with the system. A use case is a descrīption of a system in terms of a sequence of actions. It should yield an observable result or value for the actor. Following are some characteristics of use cases, which:

    • Are initiated by an actor
    • Model an interaction between an actor and the system
    • Describe a sequence of actions
    • Capture functional requirements
    • Should provide some value to an actor
    • Represent a complete and meaningful flow of events

    The purpose of a use case is to facilitate agreement between developers, customers, and users about what the system should do. A use case becomes sort of a contract between developers and customers. It's also a basis for use-case realizations, which play a major role in design. In addition, you can produce sequence diagrams, collaboration diagrams, and class diagrams from use cases. Furthermore, you can derive user documentation from use cases. Use cases may also be useful in planning the technical content of iterations, and give system developers a better understanding of the purpose of the system. Finally, you can use them as an input for test cases.

    Use case diagrams present relationships between actors and use cases. In this article we will use an online bookstore as an example of a project. Figure 4 shows a use case diagram for this project.


    Figure 4. Use Case Diagram
     Diagram

    The general format of a use case is:

    1. Brief descrīption
    2. Flow of events
      • Basic flow
      • Alternative flow 1
      • Alternative flow 2
    3. Special requirements
    4. Preconditions
    5. Post-conditions
    6. Extension points
    7. Context diagram
    8. Activity diagram

    The basic flow contains the most popular sequence of actions, the steps that happen when everything goes correctly. Alternative flows represent variations of the flow, including less usual cases and error conditions. A context diagram is a part of a use case diagram showing the relationships of this particular use case to actors and other use cases. An activity diagram is a flow chart that explains the use case. The context diagram and the activity diagram are not necessary, but help you visualize the use case and its position in the project.

    In our Online Bookstore project, the basic flow of the use case place an order might look like this:

    1. B1 User enters web site address in the browser.

      System displays login page.

    2. B2 User enters an email address and a password.

      System confirms correct login, presents main page, and prompts for a search string.

    3. B3 User enters search string – partial name of a book.

      System returns all books matching search criteria.

    4. B4 User selects a book.

      System presents detailed information about a book.

    5. B5 User adds the book to a shopping cart.

      Shopping cart contents is presented to the user.

    6. B6 User selects "proceed to checkout" option.

      System asks for confirmation of a shipping address.

    7. B7 User confirms shipping address.

      System presents shipping options.

    8. B8 User selects shipping option.

      Systems asks which credit card will be used.

    9. B9 User confirms credit card that is stored in the system.

      System asks for final confirmation to place an order.

    10. B10 User places the order.

      System returns a confirmation number.

    Besides the basic flow, there are many alternative flows. The first alternative flow, for instance, describes what happens when the user is a new user (not yet registered with the online bookstore). In the basic flow, the user always has a user ID and password. In contrast, alternative flow 1 describes a case when a first-time user needs to register and provide customer data. Another example of an alternative flow is an invalid password. A user entering the wrong password gets an error message.


    Table 1 shows the alternative flows that were included in the use case "place an order":
    Table 1: Alternative flows
    A1 Unregistered user
    A2 Invalid password
    A3 No books matching search criteria were found
    A4 Decline a book
    A5 Continue shopping after storing a book in the shopping cart
    A6 Enter a new address
    A7 Enter a new credit card
    A8 Cancel order

    The following convention is used for naming the flows:

    Basic flow: B

    Alternative flows: A1, A2, A3, ...

    Steps in a basic flow: B1, B2, B3, ...

    Steps in alternative flow 1: A1.1, A1.2, A1.3, ...

    Steps in alternative flow 2: A2.1, A2.2, A2.3, ...

    To derive alternative flows, use activity diagrams. Figure 5 displays an activity diagram describing this use case.


    Figure 5. Activity diagram
    Activity

    The basic flow is a straight line down, while alternative flows are usually the loops going either back or forth.

    How to create test cases from the use cases

    Before creating a test case, you need to identify all of the scenarios for the given use case. A scenario is an instance of the use case. It describes one specific path through the flow of events. Figure 6 is a hypothetical graph representing a use case with a basic flow B and alternative flows A1, A2, A3, and A4. To find all scenarios, we need to draw all possible lines through this graph.


    Figure 6. Finding scenarios in a use case
    scenarios in a use case

    There is one scenario per alternative flow plus one scenario for each combination of alternative flows. There are definitely more scenarios than alternative flows, because there's one for A1, another one for A2, and one scenario which will be a combination of these two.

    The easiest way to describe a scenario is to provide a sequence of alternative flows, for example, do flow A2 twice, and then do flow A6:

    SC16: A2, A2, A6.

    Another way to describe a scenario is to list all the steps in it, but this is both more difficult and unnecessarily detailed.

    What should you do if you have infinite loops (loops going backwards)? Theoretically it would generate an infinite number of scenarios. Figure 7 shows an infinite loop going backwards.


    Figure 7. Infinite loops
    Loops

    The reasonable approach is to do the basic flow once, do a loop once, and then do a loop a second time. If the program works for both instances of the loop, you can assume it will work for all of them.

    The book ordering example has a basic flow and eight alternative flows. Four of them are going backwards, and the other four are going forward. If you want to describe all possible use case combinations, you will have over four thousand scenarios (there are eight alternative flows, four of which we may want to do twice because they are loops going backwards, so together it is 2 to the power of (8+4), which is equal to 4096. Obviously we don't need to do all of them.

    Choose which ones represent a reasonable subset of these four thousand scenarios. Usually it is wise to select a basic flow, one scenario covering each alternative flow, and some reasonable combinations of alternative flows. Using the examples in Table 1, it probably won't make sense to do a scenario that contains both flows A1 and A7, because they are so far apart on the diagram that they don't have any influence on each other. But it makes sense to do A1 and A2, since they are immediately after each other and may be correlated.

    Table 2 illustrates the selected scenarios: one representing the basic flow, eight representing each alternative flow, and six reflecting some combination of the flows (especially the ones that have two loops going backwards close to each other).

    The following 15 scenarios are worth testing:


    Table 2. Scenarios worth being tested
    Table 2: Capturing selected scenarios
    Scenario 1 Basic Flow Scenario 9 A8
    Scenario 2 A1 Scenario 10 A1, A2
    Scenario 3 A2 Scenario 11 A3, A4
    Scenario 4 A3 Scenario 12 A4, A5
    Scenario 5 A4 Scenario 13 A3, A5
    Scenario 6 A5 Scenario 14 A6, A7
    Scenario 7 A6 Scenario 15 A7, A8
    Scenario 8 A7

    How to Create a Scenario in RequisitePro

    Scenario is not a standard requirement type in RequisitePro, so you need to add it as a new requirement type. To do that, go to Project Properties, Select the Requirement Types tab, and click Add. Next, fill in the appropriate fields (as shown in Figure 8), and click OK.


    Figure 8: Adding a requirement type scenario
    Adding a scenario

    After creating the requirement type, we should enter all scenarios and set traceability from use cases to these scenarios, as shown in Figure 9.


    Figure 9: Traceability from use cases to scenarios.
    Traceability

    In RequisitePro, you can name scenarios with the name of the use case and a sequence of alternative flows (for example: UC1, A6, A7).

    Now that you have all the scenarios, you need to get the test cases. There are four steps to do that:

    1. Identify variables for each use case step
    2. Identify significantly different options for each variable
    3. Combine options to be tested into test cases
    4. Assign values to variables

    The following sections describe details of these steps.

    Step 1: Identify variables for each use case step

    You need to identify all input variables in all of the steps in the given scenario. For example, if in some step the user enters a user ID and password, there are two variables. One variable is the user ID, and the second variable is the password. The variable can also be a selection that the user can make (for instance, Save changes or Cancel).

    Here are all of the variables from the book ordering example:

    In step B2, there are two variables: e-mail and password. Both of them are strings. In step B3, search a book, the variable is a search string, so it is also a string. In step B4, we need to select a book from a list returned from the system. In step B8, we need to select a shipping option. Amazon.com provides four options.

    Step 2: Identify significantly different options for each variable

    Options are "significantly different" if they may trigger different system behavīor. For example, if we select a user id, which is supposed to be from 6 to 10 characters long, the following entries are significantly different:

    • Alex -- because it is too short, and we expect an error message to appear
    • Alexandria -- because it is a valid user id
    • Alexandrena -- because it is too long, and we expect the system to prevent us from entering a user id that long

    However, "Alexandria" and "JohnGordon" are not significantly different, because they are both valid user ids that should cause the system to react in the same way.

    The following guidelines describe some specific cases.

    An option can be considered significantly different if:

    1. It triggers different flow of the process (usually an alternative flow)

      Example

      • Entering invalid password will trigger Alternative Flow 2
    2. It triggers different error message

      Example

      • If email is to long, the message is "Email should have no more than 50 characters"
      • If email does not contain @ sign, the message is: "Invalid email address"
    3. It causes different appearance of the user interface

      Example

      • If Method of Payment is a credit card, fields to enter credit card number, expiration date and cardholder name are shown
    4. It causes different selection to be available in the drop-downs

      Example

      The customer registration screen may contain drop-downs "Country" and "State/Province". The drop-down "State/Province" is populated based on the country selected: for the US it contains all the states, for Canada all the provinces, and for other countries it is grayed-out. This creates three different options:

      • US
      • Canada
      • Any other country
    5. It is an input to some business rule

      Example

      Assuming there is a rule "If the order is placed after 6pm, and user select Overnight Shipment, the message should inform that the book will arrive after tomorrow", we may have two separate options:

      • Overnight Shipment, order placed before 6pm
      • Overnight Shipment, order placed before 6pm
    6. It is a border condition

      Example

      Since password should have at least 6 characters we should test:

      • Password with 5 characters
      • Password with 6 characters
    7. Something is changed vs. the default is used

      Example

      On the credit card payment screen the cardholder’s name is populated with the name of a person placing the order. This creates two separate options:

      • Keep default cardholder's name
      • Change cardholder's name to a different one
    8. The entry format is not clearly defined and may be differently interpreted by the user

      Example

      Phone numbers are written differently by different people:

      • Using brackets (973) 123 4567
      • Using dashes 973-123-4567
      • Plain number with spaces 973 123 4567
    9. When regular cases differ in different countries

      Example

      Credit card expiration date format may be different in the USA and in Europe

    If we are testing numbers, we may consider the following options:

    • Regular number, reasonable from the application point of view
    • Zero
    • Negative number
    • A number with two decimals
    • The biggest number that can be entered (99999999999999 - as many nines as can fit)

    How do you know what is the minimum and maximum allowed length of a field? This requirement can come from different sources. Sometimes it comes from the business analyst or a customer. For example, if we enter a Dun and Bradstreet number that identifies a company, it should always be a number containing 9 digits. It is a business requirement.

    Quite often, however, it doesn't come from the customer or the user. If you ask the customer how big the last name field should be, they might say that they don't care and ask you to make it whatever is reasonable. In this case it is a design step rather than a requirement step to decide how long the variable should be.

    In another situation, it may be suggested by the data analyst or database designer-- for example, if all other applications in the corporation store last names in 30-character long fields, your application should probably comply with this standard as well.

    Regardless of the source of the requirement, it should always be agreed upon and documented before we do the test cases.

    There is a question about where requirements like those just discussed should be documented. One place to add this kind of requirement is a paragraph called Special Requirements in the use case. Another place where you can put this kind of requirement is in the glossary or data dictionary. In addition, you can specify a separate document type where you describe all the variables from the whole application. This makes sense especially if the same variable appears on many screens in many use cases, so you could say in one document that all the names are up to 30 characters and all the addresses are up to 100 characters. However, if they are specific to a use case, it is better to add them to special requirements in that use case.


    Table 3 shows options that were identified for variables in the basic flow of the sample project:
    Step Variable Options to be tested
    B1 Website Actual URL
    B2 Email Regular Blank Min allowed (1 char) Max allowed (50 char) One more than allowed (51 char) Very long (257 char) Invalid (no @ sign)
    B2 Password Regular Blank Too short (5 char) Min allowed (6 char) Max allowed (10 char) One more than allowed (11 char) Very long (257 char)
    B3 Search string Regular Blank Min allowed (1 char) Max allowed (300 char) One more than allowed (301 char)
    B4 Selection First selection Last selection
    B5 Action selection Add to shopping cart
    B6 Action selection Proceed to checkout
    B7 Shipping address Confirm the address on file
    B8 Shipping method 5 days 3 days 2 days Overnight
    B9 Payment method Confirm the credit card on file
    B10 Action selection Place an order

    Step 3: Combine options to be tested into test cases

    In the previous step you identified all the options. In this step, you need to combine them in the sequence of test case steps.

    Figure 10 graphically illustrates the options to be tested. In each column, there is an input variable to be tested, and each row is one option: R is regular, E is empty, and then one character, 50 characters, 51, and so forth. "L" means very large, and "I" means illegal.


    Figure 10: Options to be tested for each step
    options to be tested

    The options that have the bar after them throw the user out of the basic flow: they represent some errors that are described in alternative flows. Because you are currently designing test cases only for the first scenario, you can remove them (they will be tested in some other scenario). From whatever is left, you need to create a minimum number of test cases that cover all the conditions.

    Create test cases by connecting circles, as shown in Figure 11.


    Figure 11: Combine options to create test cases
    Combine options

    To create the first test case, you can pick and connect any options. When you create the second test case, pick one of the options that was not used in the first one. Continue adding test cases until all nodes of the graph (as shown in Figure 11) are covered. Usually you'll need from 4 to 6 test cases to cover all the options that should be tested. However, some specific situations may require more.


    Allocation of test cases can also be represented in the form of a test case allocation matrix, as shown in Table 4.
    Step number Variable or selection TC1 TC2 TC3 TC4
    B1 Website Actual URL Actual URL Actual URL Actual URL
    B2 Email Regular Min allowed (1 char) Max allowed (50 char) Regular
    B2 Password Regular Min allowed (6 char) Max allowed (10 char) Min allowed (6 char)
    B3 Search string Regular Min allowed (1 char) Max allowed (300 char) Regular
    B4 Selection First selection Last selection First selection Last selection
    B5 Action selection Add to shopping cart Add to shopping cart Add to shopping cart Add to shopping cart
    B6 Action selection Proceed to checkout Proceed to checkout Proceed to checkout Proceed to checkout
    B7 Shipping address Confirm the address on file Confirm the address on file Confirm the address on file Confirm the address on file
    B8 Shipping method 5 days 3 days 2 days Overnight
    B9 Payment method Confirm the credit card on file Confirm the credit card on file Confirm the credit card on file Confirm the credit card on file
    B10 Action selection Place an order Place an order Place an order Place an order

    Table 4 describes the graph from Figure 11 in the form of a matrix where every column contains a different test case. Each row corresponds to one variable entered by a user.

    Step 4: Assign values to variables

    In this step, you replace placeholders like "a very long last name" or "a long phone number with extension" with actual values, like "Georgiamitsopolis" and "011-48 (242) 425-3456 ext. 1234" respectively.

    In this step you also split all the test cases from the matrix shown in Table 4, creating a separate table for each test case.

    For Test Case 1 of Book Order Use Case, you will have a table like that shown in Table 5. This will be a document that you give to a tester. The tester will follow the directions from columns 2 and 3, and record the results in columns 5, 6, and 7.


    Table 5: Final test case
    Step number Variable or selection Value Expected result Actual result Pass/Fail Comments
    B1 Website www.amazon.com Logon Screen
    B2 Email jsmith@hotmail.com
    B2 Password Johnsm Main Screen
    B3 Search string “Rational” List of books
    B4 Book selection First selection Book details
    B5 Action selection Add to shopping cart Cart contents
    B6 Action selection Proceed to checkout Prompt for address
    B7 Shipping address Confirm the address on file Prompt for shipping
    B8 Shipping method 5 days Prompt for payment
    B9 Payment method Confirm the credit card on file Prompt for confirmation
    B10 Action selection Place an order Order number

    Once again, RequisitePro helps you to create traceability. After producing all your test cases, you can set traceability from scenarios to test cases.

    Figure 12 shows all the scenarios: 21 scenarios derived from different combinations of alternative flows.


    Figure 12: Traceability Matrix
    matrix

    After setting the traceability between scenarios and test cases, we can create a traceability tree that shows traceability all the way from use cases to the test cases.

    There are two options. The first option -- shown in Figure 13 -- is to trace out of the use case, which shows use cases on the top level and tracing to scenarios and test cases.


    Figure 13: Traceability tree from use case
    Traceability tree

    The second method is traceability into test cases, shown in Figure 14. In this case the tree looks different: you start with test cases, and then trace back from scenarios and use cases.


    Figure 14: Traceability tree from test case
    Traceability tree

    One of the main reasons to do this traceability -- and spend time in putting it into RequisitePro -- is to know what to retest when something changes. Traceability and so-called suspect relationships, shown in Figure 15, show you which test cases might have been changed because a previous scenario and use case changed.


    Figure 15: Suspect relationships
    Suspect relationships

    Mapping to the IBM Rational Unified Process

    How do these activities map to the IBM Rational Unified Process (RUP)? Most of them take place in the Inception and Elaboration phases quite early in the process. Just after you have use cases, we can start doing scenarios and test cases. Figure 16 depicts where the activities fit in the RUP methodology.


    Figure 16: Traceability activities mapped to RUP phases
    RUP phases

    While doing scenarios and test cases, you can give feedback to use case designers and refine requirements. This can help shift some tasks early on in the process, and eventually contribute to the team's ability to finish the project sooner. Test cases are used throughout Elaboration, and almost the whole Construction phase.

    Conclusions

    The article presented a method of deriving functional test cases from use cases. Here are some benefits of this approach:

    • Test cases are derived in a more automatic way
    • Avoids duplicate testing
    • Better test coverage
    • Easier monitoring of testing progress
    • Easier work load balancing between testers
    • Easier regression testing
    • Decreases project time by moving some tasks from Construction to Elaboration
    • Contributes to early discovery of missing requirements

    The test cases that you create can be used for manual testing, as well as for automated testing using tools like IBM Rational Robot?. This method has been successfully used in multiple projects.

    Resources:

    1. Jim Heumann, "From Use Cases to Test Cases - Ensuring Quality from the Beginning." RUC 2001.
    2. Jim Heumann, "Using Use Cases to Create Test Cases." The Rational Edge, June 2001.
    3. Dean Leffingwell and Don Widrig, "Managing Software Requirements: A Unified Approach". Addison-Wesley, 1999.
    4. Dean Leffingwell and Don Widrig, "The Role of Requirements Traceability in System Development", The Rational Edge, September 2002.
    5. Rational Unified Process. Rational Software Corporation, 2001.

    Click here to view original RUC presentation of this article.



    About the author

    Director of Technology Solutions, The A Consulting Team, Inc.

  • 【zz】什么样的用例是好的用例_用例checklist(中英文对照)

    2009-01-18 22:06:19

    Here is a checklist for having well-documented,effective and useful test cases:

    以下是一个如何设计文档化、高效、有用测试用例的检查表(checklist

    Quality Attributes

    质量属性

    ·                        Accurate: tests what the descrīption says it will test.

    ·                        正确性:确保测试标题描述部分的内容正确性。

    ·                        Economical: has only the steps needed for its purpose.

    ·                        经济性:只为确定需要的目的设计相应的测试步骤。

    ·                        Repeatable, self standing: same results no matter who tests it.

    ·                        可重复性:自我一致性,即不管谁执行此用例,结果一样。

    ·                        Appropriate: for both immediate and future testers.

    ·                        适应性:既能适应短期需要,又能考虑长远需要。

    ·                        Traceable: to a requirement.

    ·                        可追踪性:用例能追踪到一个具体的需求。

    ·                        Self cleaning: returns the test environment to clean state.

    ·                        自我清理性:单个用例不会影响整个测试环境,即用例执行完了可以恢复原有的测                                                       试环境。


    Structure and testability

    结构化和可测试性

    ·                        Has a name and number

    ·                        含有规范的测试标题和编号。

    ·                        Has a stated purpose that includes what requirement is being tested

    ·                        含有一个确定的测试某一个特定需求的目的。

    ·                        Has a descrīption of the method of testing

    ·                        含有关于测试方法的描述。

    ·                        Specifies setup information - environment, data, prerequisite tests, security access

    ·                        指定条件信息-环境、数据、预置的条件测试、安全入口等。

    ·                        Has actions and expected results

    ·                        含有操作步骤和预期结果。

    ·                        States if any proofs, such as reports or screen grabs, need to be saved

    ·                        陈述任何辅助证据,例如截图报告并确保这些东西妥善保存。

    ·                        Leaves the testing environment clean

    ·                        确保测试环境的干净(即用例不会影响整个环境)。

    ·                        Uses active case language

    ·                        描述时使用主动语气结构。

    ·                        Does not exceed 15 steps

    ·                        操作步骤不要超过15步。

    ·                        Matrix does not take longer than 20 minutes to test

    ·                        确保单个用例测试执行时用时不超过20分钟。

    ·                        Automated scrīpt is commented with purpose, inputs, expected results

    ·                        自动化脚本用例添加必要的注释,比如目的、输入和期望结果。

    ·                        Setup offers alternative to prerequisite tests, if possible

    ·                        如果可能,建议提供可选择性的预置条件测试。

    ·                        Is in correct business scenario order with other tests

    ·                        用例之间的先后顺序是否跟业务流程一致,即用例在业务流程中的彼此顺序关系是否合理。


    Configuration management

    配置管理

    ·                        Employs naming and numbering conventions

    ·                        采用命名和编号规范归档。

    ·                        Saved in specified formats, file types

    ·                        保存为特定的格式,文件类型。

    ·                        Is versioned to match software under test

    ·                        用例版本是否与当前被测试软件版本一致(对应)。

    ·                        Includes test objects needed by the case, such as databases

    ·                        包含用例需要的相应测试对象,如特定数据库。

    ·                        Stored as read

    ·                        存档阅读。

    ·                        Stored with controlled access

    ·                        存档时按角色控制访问方式

    ·                        Stored where network backup operates

    ·                        当网络备份时存档。

    ·                        Archived off-site

    ·                        离线归档。

  • 【zz】如何成为伟大的测试工程师-Hallmarks

    2009-01-18 22:05:36

    Hallmarks of a Great Tester

    If you ask me, I'll tell you a great tester

    Is devious

    A great tester has a streak of deviousness.  Anyone can follow the lists of test cases that abundantly fill most books on testing.  A great tester can move beyond these lists and dream up an endless series of gnarly methods for attacking the program.  A great tester is described by developers as "sick" and "demented".

    Is curious

    A great tester is interested by everything.  A great tester wants to understand why everything works that way it does.  The best (or worst, depending on your point of view) bugs are a result of interaction between two pieces of software (applications, modules, components, whatever).  A great tester knows that understanding how something works leads directly to understanding how that something interacts with another something, which interaction leads directly to bugs.  A great tester manifests this curiosity in every aspect of life:  how does marketing work?  How are construction cranes built?  Why do they add rebar to concrete?  How are crayons made?  A great tester's curiosity knows no bounds.

    Is excited by bugs

    A great tester thinks bugs are cool.  A great tester shows up in a developer's office on a regular basis with a big grin eager to show off the latest nifty keen horridly awful bug that the tester found in the developer's code.  A great tester boasts about bugs to other testers and eagerly listens to other testers' exploits.

    Knows there are always more bugs

    A great tester knows that no application is ever bug free.  A great tester knows that an application that seems to be bug free is really full of bugs they haven't thought to look for.  A great tester is always on the lookout for new types of bugs.  A great tester views every bug found by a customer as a sign they missed an entire class of bugs.

    Stays on track

    A great tester knows that finding and isolating bugs to their root cause requires focus.  A great tester doesn't ignore bugs found along the way, but postpones investigating them until the current bug is nailed.  (And, of course, gleefully told to the corresponding developer.  And boasted about to other testers.)

    Scopes appropriately

    A great tester knows that they will not have sufficient time to run every test case they would like to run.  A great tester prioritizes and scopes their tests so that the tests most likely to find the bugs most likely to affect the customer are executed first.

    Investigates weird behavīor

    A great tester watches for odd occurrences.  Icons that display one position off from where they should and radio buttons that don't stay set may be a simple programming error, but a great tester knows that such oddities are just as likely to be but the tip of a nasty bug.  A great tester goes beyond "That's weird but that's life" to "A-ha!  That's what's going on!"

    Writes precise bugs

    A great tester takes the time to narrow a bug down to the minimum number of steps necessary to reproduce a bug.  A great tester tests around a bug to understand what the bug actually is.  A great tester writes bugs that state the bug exactly and clearly distinguish between what is proven fact and what is conjecture on the part of the tester.

    Has passion for the customer

    A great tester knows that they are the last defense against the customer receiving a product that doesn't serve the customer's needs.  A great tester understands every aspect of the customer.  A great tester understands what the customer needs to do and how the customer wants to use the product.  A great tester looks beyond the customer's needs to see how the product can revolutionize the customer's tasks.  A great tester promotes the customer's point of view throughout the product cycle, from the first nascent product vision through specifying and implementing features to cutting features and triaging bugs to product release and ongoing maintenance.  A great tester helps the rest of the product team understand the customer as well as they do.

    Is a specializing generalist

    A great tester is completely familiar with every detail of their feature.  A great tester also understands how their feature fits into and affects the entire product.  A great tester is willing to change or even cut their feature in order to make the product as a whole better.

    Picks their fights

    A great tester recognizes that fixing every bug is often not worth the resources that would be required.  A great tester balances each bug against each other bug and allows some bugs to be shipped so that other bugs can be fixed.

    Stands their ground

    A great tester knows that some bugs just have to be fixed.  A great tester is willing to be obstinate and obdurate and to ruffle feathers if necessary in order to ensure that a must fix bug is in fact fixed.  A great tester calmly shows why the bug must be fixed and convinces the rest of the team that it indeed can't be shipped.

    Can ask developers where the bathroom is

    Visitors to a foreign country are well advised to become familiar with the language and customs of the country, enough so that they can get a cab back to their hotel, ask for directions to the bathroom, and know whether shaking hands is the height of civility or completely gauche.  Likewise, a great tester is familiar with the language and customs of developers.  A great tester understands UML well enough to get the gist of UML diagrams and to draw a class or sequence diagram without making developers laugh too hard.  A great tester can write code at least as well as first year programming college students.  A great tester understands design concepts sufficiently to participate in design discussions and reviews without being asked to leave the room.

    Knows testability is just one of many concerns

    A great tester knows the only way to truly test an application is to build testability in to every aspect of the product.  A great tester analyzes the product's architecture, design, and features, and develops a plethora of ideas for ensuring the product can be tested.  A great tester, however, knows that testability is not the only factor affecting the architecture, design and features.  A great tester balances testability against the other factors and helps the team create the right mix.

    Knows when to ask for help

    A great tester takes pleasure in a challenge.  A great tester, then, enjoys banging up against a brick wall and slowly breaking through it.  Some walls are thicker than others, however, and sometimes the wall has a tester-size hole that the tester continually manages to miss.  A great tester realizes when it's time to ask for help and does so.  A great tester knows who to ask for help.  A great tester knows there isn't any shame in asking for help.

    Makes time for training

    A great tester knows that the only way to continue to be a great tester is to never stop learning.  A great tester doesn't limit this education to testing, either, but also researches programming, program management, marketing, and anything else that is remotely related to the process of creating software.

    Never stops testing

    A great tester goes beyond feature boundaries and tests throughout the product.  A great tester tests other products.  A great tester tests books, refrigerators, lights, doors...anything in any part of their life that makes them go "That's not right".


    J_9q b(Q#T190836*** Comments, questions, feedback?   Want a fun job on a great team?  Send two coding samples and an explanation of why you chose them, and of course your resume, to me at michhu at microsoft dot com.  I need testers, and my team needs a data binding developer, program managers, and a product manager.  Great coding skills required for all positions.

  • 【ZZ】测试为先/测试驱动案例分析

    2009-01-13 22:13:36

    引用地址:http://blog.chinaunix.net/u2/67893/showart_1670084.html

    作者:Richard Sun (版权所有,严禁未经许可的转载与复制)

    进行测试为先测试驱动的程序设计是确保敏捷开发顺进行的有效措施。这篇案例将为读者提供详细的开发历程,来分析测试为先测试驱动的程序设计的过程。本文的重点:

    • 简要重复叙述一下测试为先/测试驱动得好处。
    • 简要介绍一下案例中的项目。
    • 没有利用测试为先/测试驱动设计的单元代码是什么样的?
    • 没有利用测试为先/测试驱动设计的单元代码里有什么样的问题?
    • 针对单元代码设计的手动单元测试。
    • 查找出毛病后的改进代码。

    测试为先/测试驱动得好处
    传统的瀑布型软件开发是先从客户那里获得需求,然后进行纸上谈兵的设计,接着是程序源码写作构建,最后才是测试者对质量进行检评。从需求的分析到最后的测试,两者的相隔往往有好几个月。等到测试发现结构性问题时,重新设计已经成为一个无法完成的任务。设计者程序员已经无法回到几个月前推翻纸上谈兵的错误设计,重新用新的方式进行代码编写组合。测试为先/测试驱动和瀑布型软件开发不同是:

    • 测试模拟用户的使用组件的应用方式,为开发者提供解决方案;
    • 测试驱使开发者开发可以测试的部件;
    • 及早测试,尽快排除设计中的各种微小问题;
    • 测试为开发者提供质保底线,每次的部件更改都能利用测试来检测修改后质量。

    以上这些都是我以前重复过的。这些说的容易但是想像起来是比较难一点。我下面所要谈到的案例并不是教科书里的完美案例,所谓的完美案例是完全可以自动化,完全可以进行单元测试的程序组件。举个例子说,想像你要设计一个类来代表“复数”(imaginary number),这样一个类是可以完全进行自动化单元测试。这种情况只能算得上百分之五十的现实情况,在其他百分之五十的状况下,一些手动测试和一些自动化测试都是必要的。还有很多情况下,手动测试是唯一的选择。半自动和手动测试并不代表整个开发不算作测试驱动开发。测试驱动的多数人都会说手动测试和半自动化测试并不能代表团队在进行测试驱动的开法。我觉得这种说法是偏见,只要测试组和开发组能够配合,尽可能地在最早时间将用户需求确定后,让测试组开始针对用户需求,设计思路进行测试用例设计,开发和测试能同时进行,开发出的部件能够迅速进行测试,测试用例能够经常地运行确保开发的质量不受变化的影响。这就是测试为先/测试驱动的开发。

    本文的案例简介
    用来演示测试为先/测试驱动的开发,我将使用我最近设计的一个将应用程序图标加入System Tray里的类。然后在应用程序退出后,自动将图标从System Tray里删除。这样的类,你如果知道Windows系统对System Tray里的图标管理,就知道设计这么一个类的自动化测试并不简单。我觉得这种和图形界面打交道的类,也没有必要100%地进行自动化测试。所以我对这个类的测试驱动采取手工测试为主的测试,以测试者甚至开法者本身用用户的需求,先用例程作为基础,来设计图标管理类的单元测试。

    案例的用户需求
    我是这个类的唯一用户,对于我要设计的程序,我的使用是很简单的。下面的列表就是我的需求:

    • System Tray里的应用图标的数据管理和图标的加入删除都由类对象来进行;
    • 类对象能够设定视窗柄;
    • 类对象能够设定图标的独特ID;
    • 类对象能够设定图标对系统信息处理的消息ID;
    • 类对象能够设定图标在鼠标指向后能够显示有关程序的信息(程序的名称,设计公司和其他信息);
    • 类对象必须在调用者的指示下将图标放入System Tray。
    • 类对象必须处理让调试者能够删除System Tray里的图标。
    • 类对象在自我摧毁的时候自动删除System Tray里的图标。

    这些用户需求就是我要设计使用案例,在敏捷中,这些案例就是一个个故事。我在设计每一个故事的编码之前就先设计一个测试案例。每个测试案例都在设计完成之前会运行失败。设计完成后,这些测试案例才能顺利运行。

    为了调试图标的加入和删除都能正确运行,我决定使用一个简单的Win32视窗程序来作为我的单元测试温床,我的测试是手动测试。我的目标是用单元测试来尽可能地覆盖我设计的代码面积。第一步我设计了以下的单元测试:

    void UnitTestCase0(HWND hWnd, HICON handleIcon)
    {
       // a normal core functionality test.
       gSysTrayIcon.SetTrayIconID(11200);
       gSysTrayIcon.SetNotifyWindow(hWnd);
       gSysTrayIcon.SetTrayIcon(handleIcon);
       gSysTrayIcon.SetTrayIconTip(_T("SysTrayIcon"));
       gSysTrayIcon.SetTrayIconWmMsg(WM_TRAYICON_MSGS);
       if (!gSysTrayIcon.AddIconToSysTray())
       {
          ::MessageBox(hWnd, _T("Unable to add Icon to System Tray."), _T("Error:"), MB_OK);
          return;
       }
    }
    这是一个很简单的函数,我假设我有一个全局变量叫gSysTrayIcon。它是一个类对象;它有至少六个函数;它的五个函数是数据设定函数;它的最后一个函数是让调用者告诉它把图像加入System Tray。根据我自己设计的单元测试案例,我设计了以下的类:
    #ifndef SYS_TRAY_ICON_H_
    #define SYS_TRAY_ICON_H_

    #include "shellapi.h"

    class SysTrayIcon
    {
    private:
       NOTIFYICONDATA niData;

    public:
       SysTrayIcon();
       ~SysTrayIcon();

       void SetTrayIconID(UINT iconID);
       void SetNotifyWindow(HWND hWnd);
       void SetTrayIcon(HICON iconHandle);
       void SetTrayIconTip(LPCTSTR szMsg);
       void SetTrayIconWmMsg(UINT wmMsg);
       
       BOOL AddIconToSysTray();
       BOOL DeleteIconFromSysTray();

    };

    #endif
    我的类成员设计如下,这里面有很多我无意中犯下的错误,也有我故意设置的错误,后面我用单元测试一点点地查找出一些常见的问题。为了顺利通过我上面的单元测试,首先看看我的设计初稿:
    #include "StdAfx.h"

    #include "SysTrayIcon.h"
    #include <string.h>


    SysTrayIcon::SysTrayIcon()
    {
       ZeroMemory(&niData, sizeof(NOTIFYICONDATA));
       niData.cbSize = (DWORD)sizeof(NOTIFYICONDATA);
       niData.uFlags = NIF_ICON|NIF_MESSAGE|NIF_TIP;
    }

    SysTrayIcon::~SysTrayIcon()
    {
       DeleteIconFromSysTray();
    }

    void SysTrayIcon::SetTrayIconID(UINT iconID)
    {
       niData.uID = iconID;
    }

    void SysTrayIcon::SetNotifyWindow(HWND hWnd)
    {
       niData.hWnd = hWnd;
    }

    void SysTrayIcon::SetTrayIcon(HICON iconHandle)
    {
       niData.hIcon = iconHandle;
    }

    void SysTrayIcon::SetTrayIconWmMsg(UINT wmMsg)
    {
       niData.uCallbackMessage = wmMsg;
    }

    void SysTrayIcon::SetTrayIconTip(LPCTSTR szMsg)
    {
       _tcscpy(niData.szTip, szMsg);
    }

    BOOL SysTrayIcon::AddIconToSysTray()
    {
       Shell_NotifyIcon(NIM_ADD, &niData);
       return TRUE;
    }

    BOOL SysTrayIcon::DeleteIconFromSysTray()
    {
       return Shell_NotifyIcon(NIM_DELETE, &niData);
    }

    敏捷的宗旨是,在最短的时间内为客户提供完整的设计,让客户能够看到期待的价值,让客户能迅速反馈,并把反馈意见转变为设计改进。我以上的代码给我自己提供一个可以测试的机会。我用我的测试案例来实践我的设计,测试程序是一个SDI视窗程序。程序运行开始先把一个图标放入System Tray,然后,用户可以按在程序的缩小按钮上,程序会消失,但是System Tray里的程序图标。用户用鼠标左键双击System Tray里的程序图标,程序视窗会重新出现在桌面上。用户把鼠标光标移到System Tray里的程序图标上,一秒钟后就会一个提示标题出现,显示程序的名称。当我关闭程序视窗,视窗消失,System Tray里的程序图标也一并消失。这就是我的第一个测试。这个测试案例运行,不会出现任何问题。

    我写的第一个案例是开发者通常会做的测试,一个简单的案例保证设计到达最基本的用户需求。作为认真的开发者,和有专业意识的QA,这样简单的测试根本不够。各种各样的边界问题会通过设计的空隙造成程序运行异常。我就设计了另一个测试边际问题的测试,代码如下:

    void UnitTestCase1(HWND hWnd, HICON handleIcon)
    {
       gSysTrayIcon.AddIconToSysTray();
    }
    这个案例其实很简单。假设我建立了一个gSysTrayIcon,但是我不对其做任何初始化设定。那会出现什么问题?我运行一下这个案例,结果我马上发现了两个问题,一是ystem Tray里的程序图标是一个空格。接着我把标光标移到System Tray里的程序图标的位置上,马上那个位置就被其他图标给占据了。这些行为都是不对的。仔细看看我的设计,我在调用AddIconToSysTray()之前,没有调用一些重要的对象处理,这三个:SetNotifyWindow(HWND hWnd),SetTrayIcon(HICON iconHandle),和SetTrayIconWmMsg(UINT wmMsg)。所以我的案例会出现异常。在现实中,测试或者开发者自己都能运用自己的经验和知识来判断这些边界的问题,然后用单元测试来鉴别设计在处理这些问题的能力。现在我已经了解到我的设计有毛病,就要想办法解决。首先看看SetNotifyWindow(HWND hWnd),这个函数的边界是HWND参数不能是NULL(或是0)。如果这种情况出现,我应该如何处理?我的解决是用扔出异常。我就要为以上的单元测试进行一点改变。下面是我的修改:
    void UnitTestCase1(HWND hWnd, HICON handleIcon)
    {
       // without any initailization.
       try
       {
          if (!gSysTrayIcon.AddIconToSysTray())
          {
             ::MessageBox(hWnd, _T("Unable to add Icon to System Tray."), _T("Error:"), MB_OK);
             return;
          }
       }
       catch(const AppException& e)
       {
          ::MessageBox(hWnd, e.ToString(), _T("Error:"), MB_OK);
       }
    }
    然后我再更改我的设计,促使我的设计在处理错误输入时会抛出异常:
    void SysTrayIcon::SetNotifyWindow(HWND hWnd)
    {
       if (hWnd == NULL)
       {
          // throw excepttion
          throw AppException(_T("The handle of the window is invalid."));
       }
       niData.hWnd = hWnd;
    }
    我再运行一下我的案例,结果还是不行,原来的毛病一点都没有改变。我再看看我的测试案例,结果发现我的修改并没有解除我所面对的问题。在我调用AddIconToSysTray()之前,我根本没有调用SetNotifyWindow,所以我的测试案例根本没有解决我的问题。我要修改的是AddIconToSysTray()。下面是我的修改:
    BOOL SysTrayIcon::AddIconToSysTray()
    {
       if (niData.hWnd == NULL)
       {
          throw AppException(_T("The handle of the window is invalid."));
       }
       else if (niData.hIcon == NULL)
       {
          throw AppException(_T("The handle of the icon is invalid."));
       }
       else if (niData.uCallbackMessage == 0)
       {
          throw AppException(_T("The callback message ID is invalid."));
       }


       BOOL retVal = Shell_NotifyIcon(NIM_ADD, &niData);
       return retVal;
    }
    修改后运行一下,我的程序输出了异常信息提示,当我选择提示的“OK”按钮后,程序没有在System Tray里添加程序图标。我为了测试剩下两个判断分支,设计了两个案例里,加上上一个案例我有三个:
    void UnitTestCase1(HWND hWnd, HICON handleIcon)
    {
       // without any initailization.
       try
       {
          if (!gSysTrayIcon.AddIconToSysTray())
          {
             ::MessageBox(hWnd, _T("Unable to add Icon to System Tray."), _T("Error:"), MB_OK);
             return;
          }
       }
       catch(const AppException& e)
       {
          ::MessageBox(hWnd, e.ToString(), _T("Error:"), MB_OK);
       }
    }

    void UnitTestCase2(HWND hWnd, HICON handleIcon)
    {
       // without any initailization on ICON handle.
       try
       {
          gSysTrayIcon.SetNotifyWindow(hWnd);
          if (!gSysTrayIcon.AddIconToSysTray())
          {
             ::MessageBox(hWnd, _T("Unable to add Icon to System Tray."), _T("Error:"), MB_OK);
             return;
          }
       }
       catch(const AppException& e)
       {
          ::MessageBox(hWnd, e.ToString(), _T("Error:"), MB_OK);
       }
    }

    void UnitTestCase3(HWND hWnd, HICON handleIcon)
    {
       // without any initailization for message callback ID.
       try
       {
          gSysTrayIcon.SetNotifyWindow(hWnd);
          gSysTrayIcon.SetTrayIcon(handleIcon);
          if (!gSysTrayIcon.AddIconToSysTray())
          {
             ::MessageBox(hWnd, _T("Unable to add Icon to System Tray."), _T("Error:"), MB_OK);
             return;
          }
       }
       catch(const AppException& e)
       {
          ::MessageBox(hWnd, e.ToString(), _T("Error:"), MB_OK);
       }
    }

    还有什么可以测试?首先,NOTIFYICONDATA::uID的值有没有限度?我们可以试试0和-1(-1应该是32位正值整数的最大值),对程序的影响也不大:

    void UnitTestCase4(HWND hWnd, HICON handleIcon)
    {
       // What happen to have Icon ID to be 0.
       gSysTrayIcon.SetTrayIconID(0);
       gSysTrayIcon.SetNotifyWindow(hWnd);
       gSysTrayIcon.SetTrayIcon(handleIcon);
       gSysTrayIcon.SetTrayIconTip(_T("SysTrayIcon"));
       gSysTrayIcon.SetTrayIconWmMsg(WM_TRAYICON_MSGS);
       if (!gSysTrayIcon.AddIconToSysTray())
       {
          ::MessageBox(hWnd, _T("Unable to add Icon to System Tray."), _T("Error:"), MB_OK);
          return;
       }
    }

    void UnitTestCase5(HWND hWnd, HICON handleIcon)
    {
       // What happen to have Icon ID to be 0.
       gSysTrayIcon.SetTrayIconID(-1);
       gSysTrayIcon.SetNotifyWindow(hWnd);
       gSysTrayIcon.SetTrayIcon(handleIcon);
       gSysTrayIcon.SetTrayIconTip(_T("SysTrayIcon"));
       gSysTrayIcon.SetTrayIconWmMsg(WM_TRAYICON_MSGS);
       if (!gSysTrayIcon.AddIconToSysTray())
       {
          ::MessageBox(hWnd, _T("Unable to add Icon to System Tray."), _T("Error:"), MB_OK);
          return;
       }
    }
    最后是鼠标滑到System Tray的图标时要显示的字符串。NOTIFYICONDATA::szTip最大容量应该是64个字节。我用以下的测试案例来测试我的设计:
    void UnitTestCase6(HWND hWnd, HICON handleIcon)
    {
       // What happen to have Icon ID to be 0.
       gSysTrayIcon.SetTrayIconID(11200);
       gSysTrayIcon.SetNotifyWindow(hWnd);
       gSysTrayIcon.SetTrayIcon(handleIcon);
       gSysTrayIcon.SetTrayIconTip(_T("kdhfhdfjhdsfhdsjfhdsjhfjdshfjdshfjdshjfhdsjfsjdhfjdshjs" \
          "hdfhdsfjhdsjfhsdjfhdshfhdsjfhdsfhsdjhfsdjhfjdshfjhdsfjhdsfhsdhfsjdhfjshdjfhdsfjhsdj" \
          "dfhhdsjfhdjshfjdhfjdhfdhfjhdsfjhdjhfjdsfhjsadhhdskfhadskfhdskjfhkdsjfhkdsjhfkjadshf" \
          "kjasdhkfhadskfhdskjhfkadsjhfkfashkfhaskdhfkadsfhkdsafhkdsjhfkdsahfkdshkjfhdaskhkads" \
          "hfkdshfkjdhfkjdhfkdshfiohirhu'prhpiurhf"));
       gSysTrayIcon.SetTrayIconWmMsg(WM_TRAYICON_MSGS);
       if (!gSysTrayIcon.AddIconToSysTray())
       {
          ::MessageBox(hWnd, _T("Unable to add Icon to System Tray."), _T("Error:"), MB_OK);
          return;
       }
    }
    让我吃惊的是,程序并没有出现任何异常。只是64字节以后的字节都被忽略了。这种现象并不代表我的设计没有问题,我的原有设计是这样的:
    void SysTrayIcon::SetTrayIconTip(LPCTSTR szMsg)
    {
       _tcscpy(niData.szTip, szMsg);
    }
    _tcscpy是个很不安全的函数,它不检测接受缓冲的容量,所以,原缓冲的容量可以比接受缓冲的容量大,这样的代码很容易出现buffer overflow。所以,我们必须在字符串拷贝的时候检测两个缓冲的容量大小,接受缓冲的容量必须比原缓冲的容量要大。但是在我们现在所面对的情况下,这种情况我们采用另一种解决方式比较容易,就是保证字符串的拷贝不超过一定的数量。而且我们采用一个比较安全的拷贝函数。首先我改变了原有的测试用例:
    void UnitTestCase6(HWND hWnd, HICON handleIcon)
    {
       // What happen to have Icon ID to be 0.
       gSysTrayIcon.SetTrayIconID(11200);
       gSysTrayIcon.SetNotifyWindow(hWnd);
       gSysTrayIcon.SetTrayIcon(handleIcon);
       try
       {
          gSysTrayIcon.SetTrayIconTip(_T("kdhfhdfjhdsfhdsjfhdsjhfjdshfjdshfjdshjfhdsjfsjdhfjdshjs" \
             "hdfhdsfjhdsjfhsdjfhdshfhdsjfhdsfhsdjhfsdjhfjdshfjhdsfjhdsfhsdhfsjdhfjshdjfhdsfjhsdj" \
             "dfhhdsjfhdjshfjdhfjdhfdhfjhdsfjhdjhfjdsfhjsadhhdskfhadskfhdskjfhkdsjfhkdsjhfkjadshf" \
             "kjasdhkfhadskfhdskjhfkadsjhfkfashkfhaskdhfkadsfhkdsafhkdsjhfkdsahfkdshkjfhdaskhkads" \
             "hfkdshfkjdhfkjdhfkdshfiohirhu'prhpiurhf"));
       }
       catch(const AppException& e)
       {
          ::MessageBox(hWnd, e.ToString(), _T("Error:"), MB_OK);
       }
       gSysTrayIcon.SetTrayIconWmMsg(WM_TRAYICON_MSGS);
       if (!gSysTrayIcon.AddIconToSysTray())
       {
          ::MessageBox(hWnd, _T("Unable to add Icon to System Tray."), _T("Error:"), MB_OK);
          return;
       }
    }
    用我原来的设计来测试以上的测试用例,什么都不会发生。说明我的原有设计要改变一些:
    void SysTrayIcon::SetTrayIconTip(LPCTSTR szMsg)
    {
       HRESULT hr = StringCchCopyN(niData.szTip, 63, szMsg, 63);
       if (FAILED(hr))
       {
          // throw exception
          throw AppException(_T("Invalid tip string"));
       }
    }
    使用更新的代码,再运行我的测试用例,让我意外的是,测试用例竟然报道程序出错。说明我的更改是正确的。我的希望是如果原缓冲的容量太大,程序只拷贝接受缓冲容量所能承受的字符串量。这样我的测试用例应该不会报错。是什么造成这个问题?仔细查询一下有关StringCchCopyN()的说明,就发现我的问题在哪里了。如果原缓冲的容量太大,程序只拷贝接受缓冲容量所能承受的字符串量,StringCchCopyN()的返回值是STRSAFE_E_INSUFFICIENT_BUFFER,而不是S_OK。所以我的源代码必须进行一定的变化:
    void SysTrayIcon::SetTrayIconTip(LPCTSTR szMsg)
    {
       HRESULT hr = StringCchCopyN(niData.szTip, 63, szMsg, 63);
       if (FAILED(hr))
       {
          if (hr != STRSAFE_E_INSUFFICIENT_BUFFER)
          {
             // throw exception
             throw AppException(_T("Invalid tip string"));
          }
       }
    }
    再次运行上面的测试用例,我不再看见原有的错误消息。你可以看出我到现在,一直在使用我的测试和我对我要设计的代码的结构的熟悉来指导我的设计。白盒测试不仅能提供我所需要的程序质量检测,同时也指引我的设计方向。再试试几个其他的案例,这就是一个典型的案例,如果我用NULL作为原缓冲,那会出现什么问题:
    void UnitTestCase8(HWND hWnd, HICON handleIcon)
    {
       // What happen to have Icon ID to be 0.
       gSysTrayIcon.SetTrayIconID(11200);
       gSysTrayIcon.SetNotifyWindow(hWnd);
       gSysTrayIcon.SetTrayIcon(handleIcon);
       gSysTrayIcon.SetTrayIconTip(NULL);
       gSysTrayIcon.SetTrayIconWmMsg(WM_TRAYICON_MSGS);
       if (!gSysTrayIcon.AddIconToSysTray())
       {
          ::MessageBox(hWnd, _T("Unable to add Icon to System Tray."), _T("Error:"), MB_OK);
          return;
       }
    }
    试试之后的结果是整个测试程序垮掉。我们又发现了一个问题。我先更改一下我的测试用例:
    void UnitTestCase8(HWND hWnd, HICON handleIcon)
    {
       // What happen to have Icon ID to be 0.
       gSysTrayIcon.SetTrayIconID(11200);
       gSysTrayIcon.SetNotifyWindow(hWnd);
       gSysTrayIcon.SetTrayIcon(handleIcon);
       try
       {
          gSysTrayIcon.SetTrayIconTip(NULL);
       }
       catch(const AppException& e)
       {
          ::MessageBox(hWnd, e.ToString(), _T("Error:"), MB_OK);
       }
       gSysTrayIcon.SetTrayIconWmMsg(WM_TRAYICON_MSGS);
       if (!gSysTrayIcon.AddIconToSysTray())
       {
          ::MessageBox(hWnd, _T("Unable to add Icon to System Tray."), _T("Error:"), MB_OK);
          return;
       }
    }
    然后再修改我的设计:
    void SysTrayIcon::SetTrayIconTip(LPCTSTR szMsg)
    {
       if (szMsg == NULL)
       {
          throw AppException(_T("Tip string pointer cannot be NULL."));
       }

       HRESULT hr = StringCchCopyN(niData.szTip, 63, szMsg, 63);
       if (FAILED(hr))
       {
          if (hr != STRSAFE_E_INSUFFICIENT_BUFFER)
          {
             // throw exception
             throw AppException(_T("Invalid tip string"));
          }
       }
    }
    运行后看到我所希望看到的错误信息。这样我又防止了一个毛病漏到测试组的手上。

    最后在总结之前,说说图标柄输入如果是NULL的情况,我们可以进行一些改进,如果用户在调用SysTrayIcon::SetTrayIcon(HICON iconHandle)输入非法值NULL,解决方法不一定是要直接抛出异常。我们可以让系统帮助设定一个默认图标柄。首先我们再Copy & paste制作一个新的单元测试:

    void UnitTestCase9(HWND hWnd, HICON handleIcon)
    {
       // What happen to have Icon ID to be 0.
       gSysTrayIcon.SetTrayIconID(15923);
       gSysTrayIcon.SetNotifyWindow(hWnd);
       gSysTrayIcon.SetTrayIcon(NULL);
       gSysTrayIcon.SetTrayIconTip(_T("SysTrayIcon"));
       gSysTrayIcon.SetTrayIconWmMsg(WM_TRAYICON_MSGS);
       if (!gSysTrayIcon.AddIconToSysTray())
       {
          ::MessageBox(hWnd, _T("Unable to add Icon to System Tray."), _T("Error:"), MB_OK);
          return;
       }
    }
    运行以上的单元测试,我马上看见原有的异常被抛出,表明我的测试失败了。我的意图是如果图标柄的设置是NULL,那么,我就让系统找到一个默认的图标,并用它作为程序在SystemTray里的图标。我对代码进行以下修改:
    BOOL SysTrayIcon::AddIconToSysTray()
    {
       if (niData.hWnd == NULL)
       {
          throw AppException(_T("The handle of the window is invalid."));
       }
       else if (niData.hIcon == NULL)
       {
          HICON defaultIcoHdl = ::LoadIcon(NULL, MAKEINTRESOURCE(IDI_APPLICATION));
          if (defaultIcoHdl != NULL)
          {
             niData.hIcon = defaultIcoHdl;
          }
          else
          {
             throw AppException(_T("The handle of the icon is invalid."));
          }
       }
       else if (niData.uCallbackMessage == 0)
       {
          throw AppException(_T("The callback message ID is invalid."));
       }
       
       BOOL retVal = Shell_NotifyIcon(NIM_ADD, &niData);
       return retVal;
    }
    运行我的单元测试,结果就没有出错,但是我的第二个单元测试原来设计为,如果图标柄的设置是NULL,就抛出异常,现在这个使用案例已经没有意义了,所以第二个单元测试就可以被删掉了。

    总结
    我的这篇文章完整(并非完美)地展示了一个简单的测试为先,测试驱动的开发案例。设计过程中,我用测试案例来主导我的设计,只有最简单的设计来实现我的需求。我只在更改错误的情况下增加功能,而不是随便凭着自己的想像来增加我不需要的功能。我在测试中找出了不少我问题,而且都是在开发过程中发现的问题,也就是说在开法的最基本阶段测试就开始进行了,而且很多问题在开发初期就被检测出来并修改好,尽早测试,可以为后面的开发减少很多不必要的困难。

    我这个案例不是一个完美的案例。现实中,能够完美展示单元测试的好处的完美案例是不存在的,所有我见过的完美案例都是在教科书里出现的。这些案例有时给人的感觉是不真实,也展示出这些案例的局限性。我的案例在很大程度上依赖手动化测试,这有时是违反敏捷开发的用意的。在敏捷开发中,自动化单元测试和接受性测试是非常重要的。我敢说很多进行敏捷开发的专家都会说我的案例算不上敏捷开发。我对这一观点只同意到一定的程度,软件开发是个人与人互动的社会活动,开发者在手动测试上所花时间过多的话,就要将这样的任务推给QA,有能力的QA应该可以和开发者一起考虑什么样的接受性测试和单元测试能够帮助整个团队提交更好的产品。

    我这个案例同时也说明,用户界面的设计也能通过单元测试来进行。这样的测试不仅仅是开发者自己进行,有能力的QA可以和开发者一起进行接受性测试,QA可以享受一下开发的乐趣。同时可以和开发者一起合作进行质量监控,这样双方不会因为竞争而感受双方的相互威胁,QA帮助开发者及早进行测试,从而建立友好的合作关系。

    附录A:测试的类头文件

    #pragma once

    #include "shellapi.h"

    class SysTrayIcon
    {
    private:
       NOTIFYICONDATA niData;

    public:
       SysTrayIcon();
       ~SysTrayIcon();

       void SetTrayIconID(UINT iconID);
       void SetNotifyWindow(HWND hWnd);
       void SetTrayIcon(HICON iconHandle);
       void SetTrayIconTip(LPCTSTR szMsg);
       void SetTrayIconWmMsg(UINT wmMsg);
       
       BOOL AddIconToSysTray();
       BOOL DeleteIconFromSysTray();

    };

    附录B:测试的类源码
    #include "StdAfx.h"

    #include "SysTrayIcon.h"
    #include "ExceptionBase.h"
    #include <strsafe.h>

    SysTrayIcon::SysTrayIcon()
    {
       ZeroMemory(&niData, sizeof(NOTIFYICONDATA));
       niData.cbSize = (DWORD)sizeof(NOTIFYICONDATA);
       niData.uFlags = NIF_ICON|NIF_MESSAGE|NIF_TIP;
    }

    SysTrayIcon::~SysTrayIcon()
    {
       DeleteIconFromSysTray();
    }

    void SysTrayIcon::SetTrayIconID(UINT iconID)
    {
       niData.uID = iconID;
    }

    void SysTrayIcon::SetNotifyWindow(HWND hWnd)
    {
       niData.hWnd = hWnd;
    }

    void SysTrayIcon::SetTrayIcon(HICON iconHandle)
    {
       niData.hIcon = iconHandle;
    }

    void SysTrayIcon::SetTrayIconWmMsg(UINT wmMsg)
    {
       niData.uCallbackMessage = wmMsg;
    }

    void SysTrayIcon::SetTrayIconTip(LPCTSTR szMsg)
    {
       if (szMsg == NULL)
       {
          throw AppException(_T("Tip string pointer cannot be NULL."));
       }

       HRESULT hr = StringCchCopyN(niData.szTip, 63, szMsg, 63);
       if (FAILED(hr))
       {
          if (hr != STRSAFE_E_INSUFFICIENT_BUFFER)
          {
             // throw exception
             throw AppException(_T("Invalid tip string"));
          }
       }
    }

    BOOL SysTrayIcon::AddIconToSysTray()
    {
       if (niData.hWnd == NULL)
       {
          throw AppException(_T("The handle of the window is invalid."));
       }
       else if (niData.hIcon == NULL)
       {
          HICON defaultIcoHdl = ::LoadIcon(NULL, MAKEINTRESOURCE(IDI_APPLICATION));
          if (defaultIcoHdl != NULL)
          {
             niData.hIcon = defaultIcoHdl;
          }
          else
          {
             throw AppException(_T("The handle of the icon is invalid."));
          }
       }
       else if (niData.uCallbackMessage == 0)
       {
          throw AppException(_T("The callback message ID is invalid."));
       }
       
       BOOL retVal = Shell_NotifyIcon(NIM_ADD, &niData);
       return retVal;
    }

    BOOL SysTrayIcon::DeleteIconFromSysTray()
    {
       return Shell_NotifyIcon(NIM_DELETE, &niData);
    }

    附录C:单元测试调试程序
    // SysTrayIcon.cpp : Defines the entry point for the application.
    //

    #include "stdafx.h"
    #include <stdio.h>
    #include "SysTrayIconTest.h"
    #include "..\\SysTrayIcon.h"
    #include "..\\ExceptionBase.h"
    #define MAX_LOADSTRING 100

    // Global Variables:
    HINSTANCE hInst;                        // current instance
    TCHAR szTitle[MAX_LOADSTRING];               // The title bar text
    TCHAR szWindowClass[MAX_LOADSTRING];         // the main window class name
    SysTrayIcon gSysTrayIcon;
    int gUnitTestIdx = -1;


    #define WM_TRAYICON_MSGS 10025

    // Forward declarations of functions included in this code module:
    ATOM            MyRegisterClass(HINSTANCE hInstance);
    BOOL            InitInstance(HINSTANCE, int);
    LRESULT CALLBACK   WndProc(HWND, UINT, WPARAM, LPARAM);
    LRESULT CALLBACK   About(HWND, UINT, WPARAM, LPARAM);

    void UnitTestCase0(HWND hWnd, HICON handleIcon)
    {
       // a normal core functionality test.
       gSysTrayIcon.SetTrayIconID(11200);
       gSysTrayIcon.SetNotifyWindow(hWnd);
       gSysTrayIcon.SetTrayIcon(handleIcon);
       gSysTrayIcon.SetTrayIconTip(_T("SysTrayIcon"));
       gSysTrayIcon.SetTrayIconWmMsg(WM_TRAYICON_MSGS);
       if (!gSysTrayIcon.AddIconToSysTray())
       {
          ::MessageBox(hWnd, _T("Unable to add Icon to System Tray."), _T("Error:"), MB_OK);
          return;
       }
    }

    void UnitTestCase1(HWND hWnd, HICON handleIcon)
    {
       // without any initailization.
       try
       {
          if (!gSysTrayIcon.AddIconToSysTray())
          {
             ::MessageBox(hWnd, _T("Unable to add Icon to System Tray."), _T("Error:"), MB_OK);
             return;
          }
       }
       catch(const AppException& e)
       {
          ::MessageBox(hWnd, e.ToString(), _T("Error:"), MB_OK);
       }
    }

    // No longer vallid.
    //void UnitTestCase2(HWND hWnd, HICON handleIcon)
    //{
    //   // without any initailization.
    //   try
    //   {
    //      gSysTrayIcon.SetNotifyWindow(hWnd);
    //      if (!gSysTrayIcon.AddIconToSysTray())
    //      {
    //         ::MessageBox(hWnd, _T("Unable to add Icon to System Tray."), _T("Error:"), MB_OK);
    //         return;
    //      }
    //   }
    //   catch(const AppException& e)
    //   {
    //      ::MessageBox(hWnd, e.ToString(), _T("Error:"), MB_OK);
    //   }
    //}

    void UnitTestCase3(HWND hWnd, HICON handleIcon)
    {
       // without any initailization.
       try
       {
          gSysTrayIcon.SetNotifyWindow(hWnd);
          gSysTrayIcon.SetTrayIcon(handleIcon);
          if (!gSysTrayIcon.AddIconToSysTray())
          {
             ::MessageBox(hWnd, _T("Unable to add Icon to System Tray."), _T("Error:"), MB_OK);
             return;
          }
       }
       catch(const AppException& e)
       {
          ::MessageBox(hWnd, e.ToString(), _T("Error:"), MB_OK);
       }
    }

    void UnitTestCase4(HWND hWnd, HICON handleIcon)
    {
       // What happen to have Icon ID to be 0.
       gSysTrayIcon.SetTrayIconID(0);
       gSysTrayIcon.SetNotifyWindow(hWnd);
       gSysTrayIcon.SetTrayIcon(handleIcon);
       gSysTrayIcon.SetTrayIconTip(_T("SysTrayIcon"));
       gSysTrayIcon.SetTrayIconWmMsg(WM_TRAYICON_MSGS);
       if (!gSysTrayIcon.AddIconToSysTray())
       {
          ::MessageBox(hWnd, _T("Unable to add Icon to System Tray."), _T("Error:"), MB_OK);
          return;
       }
    }

    void UnitTestCase5(HWND hWnd, HICON handleIcon)
    {
       // What happen to have Icon ID to be 0.
       gSysTrayIcon.SetTrayIconID(-1);
       gSysTrayIcon.SetNotifyWindow(hWnd);
       gSysTrayIcon.SetTrayIcon(handleIcon);
       gSysTrayIcon.SetTrayIconTip(_T("SysTrayIcon"));
       gSysTrayIcon.SetTrayIconWmMsg(WM_TRAYICON_MSGS);
       if (!gSysTrayIcon.AddIconToSysTray())
       {
          ::MessageBox(hWnd, _T("Unable to add Icon to System Tray."), _T("Error:"), MB_OK);
          return;
       }
    }

    void UnitTestCase6(HWND hWnd, HICON handleIcon)
    {
       // What happen to have Icon ID to be 0.
       gSysTrayIcon.SetTrayIconID(11200);
       gSysTrayIcon.SetNotifyWindow(hWnd);
       gSysTrayIcon.SetTrayIcon(handleIcon);
       try
       {
          gSysTrayIcon.SetTrayIconTip(_T("kdhfhdfjhdsfhdsjfhdsjhfjdshfjdshfjdshjfhdsjfsjdhfjdshjs" \
             "hdfhdsfjhdsjfhsdjfhdshfhdsjfhdsfhsdjhfsdjhfjdshfjhdsfjhdsfhsdhfsjdhfjshdjfhdsfjhsdj" \
             "dfhhdsjfhdjshfjdhfjdhfdhfjhdsfjhdjhfjdsfhjsadhhdskfhadskfhdskjfhkdsjfhkdsjhfkjadshf" \
             "kjasdhkfhadskfhdskjhfkadsjhfkfashkfhaskdhfkadsfhkdsafhkdsjhfkdsahfkdshkjfhdaskhkads" \
             "hfkdshfkjdhfkjdhfkdshfiohirhu'prhpiurhf"));
       }
       catch(const AppException& e)
       {
          ::MessageBox(hWnd, e.ToString(), _T("Error:"), MB_OK);
       }
       gSysTrayIcon.SetTrayIconWmMsg(WM_TRAYICON_MSGS);
       if (!gSysTrayIcon.AddIconToSysTray())
       {
          ::MessageBox(hWnd, _T("Unable to add Icon to System Tray."), _T("Error:"), MB_OK);
          return;
       }
    }

    void UnitTestCase7(HWND hWnd, HICON handleIcon)
    {
       // What happen to have Icon ID to be 0.
       gSysTrayIcon.SetTrayIconID(11200);
       gSysTrayIcon.SetNotifyWindow(hWnd);
       gSysTrayIcon.SetTrayIcon(handleIcon);
       gSysTrayIcon.SetTrayIconTip(_T(""));
       gSysTrayIcon.SetTrayIconWmMsg(WM_TRAYICON_MSGS);
       if (!gSysTrayIcon.AddIconToSysTray())
       {
          ::MessageBox(hWnd, _T("Unable to add Icon to System Tray."), _T("Error:"), MB_OK);
          return;
       }
    }

    void UnitTestCase8(HWND hWnd, HICON handleIcon)
    {
       // What happen to have Icon ID to be 0.
       gSysTrayIcon.SetTrayIconID(11200);
       gSysTrayIcon.SetNotifyWindow(hWnd);
       gSysTrayIcon.SetTrayIcon(handleIcon);
       try
       {
          gSysTrayIcon.SetTrayIconTip(NULL);
       }
       catch(const AppException& e)
       {
          ::MessageBox(hWnd, e.ToString(), _T("Error:"), MB_OK);
       }
       gSysTrayIcon.SetTrayIconWmMsg(WM_TRAYICON_MSGS);
       if (!gSysTrayIcon.AddIconToSysTray())
       {
          ::MessageBox(hWnd, _T("Unable to add Icon to System Tray."), _T("Error:"), MB_OK);
          return;
       }
    }

    void UnitTestCase9(HWND hWnd, HICON handleIcon)
    {
       // What happen to have Icon ID to be 0.
       gSysTrayIcon.SetTrayIconID(15923);
       gSysTrayIcon.SetNotifyWindow(hWnd);
       gSysTrayIcon.SetTrayIcon(NULL);
       gSysTrayIcon.SetTrayIconTip(_T("SysTrayIcon"));
       gSysTrayIcon.SetTrayIconWmMsg(WM_TRAYICON_MSGS);
       if (!gSysTrayIcon.AddIconToSysTray())
       {
          ::MessageBox(hWnd, _T("Unable to add Icon to System Tray."), _T("Error:"), MB_OK);
          return;
       }
    }

    int APIENTRY _tWinMain(HINSTANCE hInstance,
                         HINSTANCE hPrevInstance,
                         LPTSTR    lpCmdLine,
                         int       nCmdShow)
    {
        // TODO: Place code here.
       MSG msg;
       HACCEL hAccelTable;

       // Initialize global strings
       LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
       LoadString(hInstance, IDC_SYSTRAYICON, szWindowClass, MAX_LOADSTRING);
       MyRegisterClass(hInstance);

       gUnitTestIdx = _tstoi(lpCmdLine);

       // Perform application initialization:
       if (!InitInstance (hInstance, nCmdShow)) 
       {
          return FALSE;
       }

       hAccelTable = LoadAccelerators(hInstance, (LPCTSTR)IDC_SYSTRAYICON);

       // Main message loop:
       while (GetMessage(&msg, NULL, 0, 0)) 
       {
          if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) 
          {
             TranslateMessage(&msg);
             DispatchMessage(&msg);
         &
  • 转:微软公司的测试职能及面试考题

    2009-01-07 22:41:57

     

    第一:微软公司软件测试简介

        微软的软件测试人员分为两类:测试工具软件开发工程师软件测试工程师。测试工具软件开发工程师主要负责编写测试工具代码,并利用测试工具对软件进行测试;或者开发测试工具为软件测试工程师服务。软件测试工程师主要负责理解产品的功能要求,然后对其进行测试,检查软件有没有错误,决定软件是否具有稳定性,并写出相应的测试规范和测试案例。

        在微软内部,软件测试人员与软件开发人员的比率一般为1.5~2.5左右,微软软件开发的实践过程已经证明这种人员结构的合理性。

        微软认为,测试人员的任务就是站在使用者的角度上,通过不断地使用和攻击刚开发出来的软件产品,尽量多地找出产品中存在的问题。 微软在测试时主要考虑以下几个问题:
      (1) 测试要考虑到所有的出错可能性。同时要做一些不是按常规做的、非常奇怪的事。
      (2) 除了漏洞之外,测试还应考虑性能问题,保证软件运行良好,非常快,没有内存泄露,不会出现软件运行越来越慢的情形。
      (3) 测试要考虑软件的兼容性。
    微软测试中使用的测试文档主要包括以下几种:

    (1) 测试计划。测试计划和产品开发紧密相关,由多个部分组成。所有大型的商业软件都需要完整的测试计划,需要具体到每一个步骤,并且每一个部分都要符合规范要求。

        测试计划包括内容: 1) 概述 2) 测试目标和发布标准 3) 计划将测试的领域 4) 测试方法描述 5) 测试进度表 6) 测试资源 7) 配置范围和测试工具

    (2) 测试规范。测试规范是指微每一个在测试计划中确定的产品领域所写的文档,用来描述该领域的测试需求。编写测试规范,需要参照项目经理写的产品规范,开发人员写的开发计划。每个领域都应该有一份详细的测试规范,所以还需要参照测试计划。 测试规范包括的内容: 1) 背景信息 2) 被测试的特性 3) 功能考虑 4) 测试考虑。 5) 测试想定

    (3) 测试案例。测试案例是指描述如何测试某一个领域的文档,这些文档符合测试规范中的需求说明。根据测试规范的测试想定(scenario)开发,根据测试反馈信息,对于没有考虑到的新问题,不断添加测试案例。 测试案例没有固定格式,只要清楚表明了测试步骤和需要验证的事实,使得任何一位测试人员都可以根据测试案例的描述完成测试

    (4) 测试报告。测试管理人员以测试报告的形式向整个产品开发部门报告测试结果及发现的缺陷或错误。撰写测试报告的目的是为了让整个产品开发部门了解产品开发的进展情况,以使缺陷或错误能够迅速得到修复。 测试报告的格式并无定式,要求能够完整、清楚地反映当前的测试进展情况,要易懂,不要使人迷惑或产生误解。

    (5) 缺陷或错误报告。测试人员以缺陷或错误报告的形式向开发人员报告所发现的缺陷或错误。撰写缺陷或错误报告的目的是为了使缺陷或错误能够得到修复,测试人员的缺陷或错误报告撰写的好坏会直接影响到开发人员对缺陷或错误的修复。 一份缺陷或错误报告应该包括的几个要点: 1) 缺陷或错误名称 2) 被测试软件的版本 3) 优先度与严重性 4) 报告测试的步骤 5) 缺陷或错误造成的后果 6) 预计的操作结果 7) 其他信息

    第二:面试试题分析

        考官从办公室(面试现场)随意选取一个简单物品,假定是一个喝水的带广告图案的花纸杯,让应聘人对它设计出尽可能多的测试用例。

        这个题目考察你的经验、想象力和思维的敏捷性。所以考官希望你源源不断地说出各种各样的测试用例,一直不停顿,直到他(她)满意为止。通常要十到十五分钟。选择简单物品其实增加了问题的难度。

        一般有测试经验的应试者可以从“基本功能测试”、“可用性测试”、“安全测试”、“压力测试”、“性能测试”等等角度思考,想出足够的测试用例并不难。

        从考察你思维的超常性的角度,这题要考你是否能发现常人想象不到的用例。以上的回答中有不少好的例子,比如muse21的“3 带广告的图案沾水后是否掉色、模糊”;bottle的“f.装水,并且放入汤匙,看杯子是否能平稳放置而不会倾倒在桌上”...我还听说过其他一些好的答案,比如“杯子设计是否上大下小,在运输过程中可以套在一起有效利用空间,在使用时也容易拿开”,“为国际化和本地化的需要,广告图案和文字是否在政治、宗教和文化方面具有广泛的适用性”...有必要指出,超常的想象力只有同现实性相结合才能显其高妙,胡思乱想到无理取闹反会弄巧成拙。

        还要考察你捕捉关键问题的能力,看你是否答出了一些关键的测试用例。比如安全性问题。杯子所用的材料(包括纸基、涂层和广告颜料)是否符合食品卫生标准,在内外温度等环境因素下是否会与所盛各种饮料反应,而产生对人体有害的物质。所有与人的饮食有关的产品,这一条应该是头等重要的。

        zhgliu提到“规格说明书”也是非常好的。我们都知道测试是从设计阶段就开始。所以做为测试不仅要确保设计的规格明确,并按规格设计测试,还有责任对杯子的设计提出建议,对不合理的设计提出更该。Mslgn的“如果是一次性杯子,能否标示已使用(比如变色)”和“杯子是否有使用者标贴(多人使用时防止混淆)”就是非常好的设计建议(我在美国市场还没见过有这种功能的纸杯,不知国内现在是否有)。另外还有人建议杯子上不要印广告,或至少要有没有广告的品种,因为团体消费者可能不能接受。

        还有人提出用户试用测试用例,比如由公司组织一个晚会,晚会上提供各种免费的饮料,让大家试用这种纸杯。测试人员在晚会中观察并向来宾收集意见。

        这个问题显然没有标准答案。但要想用你的答案打动考官则需要一定的经验和素质。

    参考整理答案
    1、容量(冰,凉水,开水)
    2、抗摔能力(空杯,半杯水,满杯水,以及不同的温度下)
    3、一杯开水(假定100摄氏度)保温的时间(多久后变到室温),自然还有冰块在室温下多长时间融化
    4、残疾人士用此杯去喝水的容易程度
    5、广告的图案是否容易剥落?
    6、纸质。
    7、带广告的图案与广告的切合程度。

    一、GUI测试:
    1 看其形状、大小设计是否适合人方便拿起;
    2 外观是否吸引人(广告嘛),赏心悦目;
    3 带广告的图案沾水后是否掉色、模糊。

    二、功能、压力测试:
    A 考量其装载能力:
    在杯子内分别装入少量的、半杯的、满杯的:
    1 热水;
    2 冷水;
    3 冰水;
    4 咖啡;
    看其装载量和装载时间以及纸杯拿在手中的硬度是否达到设计标准
    B 装入热水后,纸杯是否有异味。

    三、24*7测试:
    装入液体后记录其多久以后漏水。

    摘自:《测试新手学习宝典》