Module 4 Advance Software engineering
Module 4 Advance Software engineering
Reuse Activities
Managing Reuse
Implementation,
testing.
Object design-Reusing pattern solutions:
Conceptually, software system development fills the gap between a given problem and an
existing machine. The activities of system development incrementally close this gap by
identifying and defining objects that realize part of the system (Figure 8-1).
Figure8-1 Object design closes the gap between application objects identified during
requirements and off-the-shelf components selected during system design (stylized UML
class diagram).
Analysis reduces the gap between the problem and the machine by identifying objects
representing problem-specific concepts. During analysis the system is described in terms of
external behavior such as its functionality (use case model), the application domain concepts
it manipulates (object model), its behavior in terms of interactions (dynamic model), and its
nonfunctional requirements.
System design reduces the gap between the problem and the machine in two ways. First,
system design results in a virtual machine that provides a higher level of abstraction than the
machine. This is done by selecting off-the-shelf components for standard services such as
middleware, user interface toolkits, application frameworks, and class libraries. Second,
system design identifies off-the-shelf components for application domain objects such as
reusable class libraries of banking objects.
After several iterations of analysis and system design, the developers are usually left with a
puzzle that has a few pieces missing. These pieces are found during object design. This
includes identifying new solution objects, adjusting off-the-shelf components, and precisely
specifying each subsystem interface and class. The object design model can then be
partitioned into sets of classes that can be implemented by individual developers.
Design Patterns
In object-oriented development, design patterns are template solutions that developers have
refined over time to solve a range of recurring problems [Gamma et al., 1994]. A design
pattern has four elements:
For example, we can restate the problem of writing a set class of Figure 8-3 as implementing
a new class (i.e., MySet) that complies with an existing interface (i.e., the Java Set interface)
reusing the behavior provided by an existing class (i.e., the Hashtable class). Both the Set
interface and the Hashtable class are already provided and neither can be modified.
Figure8-5 An example of design pattern, Adapter (adapted from [Gamma et al., 1994]).
This passage talks about a challenge in software development: balancing stability and
flexibility in system design. Here’s a simpler explanation:
System Design: During this stage, developers break the system into smaller parts,
called subsystems, to manage complexity. Each subsystem is isolated from the others,
meaning changes in one subsystem won’t directly affect others. This creates a solid
structure, but it can also limit flexibility.
Object Design: This focuses on making the software easy to change and adapt later.
The goal is to minimize the cost of future changes (like adding new features or fixing
bugs). However, creating this flexibility might make the initial design more complex.
The paradox is that stability (from system design) and flexibility (from object design) seem
to conflict. On one hand, we want a stable, solid structure to manage complexity, but on the
other hand, we also want flexibility to allow changes in the future.
To solve this paradox, the solution is anticipating change early on. Developers should
design with future changes in mind because they often come from similar sources. These
sources of change include:
By designing systems with these types of changes in mind, developers can reduce the cost
and impact of future changes, keeping the system both stable and flexible.
After identifying a design pattern for each type of anticipated change (Table 8-1), we discuss
the pattern in the context of an actual situation and, in each case, discuss how inheritance and
delegation are used as building blocks to achieve modifiability and extensibility.
1. Problem:
o The ARENA system needs a storage method for Leagues, but at first, it only
needs a simple storage system for testing.
o The team doesn't know which storage method will be best or face performance
issues yet, so they want to keep things flexible.
2. Goal:
o In later stages, they plan to use different storage systems (e.g., file-based and
database-based), but they want to manage these without constantly changing
the rest of the system.
3. Solution: Use the Bridge Design Pattern:
o Bridge pattern separates the high-level storage logic from the details of the
storage method (file, database, etc.), making it easier to switch between
storage types without changing the whole system.
4. Components of the Bridge Pattern:
o LeagueStore: The interface that provides high-level operations for storage. It
doesn’t care about how storage is done (just knows how to store and retrieve
data).
o LeagueStoreImplementor: An abstract interface that defines how different
storage types should behave.
o Implementations:
1. StubStoreImplementor: A simple placeholder for testing (doesn't
store real data).
2. XMLStoreImplementor: Stores data in files (XML format).
3. JDBCStoreImplementor: Stores data in a relational database.
5. Benefit:
o The Bridge pattern allows the system to switch between storage methods
(file, database) easily without needing to rewrite other parts of the system.
Figure 8-7 Applying the Bridge design pattern for abstracting database vendors (UML class
diagram).
The LeagueStore is the interface class to the pattern, and provides all high-level functionality
associated with storage. The LeagueStoreImplementor is an abstract interface that provides
the common interface for the three implementations, namely the StubStoreImplementor for
the stubs, the XMLStoreImplementor for the file-based implementation, and the
JDBCStoreImplementor for the relational database implementation
1. Problem:
o Software development is costly, especially as systems become more complex
and deadlines shorten.
o Developers reuse old code or use off-the-shelf components (pre-made
software parts) to save time and money.
2. Example:
o User interfaces (like buttons, windows) are often built using pre-made
toolkits instead of building everything from scratch.
o When updating systems, only certain parts (like the client-side) may need
upgrading, while the backend stays the same.
3. Challenge:
o Developers work with existing code (or old software), which they can’t
modify and wasn’t designed for the new system.
4. Solution - Adapter Pattern:
o To deal with this, developers encapsulate the existing components using the
Adapter pattern.
o The Adapter acts as a translator between the old component and the new
system.
o This decouples the new system from the old code, reducing its impact on the
design.
The Adapter pattern uses specification inheritance between the ClientInterface and the
Adapter. The Adapter in turn delegates to the LegacyClass implementor class to realize the
operations declared in ClientInterface. On the one hand, this enables all client code that
already uses the ClientInterface to work with instances of Adapter transparently and without
modification of the client. On the other hand, the same Adapter can be used for subtypes of
the LegacyClass.
3. Encapsulating Context with the Strategy Pattern
Figure 8-10 Applying the Strategy pattern for encapsulating multiple implementations of a
NetworkInterface (UML class diagram). The LocationManager implementing a specific
policy configures NetworkConnection with a concrete NetworkInterface (i.e., the
mechanism) based on the current location. The Application uses the NetworkConnection
independently of concrete NetworkInterfaces. See corresponding Java code in Figure 8-11.
To achieve both of these goals, we apply the Strategy design pattern (Appendix A.9, [Gamma
et al., 1994]). The system model and implementation, respectively, are shown in Figures 8-10
and 8-11. The Strategy class is realized by NetworkInterface, which provides the common
interface to all networks; the Context class is realized by a NetworkConnection object, which
represents a point-to-point connection between the wearable and a remote host. The Client is
the mobile application. The Policy is the LocationManager, which monitors the current
location of the wearable and the availability of networks, and configures the
NetworkConnection objects with the appropriate NetworkInterfaces. When the
LocationManager object invokes the setNetworkInterface() method, the NetworkConnection
object shuts down the current NetworkInterface and initializes the new NetworkInterface
transparently from the rest of the application.
The class diagrams for the Bridge and the Strategy patterns (see Figures 8-7 and 8-10) are
almost identical. The key difference is in the creator of the concrete implementation classes:
In the Bridge pattern, the class Abstraction creates and initializes the
ConcreteImplementations. In the Strategy pattern, however, the Context is not aware of the
ConcreteStrategies. Instead, a client creates the ConcreteStrategy objects and configures the
Context. Moreover, ConcreteImplementations in the Bridge pattern are usually created at
initialization time, while ConcreteStrategies in the Strategy pattern are usually created and
substituted several times during run time.
4. Encapsulating Platforms with the Abstract Factory Pattern
We use the Abstract Factory design pattern (Appendix A.1) to solve this problem. In our
intelligent house, each manufacturer provides temperature sensors, electric blinds that report
if they are forced in, and intelligent light bulbs that report if they have burned out. As shown
in Figure 8-12, these generic objects are called AbstractProducts (e.g., LightBulb, Blind),
and their concrete realizations are called ConcreteProducts (e.g., EIBLightBulb,
ZumtobelLightBulb, EIBBlind, ZumtobelBlind). One factory for each manufacturer (e.g.,
ZumtobelFactory, EIBFactory) provides methods for creating the ConcreteProducts (e.g.,
createLightBulb(), createBlind()). The Client classes (e.g., a TheftApplication) access only
the interfaces provided by the AbstractFactory and the AbstractProducts, thereby shielding
the Client classes completely from the manufacturer of the underlying products.
Figure 8-12 Applying the Abstract Factory design pattern to different intelligent house
platforms (UML class diagram, dependencies represent «call» relationships).
The Abstract Factory pattern uses specification inheritance to decouple the interface of a
product from its realization. However, since products of the same platform usually depend on
each other and access the concrete product classes, products of different platforms cannot be
substituted transparently
Figure 8-13 Applying the Command design pattern to Matches in ARENA (UML class
diagram).
The Command design pattern uses specification inheritance between the Command class and
ConcreteCommands, enabling new commands to be added independently from the Invoker.
Delegation is used between ConcreteCommands and Receivers, and between Invoker and
Command, enabling ConcreteCommands to be dynamically created, executed, and stored.
The Command pattern is often used in a Model/View/Controller software architecture, where
Receivers are model objects, Invoker and Commands are controller objects, and Clients
creating Commands are view objects.
Swing addresses this problem with the Composite design pattern (Appendix A.5, [Gamma et
al., 1994]) as depicted in Figure 8-16. An abstract class called Component is the roof of all
user interface objects, including Checkboxes, Buttons, and Labels. Composite, also a subclass
of Component, is a special user interface object representing aggregates including the Panels
we mentioned above. Note that Windows and Applets (the root of the instance hierarchy) are
also Composite classes that have additional behavior for dealing with the window manager
and the browser, respectively.
Figure 8-17 An example of dynamic site with WebObjects (UML component diagram)
Figure 8-17 shows an example of a dynamic publishing site built with WebObjects. The
WebBrowser originates an HTTP request containing a URL, which is sent to the WebServer.
If the WebServer detects that the request is to a static HTML page, it passes it on the
StaticHTML object, which selects and sends the page back to the WebBrowser as a response.
The WebBrowser then renders it for the user. If the WebServer detects that the request
requires a dynamic HTML page, it passes the request to a WebObjects WOAdaptor. The
WOAdaptor packages the incoming HTML request and forwards it to the
WebObjectsApplication object. Based on Templates defined by the developer and relevant
data retrieved from the RelationalDatabase, the WebObjectsApplication then generates an
HTML response page, which is passed back through the WOAdaptor to the WebServer. The
WebServer then sends the page to the WebBrowser, which renders it for the user
Managing Reuse
Historically, software development started as a craft, in which each application was custom
made according to the wishes and needs of a single customer. After all, software development
represented only a fraction of the cost of hardware, and computing solutions were affordable
only to few. With the price of hardware dropping and computing power increasing
exponentially, the number of customers and the range of applications has broadened
dramatically
Reuse, whether design patterns, frameworks, or components, has many technical and
managerial advantages:
Benefits of Reusing Design Patterns and Components:
1. Lower Development Effort:
o Reusing solutions or components avoids common mistakes.
o Systems using design patterns are easier to extend and adapt to changes,
reducing development effort.
o This saves resources, allowing more focus on testing the software, improving
its quality.
2. Lower Risk:
o When using the same design patterns or components repeatedly, the common
problems are already known.
1. Documenting Reuse
Reuse activities involve two types of documentation: the documentation of the template
solution being reused and the documentation of the system that is reusing the solution.
2. Assigning Responsibilities
Below are the main roles involved in reuse:
• Component expert. The component expert is familiar with using a specific component.
The component expert is a developer and usually has received third-party training in the use
of the component.
• Pattern expert. The pattern expert is the analog of the component expert for a family of
design patterns. However, pattern experts are usually self-made and acquire their knowledge
from experience.
• Technical writer. The technical writer must be aware of reuse and document dependencies
between components, design patterns, and the system, as discussed in the previous section.
This may require the technical writer to become familiar with the solutions typically reused
by the organization and with their associated terms.
• Identify missing attributes and operations. During this activity, we examine each
subsystem service and each analysis object. We identify missing operations and attributes
that are needed to realize the subsystem service. We refine the current object design model
and augment it with these operations.
• Specify visibility and signatures. During this activity, we decide which operations are
available to other objects and subsystems, and which are used only within a subsystem. We
also specify the return type of each operation as well as the number and type of its
parameters. This goal of this activity is to reduce coupling among subsystems and provide a
small and simple interface that can be understood easily by a single developer.
• Specify contracts. During this activity, we describe in terms of constraints the behavior of
the operations provided by each object. In particular, for each operation, we describe the
conditions that must be met before the operation is invoked and a specification of the result
after the operation returns
Figure 9-1 The Class Implementor, the Class Extender, and the Class User role (UML use
case diagram)
• Theclass implementor is responsible for realizing the class under consideration. Class
implementors design the internal data structures and implement the code for each public
operation. For them, the interface specification is a work assignment.
• Theclass user invokes the operations provided by the class under consideration during the
realization of another class, called the client class. For class users, the interface specification
discloses the boundary of the class in terms of the services it provides and the assumptions it
makes about the client class.
• The class extender develops specializations of the class under consideration. Like class
implementors, class extenders may invoke operations provided by the class of interest, the
class extenders focus on specialized versions of the same services. For them, the interface
specification both a specifies the current behavior of the class and any constraints on the
services provided by the specialized class.
For example, consider the ARENA Game abstract class (Figure 9-2). The developer
responsible for realizing the Game class, including operations that apply to all Games, is a
class implementor. The League and Tournament classes invoke operations provided by the
Game interface to organize and start Matches. Developers responsible for League and
Tournament are class users of Game. The TicTacToe and Chess classes are concrete Games
that provide specialized extensions to the Game class. Developers responsible for those
classes are class extenders of Game.
Figure 9-2 ARENA Game abstract class with user classes and extender classes.
Operation parameters and return values are typed in the same way as attributes are. The type
constrains the range of values the parameter or the return value can take. Given an operation,
the tuple made out of the types of its parameters and the type of the return value is called the
signature of the operation.
A contract specifies constraints that the class user must meet before using the class as well as
constraints that are ensured by the class implementor and the class extender when used.
Contracts include three types of constraints:
An invariant is a predicate that is always true for all instances of a class. Invariants
are constraints associated with classes or interfaces. Invariants are used to specify
consistency constraints among class attributes.
A precondition is a predicate that must be true before an operation is invoked.
Preconditions are associated with a specific operation. Preconditions are used to
specify constraints that a class user must meet before calling the operation.
A postcondition is a predicate that must be true after an operation is invoked.
Postconditions are associated with a specific operation. Postconditions are used to
specify constraints that the class implementor and the class extender must ensure after
the invocation of the operation
For example, consider the Java interface for the Tournament from Figure 9-3. This class
provides an acceptPlayer() method to add a Player in the Tournament, a removePlayer()
method to withdraw a Player from the Tournament (e.g., because the player cancelled his
application), and a getMaxNumPlayers() method to get the maximum number of Players who
can participate in this Tournament.
3. The number of active Players in a League are those that have taken part in at least one
Tournament of the League.
Figure 9-6 Associations among League, Tournament, and Player classes in ARENA
OCL bags are multisets: they can contain the same object multiple times. Bags are used to
accumulate the objects when accessing indirectly related objects.
• exists(variable|expression) is True if there exists at least one element in the collection for
which expression is True.
The OCL exists() operation is similar to forAll(), except that the expressions evaluated on
each element are ORed, that is, only one element needs to satisfy the expression for the
exists() operation to return True. For example, to ensure that each Tournament conducts at
least one Match on the first day of the Tournament,
In the design of ARENA, we noticed that the server's resources get heavily used by the
number of tournaments and matches happening at the same time, which creates a bottleneck.
We also realized that if players try to play multiple matches at once, it doesn't help with ad
revenue (since showing multiple ads at the same time doesn't increase income) and actually
reduces the quality of the game (since players are distracted and make mistakes).
So, we decided to add checks in the system during the ApplyForTournament process to
stop players from entering multiple matches at once. However, we don't want to punish
players who participate in many tournaments one after another because they help improve
both the quality of the game and ad revenue.
Figure 9-10 A sequence diagram for the applyForTournament() operation (UML sequence
diagram). This sequence diagram leads to the identification of a new operation,
isPlayerOverbooked() to ensure that players are not assigned to Tournaments that take place
simultaneously.
To prevent a player from applying to two different tournaments that will be conducted at the
same time, we draw a sequence diagram representing the control and data flow needed
(Figure 9-10). Drawing this diagram leads us to the identification of an additional operation,
isPlayerOverbooked(), that checks if the start and end dates of the Tournament of interest
overlap with those of other Tournaments into which the Player has already been accepted.
2. Specifying Types, Signatures, and Visibility
During this step, we specify the types of the attributes, the signatures of the operations, and
the visibility of attributes and operations. Specifying types refines the object design model in
two ways. First, we add detail to the model by specifying the range of each attribute.
For example, by determining the type of the start and end date of a Tournament, we make
decisions about the granularity of the time tracked by the application. By selecting a
representation of time including days, hours, minutes, and seconds, we enable LeagueOwners
to conduct several Tournaments per day. Second, we map classes and attributes of the object
model to built-in types provided by the development environment. For example, by selecting
String to represent the name attributes of Leagues and Tournaments, we can use all the
operations provided by the String class to manipulate name values
Figure9-11 Adding type information to the object model of ARENA (UML class diagram).
Only selected information is shown for brevity.
Preconditions and postconditions can also be used to specify dependencies among operations
in the same class. Consider,
for example, the operations on the TournamentControl class. Given a new Tournament,
these operations must be invoked in a specific order. We cannot resolve the sponsorship of a
Tournament without knowing which sponsors are interested. Also, we cannot advertise the
Tournament before we resolve the sponsorship issue. For TournamentControl, we can simply
write preconditions and postconditions that examine the state of the associations of the
Tournament class. To state that sponsors cannot be selected before there are interested
advertisers, we write the following:
4. Specifying Invariants
Invariants are much more difficult to write than preconditions and postconditions, but they
provide an overview of the essential properties of the class. Invariants constitute a permanent
contract that extends and overwrites the operation-specific contracts. The activity of
identifying invariants is similar to that of finding abstract classes during analysis
(Section5.4.10). A few are obvious and can be written from the start. Others can be identified
by extracting common properties from operation-specific contracts.
An example of an obvious invariant is that all Matches of a Tournament must occur within
the time frame of the Tournament:
5. Inheriting Contracts
In a polymorphic language, a class can be substituted by any of its descendents. That is, a
class user invoking operations on a class could be invoking instead a subclass. Hence, the
class user expects that a contract that holds for the superclass still holds for the subclass. We
call this contract inheritance.
For example, in ARENA, consider the inheritance hierarchy between User, LeagueOwner,
Player, Spectator, and Advertiser (Figure 9-13). The User class has an invariant stating that
the email address should be not null so that each user can be notified. If at some point in our
design, we decide that Spectators do not really need an E-mail address, then this contract will
be broken and classes invoking the User.notify() method may break. Consequently, either
Spectators should be taken out of the User hierarchy (i.e., Spectator does not fulfill the User
contract) or the invariant should be revised (i.e., the terms of the contract should be
reformulated).
• Consistency with prior decisions and documents. Developers often do not appreciate
completely the consequences of analysis and system design decisions before object design.
When detailing and refining the object design model, developers may question some of these
decisions and reevaluate them. The management challenge is to maintain a record of these
revised decisions and to make sure all documents reflect the current state of development
We discuss these challenges in Section 9.5.1, where we focus on the Object Design
Document, its development and maintenance, and its relationship with other documents, and
in Section 9.5.2, where we describe the roles and responsibilities associated with object
design.
The ODD is used to exchange interface information among teams and as a reference during
testing. The audience for the ODD includes system architects (i.e., the developers who
participate in the system design), developers who implement each subsystem, and testers.
Here’s a simplified breakdown of the three main approaches to documenting object design
(ODD):
In short, the third approach is preferred because it directly links the object design with the
source code, making it easier to update and keep everything consistent.
Figure 9-14 Embedded ODD approach. Class stubs are generated from the object design
model. The object design model is then documented as tagged comments in the source code.
The initial object design model is abandoned and the ODD is generated from the source code
instead using a tool such as Javadoc (UML activity diagram).
2. Assigning Responsibilities
Object design is characterized by a large number of participants accessing and modifying a
large amount of information. To ensure that changes to interfaces are documented and
communicated in an orderly manner, several roles collaborate to control, communicate, and
implement changes. These include the members of the architecture team who are responsible
for system design and subsystem interfaces, liaisons who are responsible for interteam
communication, and configuration managers who are responsible for tracking change.
Here’s a simplified explanation of the roles and responsibilities during object design:
1. Core Architect:
o Creates coding guidelines and conventions before object design starts.
o Ensures all architects and developers follow these conventions.
o Makes sure the object design aligns with previous decisions documented in the
System Design Document (SDD) and Requirements Analysis Document
(RAD).
2. Architecture Liaisons:
o Document the public subsystem interfaces they are responsible for.
o Create the first draft of the Object Design Document (ODD) for developers to
use.
o Work on changes to public interfaces and ensure that developers are notified
about any updates to avoid miscommunication.
o Work with the core architect to form the architecture team.
3. Object Designers:
o Refine and detail the interface specifications for the class or subsystem they
are implementing.
4. Configuration Manager:
o Releases changes to the interfaces and ODD once they are ready.
o Tracks the relationship between the source code and ODD revisions to
maintain consistency.
5. Technical Writers:
o Clean up and finalize the ODD document.
o Ensure the document is consistent in structure and content.
o Check that the document follows the coding and design guidelines.
Each role helps ensure the object design is well-documented, consistent, and aligned with the
overall system architecture.
Here’s a simplified explanation of the trade-offs when deciding if and when to use
constraints (like OCL) during the software development process:
In short, you need to carefully consider the timing and level of detail required for constraints
based on who you’re communicating with, how much information you have early on, and the
needs of testing.
An Overview of Mapping
A transformation aims at improving one aspect of the model (e.g., its modularity) while
preserving all of its other properties (e.g., its functionality). Hence, a transformation is
usually localized, affects a small number of classes, attributes, and operations, and is
executed in a series of small steps. These transformations occur during numerous object
design and implementation activities. We focus in detail on the following activities:
Figure 10-1 The four types of transformations described in this chapter: model
transformations, refactorings, forward engineering, and reverse engineering
OPTION
1. Model Transformation
Analysis, we used transformations to organize objects into inheritance hierarchies and
eliminate redundancy from the analysis model. For example, the transformation in Figure 10-
2 takes a class model with a number of classes that contain the same attribute and removes
the redundancy. The Player, Advertiser, and LeagueOwner in ARENA all have an email
address attribute. We create a superclass User and move the email attribute to the superclass.
Figure 10-2 An example of an object model transformation. A redundant attribute can be
eliminated by creating a superclass.
2. Refactoring
A refactoring is a transformation of the source code that improves its readability or
modifiability without changing the behavior of the system
For example, the object model transformation of Figure 10-2 corresponds to a sequence of
three refactorings. The first one, Pull Up Field, moves the email field from the subclasses to
the superclass User. The second one, Pull Up Constructor Body, moves the initialization code
from the subclasses to the superclass. The third and final one, Pull Up Method, moves the
methods manipulating the email field from the subclasses to the superclass. Let’s examine
these three refactorings in detail.
Pull Up Field relocates the email field using the following steps (Figure 10-3): 1. Inspect
Player, LeagueOwner, and Advertiser to ensure that the email field is equivalent. Rename
equivalent fields to email if necessary. 2. Create public class User. 3. Set parent of Player,
LeagueOwner, and Advertiser to User. 4. Add a protected field email to class User. 5.
Remove fields email from Player, LeagueOwner, and Advertiser. 6. Compile and test.
Figure 10-3 Applying the Pull Up Field refactoring.
3. Forward Engineering
Forward engineering is applied to a set of model elements and results in a set of
corresponding source code statements, such as a class declaration, a Java expression, or a
database schema. The purpose of forward engineering is to maintain a strong correspondence
between the object design model and the code, and to reduce the number of errors introduced
during implementation, thereby decreasing implementation effort.
For example, Figure 10-5 depicts a particular forward engineering transformation applied to
the classes User and LeagueOwner. First, each UML class is mapped to a Java class. Next,
the UML generalization relationship is mapped to an extends statement in the LeagueOwner
class. Finally, each attribute in the UML model is mapped to a private field in the Java
classes and to two public methods for setting and getting the value of the field. Developers
can then refine the result of the transformation with additional behavior, for example, to
check that the new value of maxNumLeagues is a positive integer.
Figure 10-5 Realization of the User and LeagueOwner classes (UML class diagram and Java
excerpts). In this transformation, the public visibility of email and maxNumLeagues denotes
that the methods for getting and setting their values are public. The actual fields representing
these attributes are private.
4. Reverse Engineering
Reverse engineering is applied to a set of source code elements and results in a set of model
elements. The purpose of this type of transformation is to recreate the model for an existing
system, either because the model was lost or never created, or because it became out of sync
with the source code.
reverse engineering does not necessarily recreate the same model. Although many CASE
tools support reverse engineering, CASE tools provide, at best, an approximation that the
developer can use to rediscover the original model.
5. Transformation Principles
A transformation aims at improving the design of the system with respect to some criterion.
We discussed four types of transformations so far: model transformations, refactorings,
forward engineering, and reverse engineering.
Here are the principles to follow during object model transformations to avoid introducing
errors:
For example, in the case of a Web browser, it might be clearer to represent HTML
documents as aggregates of text and images. However, if we decided during system design to
display documents as they are retrieved, we may introduce a proxy object to represent
placeholders for images that have not yet been retrieved.
1. One-to-One Association:
Description: A one-to-one relationship means that one object is associated with exactly one
other object.
Mapping: A one-to-one association is typically mapped to a single object or reference in
both directions (i.e., each object has a reference to the other).
Collection: Since it’s a direct reference to one object, it’s not typically mapped to a collection
but a single reference.
Example:
class Person {
Address address; // One-to-One relationship with Address
}
class Address {
Person person; // One-to-One relationship with Person
}
2. One-to-Many Association:
Description: A one-to-many relationship means one object can be associated with multiple
objects.
Mapping: In the case of one-to-many relationships, the “one” side holds a reference to a
collection of objects on the "many" side.
Collection: This is typically mapped to a collection like a List, Set, or Map depending on
whether order or uniqueness of elements is important.
Example:
class Author {
List<Book> books; // One-to-Many relationship with Book
}
class Book {
Author author; // Many-to-One relationship with Author
}
3. Many-to-One Association:
Description: A many-to-one relationship means many objects are associated with one
object.
Mapping: On the "many" side, the object holds a reference to a single object on the "one"
side.
Collection: This is typically not mapped to a collection but as a single reference to the "one"
side object.
Example:
class Book {
Author author; // Many-to-One relationship with Author
}
class Author {
List<Book> books; // One-to-Many relationship with Book
}
4. Many-to-Many Association:
List: Use a List when the order of elements matters or you need to allow duplicate
elements.
Set: Use a Set when you want to enforce uniqueness and don’t care about the order of
elements.
Map: Use a Map when there is a key-value relationship (for example, associating an object
with a unique identifier).
Conclusion:
1. Basic Concept:
Object Model: This refers to the structure of objects (classes, attributes, and relationships)
used in your application.
Persistent Storage Schema: This refers to the structure used to store data in a database
(e.g., tables, columns, and relationships between tables).
2. Types of Mappings:
There are several ways to map object models to a persistent storage schema, with the most
common being Object-Relational Mapping (ORM). The following describes the basic
techniques for mapping objects to storage schemas:
Classes to Tables: Each class in the object model typically corresponds to a table in the
relational database. The class's attributes become the columns in the table.
Example:
class Employee {
int id;
String name;
double salary;
}
Primitive Data Types: Attributes such as int, String, double, etc., map directly to
appropriate SQL data types (e.g., INT, VARCHAR, DECIMAL).
Complex Data Types: If a class contains references to other objects, those may require
additional tables and relationships to store the data (such as foreign keys).
This could result in the Person table having a foreign key reference to the Address
table.
The Order table would include a foreign key to the Customer table:
6. Handling Inheritance:
Single Table Inheritance: For an inheritance hierarchy, you can map all the classes
in the hierarchy to a single table, adding columns for the additional attributes of each
subclass. However, this can lead to a large table with many nullable columns.
Example:
class Employee {
int id;
String name;
}
class Manager extends Employee {
String department;
}
Class Table Inheritance: Each class in the hierarchy gets its own table, and a foreign
key references the parent class.
Example:
CREATE TABLE Employee (
id INT PRIMARY KEY,
name VARCHAR(255)
);
CREATE TABLE Manager (
id INT PRIMARY KEY,
department VARCHAR(255),
FOREIGN KEY (id) REFERENCES Employee(id)
);
Concrete Table Inheritance: Each concrete subclass gets its own table with all
attributes, including those from the parent class. This eliminates the need for a foreign
key but can lead to data duplication.
Example:
CREATE TABLE Manager (
id INT PRIMARY KEY,
name VARCHAR(255),
department VARCHAR(255)
);
Embedded Objects: In some cases, objects are embedded directly inside other objects. For
example, an Address might be embedded inside a Person object rather than having a
separate table for Address.
Example:
class Person {
String name;
Address address; // Address is embedded
}
In this case, Person might have the Address fields directly as columns:
8. Mapping Enumerations:
Enums: Enum values are usually stored as strings or integers in the database. You can map
enum types to string or integer fields in your table.
Example:
enum Status {
ACTIVE, INACTIVE, SUSPENDED
}
class User {
Status status;
}
Conclusion:
Managing Implementation
1. Documenting Transformations
Transformations enable us to improve specific aspects of the object design model and to
convert it into source code. By providing systematic recipes for recurring situations,
transformations enable us to reduce the amount of effort and the overall number of errors in
the source code. However, to retain this benefit throughout the lifetime of the system, we
need to document the application of transformations so that they can be consistently reapplied
in the event of changes to the object design model or the source code.
2. Buried Associations:
2. Assigning Responsibilities
Several roles collaborate to select, apply, and document transformations and the conversion
of the object design model into source code:
• The core architect selects the transformations to be systematically applied. For example, if
it is critical that the database schema is modifiable, the core architect decides that all
associations should be implemented as separate tables.
• The architecture liaison is responsible for documenting the contracts associated with
subsystem interfaces. When such contracts change, the architecture liaison is responsible for
notifying all class users.
• The developer is responsible for following the conventions set by the core architect and
actually applying the transformations and converting the object design model into source
code. Developers are responsible for maintaining up-to-date the source code comments with
the rest of the models
Testing
An Overview of Testing
Reliability is a measure of success with which the observed behavior of a system conforms
to the specification of its behavior. Software reliability is the probability that a software
system will not cause system failure for a specified time under specified conditions Failure is
any deviation of the observed behavior from the specified behavior.
A fault, also called “defect” or “bug,” is the mechanical or algorithmic cause of an erroneous
state. The goal of testing is to maximize the number of discovered faults, which then allows
developers to correct them and increase the reliability of the system.
We define testing as the systematic attempt to find faults in a planned way in the
implemented software. Contrast this definition with another common one: “testing is the
process of demonstrating that faults are not present.” The distinction between these two
definitions is important.. The explicit goal of testing is to demonstrate the presence of faults
and non optimal behavior.
Figure 11-1 Testing activities and their related work products (UML activity diagram).
Swimlanes indicate who executes the test.
• Test planning allocates resources and schedules the testing. This activity should occur
early in the development phase so that sufficient time and skill is dedicated to testing. For
example, developers can design test cases as soon as the models they validate become stable.
• Usability testing tries to find faults in the user interface design of the system. Often,
systems fail to accomplish their intended purpose simply because their users are confused by
the user interface and unwillingly introduce erroneous data.
• Unit testing tries to find faults in participating objects and/or subsystems with respect to
the use cases from the use case model.
• System testing tests all the components together, seen as a single system to identify faults
with respect to the scenarios from the problem statement and the requirements and design
goals identified in the analysis and system design, respectively:
• Functional testing tests the requirements from the RAD and the user manual.
• Performance testing checks the nonfunctional requirements and additional design goals
from the SDD. Functional and performance testing are done by developers.
• Acceptance testing and installation testing check the system against the project agreement
and is done by the client, if necessary, with help by the developers.
Testing Concepts
A test component is a part of the system that can be isolated for testing. A component can be
an object, a group of objects, or one or more subsystems.
• A fault, also called bug or defect, is a design or coding mistake that may cause abnormal
component behavior.
• A failure is a deviation between the specification and the actual behavior. A failure is
triggered by one or more erroneous states. Not all erroneous states trigger a failure.2
• A test case is a set of inputs and expected results that exercises a test component with the
purpose of causing failures and detecting faults.
With the initial understanding of the terms from the definitions in Section 11.3, let’s take a
look at Figure 11-3. What do you see? Figure 11-3 shows a pair of tracks that are not aligned
with each other. If we envision a train running over the tracks, it would crash (fail). However,
the figure actually does not present a failure, nor an erroneous state, nor a fault. It does not
show a failure, because the expected behavior has not been specified, nor is there any
observed behavior. Figure 11-3 also does not show an erroneous state, because that would
mean that the system is in a state that further processing will lead to a failure. We only see
tracks here; no moving train is shown. To speak about erroneous state, failure, or fault, we
need to compare the desired behavior (described in the use case in the RAD) with the
observed behavior (described by the test case). Assume that we have a use case with a train
moving from the upper left track to the lower right track (Figure 11-4).
Figure 11-3 An example of a fault. The desired behavior is that the train remain on the tracks
Figure 11-4 Use case DriveTrain specifying the expected behavior of the train.
A fault in the virtual machine of a software system is another example of a mechanical fault:
even if the developers have implemented correctly, that is, they have mapped the object
model correctly onto the code, the observed behavior can still deviate from the specified
behavior. In concurrent engineering projects, for example, where hardware is developed in
parallel with software, we cannot always make the assumption that the virtual machine
executes as specified.
Other examples of mechanical faults are power failures. Note the relativity of the terms
“fault” and “failure” with respect to a particular system component: the failure in one system
component (the power system) is the mechanical fault that can lead to failure in another
system component (the software system).
2. Test Cases
A test case is a set of input data and expected results that exercises a component with the
purpose of causing failures and detecting faults. A test case has five attributes: name,
location, input, oracle, and log (Table 11-1). The name of the test case allows the tester to
distinguish between different test cases. A heuristic for naming test cases is to derive the
name from the requirement it is testing or from the component being tested. For example, if
you are testing a use case Deposit(), you might want to call the test case Test_Deposit. If a
test case involves two components A and B, a good name would be Test_AB. The location
attribute describes where the test case can be found. It should be either the path name or the
URL to the executable of the test program and its inputs.
Inputdescribes the set of input data or commands to be entered by the actor of the test case
(which can be the tester or a test driver). The expected behavior of the test case is the
sequence of output data or commands that a correct execution of the test should yield. The
expected behavior is described by the oracle attribute. The log is a set of time-stamped
correlations of the observed behavior with the expected behavior for various test runs.
Executing test cases on single components or combinations of components requires the tested
component to be isolated from the rest of the system. Test drivers and test stubs are used to
substitute for missing parts of the system. A test driver simulates the part of the system that
calls the component under test. A test driver passes the test inputs identified in the test case
analysis to the component and displays the results.
A test stub simulates a component that is called by the tested component. The test stub must
provide the same API as the method of the simulated component and must return a value
compliant with the return result type of the method’s type signature. Note that the interface of
all components must be baselined. If the interface of a component changes, the corresponding
test drivers and stubs must change as well.
4. Corrections
Once tests have been executed and failures have been detected, developers change the
component to eliminate the suspected faults. A correction is a change to a component whose
purpose is to repair a fault. Corrections can range from a simple modification to a single
component, to a complete redesign of a data structure or a subsystem. In all cases, the
likelihood that the developer introduces new faults into the revised component is high.
Several techniques can be used to minimize the occurrence of such faults:
Problem tracking includes the documentation of each failure, erroneous state, and fault
detected, its correction, and the revisions of the components involved in the change. Together
with configuration management, problem tracking enables developers to narrow the search
for new faults. We describe problem tracking in more detail in Chapter 13, Configuration
Management.
• Regression testing includes the reexecution of all prior tests after a change. This ensures
that functionality which worked before the correction has not been affected. Regression
testing is important in object-oriented methods, which call for an iterative development
process. This requires testing to be initiated earlier and for test suites to be maintained after
each iteration. Regression testing unfortunately is costly, especially when part of the tests is
not automated. We describe regression testing in more detail in Section 11.4.4.
• Rationale maintenance includes the documentation of the rationale for the change and its
relationship with the rationale of the revised component. Rationale maintenance enables
developers to avoid introducing new faults by inspecting the assumptions that were used to
build the component. We describe rationale maintenance in Chapter 12, Rationale
Management.
Managing Testing
we describe the planning of test activities (Section 11.5.1). Next, we describe the test plan,
which documents the activities of testing (Section 11.5.2). Next, we describe the roles
assigned during testing (Section 11.5.3). Next, we discuss the topics of regression
testing(Section 11.5.4), automated testing (Section 11.5.5), and model-based testing (Section
11.5.6).
Test Planning is a crucial phase in software testing that involves defining a strategy to
ensure the system meets all requirements and functions properly. It outlines the testing
objectives, resources, timelines, tools, and the scope of testing activities.
1. Defining Scope and Objectives: Identify what needs to be tested, including system
components, features, and user interactions.
2. Selecting Test Types: Decide on the types of testing to be conducted, such as unit
testing, integration testing, system testing, acceptance testing, etc.
3. Resource Allocation: Determine the human resources (testers, developers) and tools
(test management tools, automation tools) required for testing.
4. Time and Schedule: Plan the timeline, including milestones and deadlines for each
phase of testing.
5. Test Environment Setup: Set up the necessary hardware, software, network
configurations, and other resources needed to run the tests.
6. Risk Management: Identify potential risks (e.g., resource shortages, environmental
issues) and develop mitigation strategies.
Test Plan:
A Test Plan is a formal document that outlines the activities, scope, and resources for
software testing. It serves as a blueprint for the entire testing process and ensures that testing
is carried out systematically and effectively.
Various roles are assigned during the testing process, and each has its own specific
responsibilities:
1. Test Manager:
o Oversees the overall testing process.
o Responsible for test planning, resource management, and test execution
oversight.
o Ensures that the testing process meets the specified objectives and quality
standards.
2. Test Lead:
o Leads the test team and manages day-to-day testing activities.
o Reviews test cases, assigns tasks to testers, and monitors progress.
o Ensures the testing is on track and that any issues are quickly addressed.
3. Testers:
o Execute the test cases as per the test plan.
o Report defects and follow up on the resolution of issues.
o Responsible for creating, reviewing, and maintaining test cases and testing
artifacts.
4. Developers:
o Collaborate with testers by fixing defects and providing necessary technical
support.
o May assist in writing unit tests and debugging errors found during integration
or system testing.
5. Business Analysts/Subject Matter Experts (SMEs):
o Ensure that the system meets the business requirements.
o Provide input into test case creation to ensure coverage of functional aspects.
o Participate in User Acceptance Testing (UAT).
6. Automation Engineers:
o Develop and maintain automated tests.
o Focus on testing repetitive tasks or those requiring heavy load testing.
7. Configuration Manager:
o Manages the test environment and ensures that all test configurations are set
up properly.
o Responsible for managing the version control of test scripts and test data.
Regression Testing:
Regression Testing is the process of re-running previously conducted tests to ensure that
new changes (such as bug fixes or new features) have not introduced new defects into the
system.
Automated Testing:
Automated Testing uses software tools to automatically execute tests and compare the actual
outcome with the expected result. It’s particularly useful for repetitive tests and large-scale
applications.
1. Efficiency: Automated tests can be run much faster than manual tests, saving time
and resources.
2. Reusability: Test scripts can be reused across different versions of the software,
improving consistency.
3. Frequency: Automated testing is ideal for regression testing and performance testing,
where the same tests need to be run multiple times.
4. Tools: Tools like Selenium, JUnit, TestNG, and Appium are used for automating
tests.
Example: In an online banking app, automated tests can be created to check if the transfer of
funds works correctly across various accounts. These tests can be run frequently, ensuring
that no new changes break the transaction feature.
Model-Based Testing:
Model-Based Testing (MBT) uses models (often formal or semi-formal) to represent the
system's behavior and automatically generates test cases from these models. The goal is to
identify unexpected behavior and edge cases early.
1. Model Creation: A model that represents the expected behavior of the system (e.g.,
state diagrams, activity diagrams) is created.
2. Test Generation: From the model, automated tools generate test cases that represent
different system states and transitions.
3. Test Execution: The generated test cases are executed to validate if the system
behaves as expected.
4. Advantages:
o Can detect unexpected behaviors and edge cases that might be missed with
traditional testing.
o Helps in reducing the time spent on test case creation and maintenance.
Example: In a web application for a restaurant reservation system, a model could be built
that represents the states of reservation (e.g., booking, confirming, canceling) and the
transitions between these states. Test cases are generated automatically to ensure that the
system works correctly in all scenarios.
Summary:
1. Test Planning: Outlines the overall approach, resources, timelines, and goals for
testing.
2. Test Plan: A detailed document that specifies what, how, and when testing will occur.
3. Roles: Different roles (Test Manager, Tester, Developer, etc.) are assigned to
different tasks in the testing process.
4. Regression Testing: Re-running tests to ensure that new changes don’t break existing
functionality.
5. Automated Testing: Using tools to automate test execution for efficiency and
consistency.
6. Model-Based Testing: Using models to generate test cases, helping to identify edge
cases and reducing test creation time.
These processes together ensure that the software is tested thoroughly, efficiently, and
consistently, while reducing human error and improving software quality.