Design, Implement, and Test Use Cases and Components

Design, Implement, and Test Use Cases and Components

You want to make sure that you design, implement, and test the user capabilities you have agreed to deliver. These capabilities are primarily captured in use-case descriptions and associated user-interface prototypes . As discussed earlier in the section Understand the Requirements and Design Constraints, you find constraints on how to implement the use cases in both the Supplementary Specification and the Software Architecture Document.

You want to design, implement, and test the most essential use cases in Elaboration, which we describe in more detail in the section Use Architecturally Significant Use Cases to Drive the Architecture, in Chapter 7. Note that in Elaboration, you typically implement only the most important scenarios for those use cases, not the complete use case. During each iteration in Construction, you will implement more and more use cases and complete the use cases that were previously only partially implemented. By the end of Construction, all use cases should be fully implemented, but they may still require some minor modifications in Transition, based on user feedback and found defects.

Design Use-Case Realizations and Components

The design representation of a use case is called use-case realization. It describes how a particular use case is realized within the design model in terms of collaborating components. Different developers prefer different approaches when producing a use-case realization. In this section we will describe an approach that has proven valuable by facilitating communication between analysts and developers, by providing a step-by-step guide to identifying good components, and by forcing the design to address the requirements ”but no more. Let's take a look at this five-step approach to producing a use-case realization, dividing the work into an analysis section and a design section:

Step 1. Produce a first-draft outline for what analysis objects are needed to provide the collaboration.

Step 2. Distribute behavior to analysis classes.

Step 3. Detail analysis classes and unify the analysis model.

Step 4. Do use-case design.

Step 5. Detail each design class and unify the design model.

It should be noted that for very simple and data-centric use cases, especially when implementing them in a powerful programming language such as a fourth-generation language (4GL, for example, Visual Basic and PowerBuilder), you typically do not need to go through all these steps (more about this later).

Note that you need to choose the right level of formality for your project. Some developers prefer to capture the output from steps 1 through 3 on whiteboards or even in their heads; others prefer to capture it directly in a visual modeling tool. Some developers may even be inclined to jump directly to step 4. This forces you to move directly from high-level requirements to detailed design decisions. It also makes it harder for you to communicate with the analyst to make sure that you understand the requirements (part of step 1), and it makes it more difficult to come up with a design that complies with the architecture and is unified since you did not do step 3. This means that if you skip steps 1 through 3, you need to mitigate these risks, for example by having experienced developers, by developing less-complex systems, by having developers who understand the requirements well, and by having strong architects on your team.

Finally, the suggested approach works no matter if you are using object-oriented programming languages or other programming languages, as long as your language supports implementation of components through encapsulation.

Let's take a closer look at these steps.

Step 1: Produce a first-draft outline for what analysis objects are needed to provide the collaboration.

Many people new to object-oriented development find it difficult to identify good objects. The RUP provides concrete guidelines that greatly facilitate this task by dividing classes into three categories, or so-called "stereotypes":

graphics/boundaryclasses.gif Boundary classes. Provide an interface toward actors or protocols for interaction with external systems.

Examples: User screens or http:// protocols.

graphics/controlclasses.gif Control classes. Encapsulate the sequencing or behavior of the use case.

Examples: Scheduler or RegistrationController.

graphics/entityclasses.gif Entity classes. Store (normally persistent) information about some phenomenon , such as an event, a person, or a real-life object, and contain the business rules associated with that information.

Examples: Student, Schedule, or Course Offering.

Use-case realizations follow a typical analysis pattern. There is usually

  • One boundary class for each actor involved in the use case to encapsulate the interface toward that actor.

  • One control class for each use case to encapsulate the sequencing within that use case.

  • As many entity classes as needed to store information about various phenomena that need to be represented.

Many of the entity classes needed may already be created (at least toward the second half of Construction), but you may have to add a few additional entity objects during this step. Place objects from the classes you need in a diagram, often called a "View of Participating Classes (VOPC)," and create relationships to show how their instances are communicating. Figure 17.2 is an example of such a diagram. Collaborate closely with the analyst responsible for the use case. Creating a VOPC often highlights holes in the use-case description. You should also consider collaborating closely with the architect, since the architect may be able to help you to rapidly produce a good first-draft model that is consistent with the rest of the system. Involving the architect can radically reduce the amount of time you need to spend on step 3. Our experience also shows that people new to object-oriented techniques need help the first few times doing this step.

Figure 17.2. Example of the View of Participating Classes. The View of Participating Classes shows the analysis classes whose instances participate in a given use case. It provides a high-level understanding of what classes are involved, without focusing on details such as what operations are involved at what time to provide the functionality of the use case.

graphics/17fig02.gif

Step 2: Distribute behavior to analysis classes.

To verify that you have all the classes you need and to understand the responsibilities of each class, you need to describe how the identified objects are collaborating with each other. For those new to component-based development, we have found that a very useful learning tool is to do this as a fun group exercise, similar to the CRC card technique introduced by Kent Beck and Ward Cunningham. [1] Get your team together and have each person play the role of either one of the objects or one of the actors. To represent operations invoked [2] upon various objects, someone (actor/object) passes an item (such as a ball) to the person (actor/object) he or she wants to request a service from. A scribe documents the interaction and the identified responsibility of the actor/object. Then the person with the ball takes a turn at making a request, and so on. Let's look at an example of how this role-play would work for the use case Register for Course for a course registration system (shown in Figure 17.3).

[1] See Beck 1998.

[2] Invoking an operation means that one object sends a message to another object instructing it what to do.

  1. The person representing the actor Student passes the ball to the person representing an instance of the boundary class RegisterForCoursesForm stating that the actor wants to create a schedule.

  2. The person representing an instance of the boundary class RegisterForCoursesForm sends a request "get course offerings" to the person representing the instance of the class RegistrationController .

  3. The person representing an instance of the boundary class RegistrationController

  4. And so on.

Figure 17.3. Example of a Collaboration Diagram. A collaboration diagram shows the dynamic behavior of a use case. It specifies the order in which operations are invoked to provide the functionality of the use case (or part of the use case). In this example, to Register for Courses, (1) the Student invokes the operation Create Schedule on an instance of the class RegisterForCoursesForm, (2) RegisterForCoursesForm invokes the operation "get course offerings" on RegistrationController, and so on.

graphics/17fig03.gif

During this role-play, you come to understand the responsibilities of each class. Any missing classes will be apparent when you have the ball and there is no instance of a class from which to request a needed service.

This description represents an extremely simplified view of this activity; you can find more guidance in the RUP in the activity Use-Case Analysis. Typically, the operations identified at this stage are at a high level, and many of them will later be broken down into several smaller operations involving more objects.

Step 3: Detail analysis classes and unify the analysis model.

During the role-play you need to document the responsibilities (operations) of each class by describing the operations and the data (attributes) the class needs to handle.

The next step is to review the responsibilities and associations of the identified classes. To do this, you need to work with other developers and architects to understand the bigger picture. What you are trying to address in this step is to ensure that different developers have not identified different analysis classes that do almost the same thing (in which case you will have to decide whether they should be merged) and refactor the analysis model to make sure that it supports, rather than breaks, the architecture of your system. You should answer questions such as the following:

  • Are there classes that should be merged or split?

  • What associations are needed between classes?

  • Are there opportunities for reuse?

  • Should some of the classes be generalized?

The steps that follow will continue to refine the analysis model, and in the future if you want to maintain the analysis model separately, you take a snapshot of the model at this stage for future reference.

Step 4: Do use-case design.

During use-case design, you continue to detail the collaboration diagram created in step 2. Do this in parallel with step 5, "Detail each design class and unify the design model." Specify in detail each operation and its parameters. In many cases, you'll notice that a previously identified high-level operation needs to be broken down into several smaller operations, potentially involving additional classes identified (one analysis class may often be implemented by two or more design classes; see below).

For use cases in which the order of invoked operations is significant, you typically want to document the use-case collaboration in a sequence diagram, rather than in a collaboration diagram (see Figure 17.4). When implementing simple use cases (such as online viewing of database records, or updates to those database records with few business rules involved) and using powerful 4GLs such as Visual Basic or PowerBuilder, it is normally less essential to document the exact sequence in which operations are invoked, since the language environment takes care of a lot of the involved logic for you (such as how to synchronize values of various fields on a screen with the corresponding database tables). In such cases, you may not bother to produce a more detailed documentation for the use-case collaboration other than the View of Participating Classes (see Figure 17.2).

Figure 17.4. Example of a Sequence Diagram. A sequence diagram shows the same information as a collaboration diagram, but presents it in a way that provides a clearer overview of the sequencing. Time flows from top to bottom. Each column represents an instance of a class, and the arrows represent that an operation is invoked.

graphics/17fig04.gif

You will also take advantage of your identified architectural mechanisms, which provide solutions for dealing with persistent storage, inter-process communication, transaction management, security, message routing, error reporting, and so on. We describe architectural mechanisms in more detail in Chapters 7 and 16.

Step 5: Detail each design class and unify the design model.

In parallel with use-case design, detail the description of each design class. Specify each operation needed to support the key scenarios, as well as required attributes. Provide the level of detail necessary to implement the class. In many cases, an analysis class will be implemented by two or more design classes. For example, when implementing the analysis class CourseCatalogSystem , you may need a series of design classes to implement a persistent storage of a set of database tables and related business rules for updating these tables.

Since a design class may participate in several different use cases, several developers may have a say in what the design class should look like. You need to decide whether to allow several people to change a design class (and then also have a collective ownership of the implementation and unit testing of that component). If you decide not to have collective ownership, there should still be a developer responsible for the design, implementation, and testing of each use case to make sure that you deliver the capabilities expected by the users. It should be noted that collective ownership typically works for smaller systems with more experienced developers. When developing larger systems, developers will not be familiar with large parts of the system, and the risk is too great that they will not understand the complexities of a component's necessary changes. This may also be the case for smaller systems where inexperienced developers may introduce too many defects when modifying a component's design or implementation.

When designing classes, keep in mind established design patterns, such as the so-called Gang of Four patterns. [3] You may find these and other patterns useful for solving common design problems.

[3] See Gamma 1995.

Similar to step 3, you need to work with other developers and architects to unify the design model. You want to ensure that different developers have not identified different design classes doing very similar things (in this case you will have to decide whether they should they be merged) and to ensure that you are producing a design model that supports, rather than breaks, the system architecture. As an example, for layered architectures you need not only to decide in which layer various components reside, but also to make sure that the relationships between components don't break any rules you may have set up about visibility between layers .

Implement Use Cases and Components

Since implementing use cases is done component-by-component, some of the components you need may already be implemented or partially implemented. Based on whether you have collective ownership, you either develop or refine the components yourself, or you work with another developer to ensure they understand what needs to be done. A design model can greatly facilitate this communication.

Your project should have programming guidelines for the language(s) you are using, and as a developer, it is important that you follow these guidelines to ensure consistency within your project and optimal usage of the language. The RUP product provides you with programming guidelines for some languages (or you can use your own guidelines).

Some developers are good at abstract thinking and always prefer to create a design and then implement it. Other developers find it more productive to do a lot of the low-level design while coding. This is perfectly fine ”do whatever is the most effective for you ”but update your design to reflect your code to ensure that the code is maintainable and understandable by others. This is facilitated by using a visual modeling environment that can round-trip engineer your code. Through reverse engineering you can capture the static design model, but you need to recreate the dynamics, that is, show how the various components interact with each other to provide the required functionality.

It is also important to continually integrate your code, something we talk more about in the section Frequently Integrate Your Application.

In many cases, code reviews can radically improve the quality of your code. Some developers find great value in pair programming, meaning that two developers work side-by-side to write the code. These and other best practices are described in more detail in the section Developer Best Practices.

Developer Testing

As a developer, you need to continuously test your implementation to verify that it provides the required functionality and performance. As mentioned earlier, code reviews can reveal some defects by statically parsing and analyzing the source files, but you also need to understand whether your application provides expected behavior. The only way of determining that is by monitoring the application during its execution at runtime. Runtime analysis ensures that your application uses memory efficiently and without errors, that your components are fast and scalable, and that you test everything before you check in the source files.

To test whether components provide required functionality, you may need to design and implement test drivers and test stubs to emulate other components that will interact with your component(s). Some visual modeling environments can automatically generate these test drivers and stubs for you. Once the stubs are ready, you can run a number of test scenarios. Typically these test scenarios are derived from the use-case scenarios in which the component(s) takes part, since the use-case scenarios identify typical ways in which the components will interact with each other when the users are using the application. You also look at the Supplementary Specification to understand other constraints that need to be tested .

There are many other problems that can be hard to detect later in the project, such as memory leaks or performance problems. As a developer, you can radically reduce the amount of time that needs to be spent on integration and system testing by doing runtime analysis of your code. Runtime analysis is done with tools that examine an application's execution details for method, object, or source line of code. Inadequate memory usage can significantly reduce scalability and can be very difficult to optimize later in the development cycle. In some cases, memory leaks can cause an application to crash when it runs out of available memory. Another reason to perform runtime analysis is to find performance bottlenecks. Runtime analysis tools can identify the root cause of performance problems in code (see Figure 17.5). Using such tools in parallel with reviewing the use cases can help ensure that all common usage scenarios have good enough performance.

Figure 17.5. Runtime Analysis Can Detect Performance Bottlenecks. Tools such as Rational PurifyPlus monitor the execution of an application, or components, to identify excessive or erroneous memory usage, performance bottlenecks, and untested code. In this screen shot, we see potential performance bottlenecks identified by thick lines, showing that the execution of these operations is much slower than the rest of the application. For each potential bottleneck, the developer can determine whether it represents a real issue by comparing the screen to common scenarios represented by the use case.

graphics/17fig05.gif

Automated test tools can also help you identify what code has been tested. This can help you to optimize unit and component tests to cover more use-case scenarios and thus make sure that your code is bug free. If you do not test the code, how can you know whether it will function or perform as expected?



The Rational Unified Process Made Easy(c) A Practitioner's Guide to Rational Unified Process
Programming Microsoft Visual C++
ISBN: N/A
EAN: 2147483647
Year: 2005
Pages: 173

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net