SE-UNIT-4
SE-UNIT-4
SE-UNIT-4
Unit 4
CODING
The input to the coding phase is the design document produced at the end of the design
phase. The detailed design is usually documented in the form of module specifications where the
data structures and algorithms for each module are specified. During the coding phase, different
modules identified in the design document are coded according to their respective module
specifications. We can describe the overall objective of the coding phase to be the following.
The objective of the coding phase is to transform the design of a system into code in a
high-level language, and then to unit test this code.
Normally, good software development organizations require their programmers to adhere to
some well defined and standard style of coding which is called their coding standard. These
software development organizations formulate their own coding standards that suit them the
most, and require their developers to follow the standards rigorously because of the significant
business advantages it offers. The main advantages of adhering to a standard style of coding are
the following:
A coding standard gives a uniform appearance to the codes written by different engineers.
It facilitates code understanding and code reuse.
It promotes good programming practices.
Page 2 of 33
It is mandatory for the programmers to follow the coding standards. Compliance of their
code to coding standards is verified during code inspection.
Any code that does not conform to the coding standards is rejected during code review
and the code is reworked by the concerned programmer.
In contrast, coding guidelines provide some general suggestions regarding the coding
style to be followed but leave the actual implementation of these guidelines to the
discretion of the individual developers.
Naming conventions for global variables, local variables, and constant identifiers:
A popular naming convention is that variables are named using mixed case lettering. Global
variable names would always start with a capital letter (e.g., GlobalData) and local variable
names start with small letters (e.g., localData). Constant names should be formed using capital
letters only (e.g., CONSTDATA).
Conventions regarding error return values and exception handling mechanisms:
The way error conditions are reported by different functions in a program should be standard
within an organization. For example, all functions while encountering an error condition should
either return a 0 or 1 consistently, independent of which programmer has written the code. This
facilitates reuse and debugging.
Representative coding guidelines:
The following are some representative coding guidelines that are recommended by many
software development organizations. Wherever necessary, the rationale behind these guidelines
is also mentioned.
Do not use a coding style that is too clever or too difficult to understand:
Page 3 of 33
Code should be easy to understand. Many inexperienced engineers actually take pride in writing
cryptic and incomprehensible code. Clever coding can obscure meaning of the code and reduce
code understandability; thereby making maintenance and debugging difficult and expensive.
Avoid obscure side effects:
The side effects of a function call include modifications to the parameters passed by reference,
modification of global variables, and I/O operations. An obscure side effect is one that is not
obvious from a casual examination of the code. Obscure side effects make it difficult to
understand a piece of code.
Do not use an identifier for multiple purposes:
Programmers often use the same identifier to denote several temporary entities. For example,
some programmers make use of a temporary loop variable for also computing and storing the
final result. Three variables use up three memory locations, whereas when the same variable is
used for three different purposes, only one memory location is used. However, there are several
things wrong with this approach and hence should be avoided. Some of the problems caused by
the use of a variable for multiple purposes are as follows:
Each variable should be given a descriptive name indicating its purpose. This is not
possible if an identifier is used for multiple purposes. Use of a variable for multiple
purposes can lead to confusion and make it difficult for somebody trying to read and
understand the code.
Use of variables for multiple purposes usually makes future enhancements more difficult.
For example, while changing the final computed result from integer to float type, the
programmer might subsequently notice that it has also been used as a temporary loop
variable that cannot be a float type.
Code should be well-documented:
As a rule of thumb, there should be at least one comment line on the average for every three
source lines of code.
Length of any function should not exceed 10 source lines:
A lengthy function is usually very difficult to understand as it probably has a large number of
variables and carries out many different types of computations. For the same reason, lengthy
functions are likely to have disproportionately larger number of bugs.
Do not use GO TO statements:
Use of GO TO statements makes a program unstructured. This makes the program very difficult
to understand, debug, and maintain.
CODE REVIEW
Testing is an effective defect removal mechanism. However, testing is applicable to only
executable code. Review is a very effective technique to remove defects from source code. In
fact, review has been acknowledged to be more cost-effective in removing defects as compared
to testing. Over the years, review techniques have become extremely popular and have been
generalized for use with other work products.
Code review for a module is undertaken after the module successfully compiles. That is,
all the syntax errors have been eliminated from the module.
Obviously, code review does not target to design syntax errors in a program, but is
designed to detect logical, algorithmic, and programming errors.
Code review has been recognized as an extremely cost-effective strategy for eliminating
coding errors and for producing high quality code.
Page 4 of 33
The reason behind why code review is a much more cost-effective strategy to eliminate
errors from code compared to testing is that reviews directly detect errors.
On the other hand, testing only helps detect failures and significant effort is needed to
locate the error during debugging.
Eliminating an error from code involves three main activities—testing, debugging, and then
correcting the errors. Testing is carried out to detect if the system fails to work satisfactorily for
certain types of inputs and under certain circumstances. Once a failure is detected, debugging is
carried out to locate the error that is causing the failure and to remove it. Of the three testing
activities, debugging is possibly the most laborious and time consuming activity. In code
inspection, errors are directly detected, thereby saving the significant effort that would have been
required to locate the error.
Normally, the following two types of reviews are carried out on the code of a module:
Code inspection.
Code walkthrough.
The procedures for conduction and the final objectives of these two review techniques are very
different.
Code Walkthrough
Code walkthrough is an informal code analysis technique. In this technique, a module is
taken up for review after the module has been coded, successfully compiled, and all syntax errors
have been eliminated. A few members of the development team are given the code a couple of
days before the walkthrough meeting. Each member selects some test cases and simulates
execution of the code by hand (i.e., traces the execution through different statements and
functions of the code).
The main objective of code walkthrough is to discover the algorithmic and logical errors
in the code.
The members note down their findings of their walkthrough and discuss those in a walkthrough
meeting where the coder of the module is present. Even though code walkthrough is an informal
analysis technique, several guidelines have evolved over the years for making this naive but
useful analysis technique more effective. These guidelines are based on personal experience,
common sense, and several other subjective factors.
The team performing code walkthrough should not be either too big or too small. Ideally,
it should consist of between three to seven members.
Discussions should focus on discovery of errors and avoid deliberations on how to fix the
discovered errors.
In order to foster co-operation and to avoid the feeling among the engineers that they are
being watched and evaluated in the code walkthrough meetings, managers should not
attend the walkthrough meetings.
Code Inspection
During code inspection, the code is examined for the presence of some common programming
errors.
The principal aim of code inspection is to check for the presence of some common types
of errors that usually creep into code due to programmer mistakes and oversights and to
check whether coding standards have been adhered to.
Page 5 of 33
As an example of the type of errors detected during code inspection, consider the classic
error of writing a procedure that modifies a formal parameter and then calls it with a
constant actual parameter.
It is more likely that such an error can be discovered by specifically looking for this kinds
of mistakes in the code, rather than by simply hand simulating execution of the code.
In addition to the commonly made errors, adherence to coding standards is also checked
during code inspection.
Good software development companies collect statistics regarding different types of
errors that are commonly committed by their engineers and identify the types of errors
most frequently committed.
Such a list of commonly committed errors can be used as a checklist during code
inspection to look out for possible errors.
Following is a list of some classical programming errors which can be checked during code
inspection:
Use of uninitialized variables.
Jumps into loops.
Non-terminating loops.
Incompatible assignments.
Array indices out of bounds.
Improper storage allocation and deallocation.
Mismatch between actual and formal parameter in procedure calls.
Use of incorrect logical operators or incorrect precedence among operators.
Improper modification of loop variables.
Comparison of equality of floating point values.
Dangling reference caused when the referenced memory has not been allocated.
SOFTWARE DOCUMENTATION
When software is developed, in addition to the executable files and the source code, several
kinds of documents such as users‘ manual, software requirements specification (SRS) document,
design document, test document, installation manual, etc.
Good documents help enhance understandability of code. As a result, the availability of
good documents help to reduce the effort and time required for maintenance.
Documents help the users to understand and effectively use the system.
Good documents help to effectively tackle the manpower turnover problem. Even when
an engineer leaves the organization, and a new engineer comes in, he can build up the
required knowledge easily by referring to the documents.
Page 6 of 33
Production of good documents helps the manager to effectively track the progress of the
project. The project manager would know that some measurable progress has been
achieved, if the results of some pieces of work have been documented and the same has
been reviewed.
Different types of software documents can broadly be classified into the following:
Internal documentation: These are provided in the source code itself.
External documentation: These are the supporting documents such as SRS document,
installation document, user manual, design document, and test document.
Internal Documentation
Internal documentation is the code comprehension features provided in the source code itself.
Internal documentation can be provided in the code in several forms. The important types of
internal documentation are the following:
Comments embedded in the source code.
Use of meaningful variable names.
Module and function headers.
Code indentation.
Code structuring (i.e., code decomposed into modules and functions).
Use of enumerated types.
Use of constant identifiers.
Use of user-defined data types.
Out of these different types of internal documentation, which one is the most valuable for
understanding a piece of code?
Careful experiments suggest that out of all types of internal documentation, a meaningful
variable name is most useful while trying to understand a piece of code.
The research finding is obviously true when comments are written without much thought. For
example, the following style of code commenting is not much of a help in understanding the
code.
a=10; /* a made 10 */
A good style of code commenting is to write to clarify certain non-obvious aspects of the
working of the code, rather than cluttering the code with trivial comments. Even when a piece of
code is carefully commented, a meaningful variable name has been found to be the most helpful
in understanding the code.
External Documentation
External documentation is provided through various types of supporting documents such as
users‘ manual, software requirements specification document, design document, test document,
etc.
Documents are not consistent, a lot of confusion is created for somebody trying to
understand the software.
All the documents developed for a product should be up-to-date and every change made
to the code should be reflected in the relevant external documents.
Even if only a few documents are not up-to-date, they create inconsistency and lead to
confusion. Another important feature required for external documents is proper
Page 7 of 33
understandability by the category of users for whom the document is designed. For
achieving this, Gunning‘s fog index is very useful.
Observe that the fog index is computed as the sum of two different factors. The first
factor computes the average number of words per sentence (total number of words in the
document divided by the total number of sentences).
This factor therefore accounts for the common observation that long sentences are
difficult to understand.
The second factor measures the percentage of complex words in the document. Note that
a syllable is a group o f words that can be independently pronounced.
For example, the word ―sentence‖ has three syllables (―sen‖, ―ten‖, and ―ce‖). Words
having more than three syllables are complex words and presence of many such words
hamper readability of a document.
Example 10.1 Consider the following sentence: ―The Gunning‘s fog index is based on the
premise that use of short sentences and simple words makes a document easy to understand.‖
Calculate its Fog index.
The fog index of the above example sentence is
0.4 � (23/1) + (4/23) � 100 = 26
If a users‘ manual is to be designed for use by factory workers whose educational qualification is
class 8, then the document should be written such that the Gunning‘s fog index of the document
does not exceed 8.
TESTING
The aim of program testing is to help realiseidentify all defects in a program. However, in
practice, even after satisfactory completion of the testing phase, it is not possible to
guarantee that a program is error free.
This is because the input data domain of most programs is very large, and it is not
practical to test the program exhaustively with respect to each value that the input can
assume. Consider a function taking a floating point number as argument.
If a tester takes 1sec to type in a value, then even a million testers would not be able to
exhaustively test it after trying for a million numbers of years.
Even with this obvious limitation of the testing process, we should not underestimate the
importance of testing.
Careful testing can expose a large percentage of the defects existing in a program, and
therefore provides a practical way of reducing defects in a system.
Basic Concepts and Terminologies
Page 8 of 33
Terminologies:
As is true for any specialized domain, the area of software testing has come to be associated with
its own set of terminologies. a few important terminologies that have been standardized by the
IEEE Standard Glossary of Software Engineering Terminology [IEEE90]:
A mistake is essentially any programmer action that later shows up as an incorrect result
during program execution. A programmer may commit a mistake in almost any
development activity. For example, during coding a programmer might commit the
mistake of not initializing a certain variable, or might overlook the errors that might arise
in some exceptional situations such as division by zero in an arithmetic operation. Both
these mistakes can lead to an incorrect result.
An error is the result of a mistake committed by a developer in any of the development
activities. Among the extremely large variety of errors that can exist in a program. One
example of an error is a call made to a wrong function.
The terms error, fault, bug, and defect are considered to be synonyms in the area of
program testing.
Example 10.2 Can a designer‘s mistake give rise to a program error? Give an example of a
designer‘s mistake and the corresponding program error.
Answer: Yes, a designer‘s mistake give rise to a program error. For example, a requirement
might be overlooked by the designer, which can lead to it being overlooked in the code as well.
A failure of a program essentially denotes an incorrect behavior exhibited by the
program during its execution. An incorrect behavior is observed either as an incorrect
result produced or as an inappropriate activity carried out by the program. Every failure is
caused by some bugs present in the program.
Example 10.3 Give an example of a program error that may not cause any failure.
Answer: Consider the following C program segment:
Marklist[roll]=mark;
Else
Marklist[roll]=0;
In the above code, if the variable roll assumes zero or some negative value under some
circumstances, then an array index out of bound type of error would result. However, it may be
the case that for all allowed input values the variable roll is always assigned positive values.
Then, the else clause is unreachable and no failure would occur.
A test case is a triplet [I , S, R], where I is the data input to the program under test, S is
the state of the program at which the data is to be input, and R is the result expected to be
produced by the program.
A n example of a test case is—[input: ―abc‖, state: edit, result: abc is displayed], which
essentially means that the input abc needs to be applied in the edit mode, and the
expected result is that the string a b c would be displayed.
A test scenario is an abstract test case in the sense that it only identifies the aspects of the
program that are to be tested without identifying the input, state, or output. A test case
can be said to be an implementation of a test scenario. In the test case, the input, output,
and the state at which the input would be applied is designed such that the scenario can
be executed.
A test script is an encoding of a test case as a short program. Test scripts are developed
for automated execution of the test cases.
A test case is said to be a positive test case if it is designed to test whether the software
correctly performs a required functionality. A test case is said to be negative test case, if
it is designed to test whether the software carries out something, which is not required of
the system. As one example each of a positive test case and a negative test case, consider
a program to manage user login. A positive test case can be designed to check if a login
system validates a user with the correct user name and password. A negative test case in
this case can be a test case that checks whether the login functionality validates and
admits a user with wrong or bogus login user name or password.
A test suite is the set of all test that have been designed by a tester to test a given
program.
Testability of a requirement denotes the extent to which it is possible to determine
whether an implementation of the requirement conforms to it in both functionality and
performance.
A failure mode of software denotes an observable way in which it can fail. In other
words, all failures that have similar observable symptoms constitute a failure mode. As
an example of the failure modes of software, consider a railway ticket booking software
that has three failure modes—failing to book an available seat, incorrect seat booking
(e.g., booking an already booked seat), and system crash.
Equivalent faults denote two or more bugs that result in the system failing in the same
failure mode. As an example of equivalent faults, consider the following two faults in C
language—division by zero and illegal memory access errors. These two are equivalent
faults, since each of these leads to a program crash.
Page 10 of 33
It includes checking documents, design, It includes testing and validating the actual
codes and programs. product.
Verification Validation
Methods used in verification are reviews, Methods used in validation are Black Box
walkthroughs, inspections and desk- Testing, White Box Testing and non-
checking. functional testing.
It can find the bugs in the early stage of It can only find the bugs that could not be
the development. found by the verification process.
Example 10.5 Is it at all possible to develop highly reliable software, using validation techniques
alone? If so, can we say that all verification techniques are redundant?
Answer: It is possible to develop highly reliable software using validation techniques alone.
However, this would cause the development cost to increase drastically. Verification techniques
help achieve phase containment of errors and provide a means to cost-effectively remove bugs.
Page 12 of 33
Testing Activities
When test cases are designed based on random input data, many of the test cases do not
contribute to the significance of the test suite, That is, they do not help detect any additional
defects not already being detected by other test cases in the suite. Testing software using a large
collection of randomly selected test cases does not guarantee that all (or even most) of the errors
in the system will be uncovered. Let us try to understand why the number of random test cases in
a test suite, in general, does not indicate of the effectiveness of testing. Consider the following
example code segment which determines the greater of two integer values x and y. This code
segment has a simple programming error:
if (x>y) max = x;
Page 13 of 33
else max = x;
For the given code segment, the test suite {(x=3,y=2);(x=2,y=3)} can detect the error, whereas a
larger test suite {(x=3,y=2);(x=4,y=3); (x=5,y=1)} does not detect the error. All the test cases in
the larger test suite help detect the same error, while the other error in the code remains
undetected. So, it would be incorrect to say that a larger test suite would always detect more
errors than a smaller one, unless of course the larger test suite has also been carefully designed.
This implies that for effective testing, the test suite should be carefully designed rather than
picked randomly. A minimal test suite is a carefully designed set of test cases such that each test
case helps detect different errors. This is in contrast to testing using some random input values.
Black-box approach
White-box (or glass-box) approach
In the black-box approach, test cases are designed using only the functional specification
of the software.
That is, test cases are designed solely based on an analysis of the input/out behavior (that
is, functional behavior) and does not require any knowledge of the internal structure of a
program. For this reason, black-box testing is also known as functional testing. On the
other hand, designing white-box test cases requires a thorough knowledge of the internal
structure of a program, and therefore white-box testing is also called structural testing.
Black- box test cases are designed solely based on the input-output behavior of a
program.
UNIT TESTING
Unit testing is undertaken after a module has been coded and reviewed. This activity is typically
undertaken by the coder of the module himself in the coding phase. Before carrying out unit
testing, the unit test cases have to be designed and the test environment for the unit under test has
to be developed.
Driver and stub modules
In order to test a single module, we need a complete environment to provide all relevant code
that is necessary for execution of the module. That is, besides the module under test, the
following are needed to test the module:
The procedures belonging to other modules that the module under test calls.
Non-local data structures that the module accesses.
A procedure to call the functions of the module under test with appropriate parameters.
Modules required to provide the necessary environment (which either call or are called by the
module under test) are usually not available until they too have been unit tested. In this context,
stubs and drivers are designed to provide the complete environment for a module so that testing
can be carried out.
Stub:
The role of stub and driver modules is pictorially shown in Figure 10.3. A stub procedure is a
dummy procedure that has the same I/O parameters as the function called by the unit under test
but has a highly simplified behavior. For example, a stub procedure may produce the expected
behavior using a simple table look up mechanism.
Figure 10.3: Unit testing with the help of driver and stub modules.
Driver:
A driver module should contain the non-local data structures accessed by the module under test.
Additionally, it should also have the code to call the different functions of the unit under test
with appropriate parameter values for testing.
Page 15 of 33
BLACK-BOX TESTING
In black-box testing, test cases are designed from an examination of the input/output values only
and no knowledge of design or code is required. The following are the two main approaches
available to design black box test cases:
Equivalence class partitioning
Boundary value analysis
Example 10.10 Design boundary value test suite for the function described in Example 10.6.
Answer: The equivalence classes have been showed in Figure 10.5. There is a boundary between
the valid and invalid equivalence classes. Thus, the boundary value test suite is {abcdefg,
abcdef}.
Figure 10.5: CFG for (a) sequence, (b) selection, and (c) iteration type of constructs.
WHITE-BOX TESTING
White-box testing is an important type of unit testing. A large number of white-box testing
strategies exist. Each testing strategy essentially designs test cases based on analysis of some
aspect of source code and is based on some heuristic.
Test data
Tests Derives
Component Test
code outputs
Basic Concepts
A white-box testing strategy can either be coverage-based or fault based.
Fault-based testing
A fault-based testing strategy targets to detect certain types of faults. These faults that a test
strategy focuses on constitute the fault model of the strategy. An example of a fault-based
strategy is mutation testing, which is discussed later in this section.
Coverage-based testing
A coverage-based testing strategy attempts to execute (or cover) certain elements of a program.
Popular examples of coverage-based testing strategies are statement coverage, branch coverage,
multiple condition coverage, and path coverage-based testing.
Testing criterion for coverage-based testing
A coverage-based testing strategy typically targets to execute (i.e., cover) certain program
elements for discovering failures. The set of specific program elements that a testing strategy
targets to execute is called the testing criterion of the strategy. For example, if a testing strategy
requires all the statements of a program to be executed at least once
Statement Coverage
The principal idea governing the statement coverage strategy is that unless a statement is
executed, there is no way to determine whether an error exists in that statement.
Example 10.11 Design statement coverage-based test suite for the following Euclid‘s GCD
computation program:
int computeGCD(x,y)
int x,y;
{
1 while (x != y){
2 if (x>y) then
3 x=x-y;
4 else y=y-x;
5}
6 return x;
}
Answer: To design the test cases for the statement coverage, the conditional expression of the
while statement needs to be made true and the conditional expression of the if statement needs to
be made both true and false. By choosing the test set {(x = 3, y = 3), (x = 4, y = 3), (x = 3, y =
4)}, all statements of the program would be executed at least once.
Branch Coverage
A test suite satisfies branch coverage, if it makes each branch condition in the program to assume
true and false values in turn. In other words, for branch coverage each branch in the CFG
representation of the program must be taken at least once, when the test suite is executed. Branch
testing is also known as edge testing, since in this testing scheme, each edge of a program‘s
Page 20 of 33
Path Coverage
A test suite achieves path coverage if it executes each linearly independent paths ( o r basis paths
) at least once. A linearly independent path can be defined in terms of the control flow graph
(CFG) of a program. Therefore, to understand path coverage-based testing strategy, we need to
first understand how the CFG of a program can be drawn.
Path
A path through a program is any node and edge sequence from the start node to a terminal node
of the control flow graph of a program.
Linearly independent set of paths (or basis path set)
If a set of paths is linearly independent of each other, then no path in the set can be obtained
through any linear operations (i.e., additions or subtractions) on the other paths in the set. Even
though it is straight forward to identify the linearly independent paths for simple programs, for
more complex programs it is not easy to determine the number of independent paths. In this
context, McCabe‘s cyclomatic complexity metric is an important result that lets us compute the
number of linearly independent paths for any arbitrary program.
V (G) = E – N + 2
Where, N is the number of nodes of the control flow graph and E is the number of edges in the
control flow graph. For the CFG of example shown in Figure 10.7, E = 7 and N = 6. Therefore,
the value of the Cyclomatic complexity = 7 – 6 + 2 = 3.
Method 2: An alternate way of computing the cyclomatic complexity of a program is based on a
visual inspection of the control flow graph is as follows
—In this method, the cyclomatic complexity V (G) for a graph G is given by the following
expression:
V(G) = Total number of non-overlapping bounded areas + 1
In the program‘s control flow graph G, any region enclosed by nodes and edges can be called as
a bounded area. This is an easy way to determine the McCabe‘s cyclomatic complexity. But,
what if the graph G is not planar (i.e., however you draw the graph, two or more edges always
intersect). Actually, it can be shown that control flow representation of structured programs
always yields planar graphs. But, presence of GOTO‘s can easily add intersecting edges.
Therefore, for non-structured programs, this way of computing the McCabe‘s cyclomatic
complexity does not apply.
Method 3: The cyclomatic complexity of a program can also be easily computed by computing
the number of decision and loop statements of the program. If N is the number of decision and
loop statements of a program, then the McCabe‘s metric is equal to N + 1.
How is path testing carried out by using computed McCabe’s cyclomatic metric value?
For the CFG of a moderately complex program segment of say 20 nodes and 25 edges, you may
need several days of effort to identify all the linearly independent paths in it and to design the
test cases. It is therefore impractical to require the test designers to identify all the linearly
independent paths in a code, and then design the test cases to force execution along each of the
identified paths. In practice, for path testing, usually the tester keeps on forming test cases with
random data and executes those until the required coverage is achieved.
value of ten or so. This is in contrast to the computational complexity that is based on the
execution of the program statements.
Estimation of testing effort:
Cyclomatic complexity is a measure of the maximum number of basis paths. Thus, it indicates
the minimum number of test cases required to achieve path coverage. Therefore, the testing
effort and the time required to test a piece of code satisfactorily is proportional to the cyclomatic
complexity of the code. To reduce testing effort, it is necessary to restrict the cyclomatic
complexity of every function to seven.
Estimation of program reliability:
Experimental studies indicate there exists a clear relationship between the McCabe‘s metric and
the number of errors latent in the code after testing. This relationship exists possibly due to the
correlation of cyclomatic complexity with the structural complexity of code. Usually the larger is
the structural complexity, the more difficult it is to test and debug the code.
Mutation Testing
All white-box testing strategies that we have discussed so far, are coverage-based testing
techniques. In contrast, mutation testing is a fault-based testing technique in the sense
that mutation test cases are designed to help detect specific types of faults in a program.
In mutation testing, a program is first tested by using an initial test suite designed by
using various white box testing strategies that we have discussed.
After the initial testing is complete, mutation testing can be taken up. The idea behind
mutation testing is to make a few arbitrary changes to a program at a time.
Each time the program is changed, it is called a mutated program and the change effected
is called a mutant.
An underlying assumption behind mutation testing is that all programming errors can be
expressed as a combination of simple errors. A mutation operator makes specific changes
to a program.
DEBUGGING
After a failure has been detected, it is necessary to first identify the program statement(s) that are
in error and are responsible for the failure, the error can then be fixed.
Debugging Approaches
The following are some of the approaches that are popularly adopted by the programmers for
debugging:
Page 24 of 33
In a high level programming languages, pointer variables and dynamic memory allocation
provide the capability for dynamic memory references. However, dynamic memory referencing
is a major source of programming errors in a program. Static analysis tools often summarize the
results of analysis of every function in a polar chart known as Kiviat Chart. A Kiviat Chart
typically shows the analyzed values for cyclomatic complexity, number of source lines,
Percentage of comment lines, Halstead‘s metrics, etc.
INTEGRATION TESTING
Integration testing is carried out after all (or at least some of ) the modules have been unit tested.
Successful completion of unit testing, to a large extent, ensures that the unit (or module) as a
whole works satisfactorily. In this context, the objective of integration testing is to detect the
errors at the module interfaces (call parameters).
For example, it is checked that no parameter mismatch occurs when one module invokes
the functionality of another module.
Page 26 of 33
Thus, the primary objective of integration testing is to test the module interfaces, i.e.,
there are no errors in parameter passing, when one module invokes the functionality of
another module.
The objective of integration testing is to check whether the different modules of a
program interface with each other properly.
During integration testing, different modules of a system are integrated in a planned
manner using an integration plan. The integration plan specifies the steps and the order in
which modules are combined to realize the full system.
After each integration step, the partially integrated system is tested. Following approaches can be
used to develop the test plan:
Big-bang approach to integration testing
Top-down approach to integration testing
Bottom-up approach to integration testing
Mixed (also called sandwiched ) approach to integration testing
Thus, in an object oriented program, unit testing would mean testing each object in isolation.
During integration testing (called cluster testing in the object-oriented testing literature) various
unit tested objects are integrated and tested.
Do Various Ob ject-orientation Features Make Testing Easy?
In this section, we discuss the implications of different object-orientation features in testing.
Encapsulation:
encapsulation feature helps in data abstraction, error isolation, and error prevention. However, as
far as testing is concerned, encapsulation is not an obstacle to testing, but leads to difficulty
during debugging. Encapsulation prevents the tester from accessing the data internal to an object.
Of course, it is possible that one can require classes to support state reporting methods to print
out all the data internal to an object. Thus, the encapsulation feature though makes testing
difficult; the difficulty can be overcome to some extent through use of appropriate state reporting
methods.
Inheritance:
The inheritance feature helps in code reuse and was expected to simplify testing. It was expected
that if a class is tested thoroughly, then the classes that are derived from this class would need
only incremental testing of the added features. However, this is not the case. Even if the base
class has been thoroughly tested, the methods inherited from the base class need to be tested
again in the derived class.
Dynamic binding:
Dynamic binding was introduced to make the code compact, elegant, and easily extensible.
However, as far as testing is concerned all possible bindings of a method call have to be
identified and tested. This is not easy since the bindings take place at run-time.
Object states:
In contrast to the procedures in a procedural program, objects store data permanently. As a result,
objects do have significant states. The behavior of an object is usually different in different
states. That is, some methods may not be active in some of its states. Also, a method may act
differently in different states. Objects are the basic unit of testing for object-oriented programs.
Besides this, there are many other significant differences as well between testing procedural and
object-oriented programs. For example, statement coverage-based testing which is popular for
testing procedural programs is not meaningful for object-oriented programs.
The reason is that inherited methods have to be retested in the derived class. In fact, the
different object- oriented features (inheritance, polymorphism, dynamic binding, state-
based behavior, etc.)
The various object-orientation features are explicit in the design models, and it is usually
difficult to extract from and analysis of the source code. As a result, the design model is a
valuable artifact for testing object-oriented programs.
Test cases are designed based on the design model. Therefore, this approach is
considered to be intermediate between a fully white-box and a fully black-box approach,
and is called a grey-box approach. Please note that grey-box testing is considered
important for object-oriented programs.
Grey-Box Testing of Object-oriented Programs
For object-oriented programs, several types of test cases can be designed based on the design
models of object-oriented programs. These are called the grey-box test cases. The following are
some important types of grey-box testing that can be carried on based on UML models:
State-model-based testing State coverage:
Page 29 of 33
SYSTEM TESTING
System tests are designed to validate a fully developed system to assure that it meets its
requirements. The test cases are therefore designed solely based on the SRS document. There are
essentially three main kinds of system testing depending on who carries out testing:
1. Alpha Testing: Alpha testing refers to the system testing carried out by the test team within
the developing organization.
2. Beta Testing: Beta testing is the system testing performed by a select group of friendly
customers.
3. Acceptance Testing: Acceptance testing is the system testing performed by the customer to
determine whether to accept the delivery of the system. The system test cases can be classified
into functionality and performance test cases. Before a fully integrated system is accepted for
system testing, smoke testing is performed.
Smoke testing is done to check whether at least the main functionalities of the software
are working properly. Unless the software is stable and at least the main functionalities
are working satisfactorily, System testing is not undertaken.
Page 30 of 33
Smoke testing is carried out before initiating system testing to ensure that system testing
would be meaningful, or whether many parts of the software would fail.
The idea behind smoke testing is that if the integrated program cannot pass even the basic
tests, it is not ready for a vigorous testing.
For smoke testing, a few test cases are designed to check whether the basic functionalities
are working.
For example, for a library automation system, the smoke tests may check whether books
can be created and deleted, whether member records can be created and deleted, and
whether books can be loaned and returned.
Performance Testing
Performance testing is an important type of system testing. Performance testing is carried out to
check whether the system meets the nonfunctional requirements identified in the SRS document.
There are several types of performance testing corresponding to various types of non-functional
requirements. For a specific system, the types of performance testing to be carried out on a
system depend on the different non-functional requirements of the system documented in its SRS
document. All performance tests can be considered as black-box tests.
Stress testing
Stress testing is also known as endurance testing. Stress testing evaluates system performance
when it is stressed for short periods of time. Stress tests are black-box tests which are designed to
impose a range of abnormal and even illegal input conditions so as to stress the capabilities of
the software. Input data volume, input data rate, processing time, utilization of memory, etc., are
tested beyond the designed capacity.
Volume testing
Volume testing checks whether the data structures (buffers, arrays, queues, stacks, etc.) have
been designed to successfully handle extraordinary situations. For example, the volume testing
for a compiler might be to check whether the symbol table overflows when a very large program
is compiled.
Configuration testing
Configuration testing is used Configuration testing is used to test system behavior in various
hardware and software configurations specified in the requirements. Sometimes systems are built
to work in different configurations for different users. For instance, a minimal system might be
required to serve a single user, and other extended configurations may be required to serve
additional users during configuration testing. The system is configured in each of the required
configurations and depending on the specific customer requirements, it is checked if the system
behaves correctly in all required configurations.
Compatibility testing
This type of testing is required when the system interfaces with external systems (e.g., databases,
servers, etc.). Compatibility aims to check whether the interfaces with the external systems are
performing as required. For instance, if the system needs to communicate with a large database
system to retrieve information, compatibility testing is required to test the speed and accuracy of
data retrieval.
Regression testing
This type of testing is required when software is maintained to fix some bugs or enhance
functionality, performance, etc.
Recovery testing
Page 31 of 33
Recovery testing tests the response of the system to the presence of faults, or loss of power,
devices, services, data, etc. The system is subjected to the loss of the mentioned resources (as
discussed in the SRS document) and it is checked if the system recovers satisfactorily. For
example, the printer can be disconnected to check if the system hangs. Or, the power may be shut
down to check the extent of data loss and corruption.
Maintenance testing
This addresses testing the diagnostic programs, and other procedures that are required to help
maintenance of the system. It is verified that the artifacts exist and they perform properly.
Documentation testing
It is checked whether the required user manual, maintenance manuals, and technical manuals
exist and are consistent. If the requirements specify the types of audience for which a specific
manual should be designed, then the manual is checked for compliance of this requirement.
Usability testing
Usability testing concerns checking the user interface to see if it meets all user requirements
concerning the user interface. During usability testing, the display screens, messages, report
formats, and other aspects relating to the user interface requirements are tested. A GUI being just
being functionally correct is not enough.
Security testing Security testing is essential for software that handle or process confidential data
that is to be guarded against pilfering. It needs to be tested whether the system is fool-proof from
security attacks such as intrusion by hackers.
Error Seeding
Sometimes customers specify the maximum number of residual errors that can be present
in the delivered software. These requirements are often expressed in terms of maximum
number of allowable errors per line of source code.
The error seeding technique can be used to estimate the number of residual errors in
software. Error seeding, as the name implies, it involves seeding the code with some
known errors.
In other words, some artificial errors are introduced (seeded) into the program. The
number of these seeded errors that are detected in the course of standard testing is
determined.
The number of errors remaining in the product. The effectiveness of the testing strategy.
Let N be the total number of defects in the system, and let n of these defects be found by
testing. Let S be the total number of seeded defects, and let s of these defects be found
during testing.
Regression testing
Regression testing spans unit, integration, and system testing. Instead, it is a separate dimension
to these three forms of testing. Regression testing is the practice of running an old test suite after
each change to the system or after each bug fix to ensure that no new bug has been introduced
due to the change or the bug fix. However, if only a few statements are changed, then the entire
test suite need not be run — only those test cases that test the functions and are likely to be
affected by the change need to be run. Whenever software is changed to either fix a bug, or
enhance or remove a feature, regression testing is carried out.
It is a way of software testing in which the It is a way of testing the software in which
internal structure or the program or the the tester has knowledge about the internal
code is hidden and nothing is known about structure or the code or the program of the
it. software.
Implementation of code is not needed for Code implementation is necessary for white
black box testing. box testing.
This testing can be initiated based on the This type of testing of software is started
requirement specifications document. after a detail design document.
It is the behavior testing of the software. It is the logic testing of the software.
Can be done by trial and error ways and Data domains along with inner or internal
methods. boundaries can be better tested.