Unit 4 Coding and Testing
Unit 4 Coding and Testing
Unit 4 Coding and Testing
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.
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:
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.
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.
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.
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
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
How to test a program?
Testing a program involves executing the program with a set of test inputs and observing if the
program behaves as expected. If the program fails to behave as expected, then the input data and
the conditions under which it fails are noted for later debugging and error correction.
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:
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.
Verification versus validation
The objectives of both verification and validation techniques are very similar since both these
techniques are designed to help remove errors in software.
Verification is the process of determining whether the output of one phase of software
development conforms to that of its previous phase; whereas validation is the process of
determining whether fully developed software conforms to its requirements specification.
For example, a verification step can be to check if the design documents produced after
the design step conform to the requirements specification. On the other hand, validation is
applied to the fully developed and integrated software to check if it satisfies the
customer‘s requirements.
The primary techniques used for verification include review, simulation, formal
verification, and testing. Review, simulation, and testing are usually considered as
informal verification techniques. Formal verification usually involves use of theorem
proving techniques or use of automated tools such as a model checker. On the other hand,
validation techniques are primarily based on product testing. Note that we have
categorized testing both under program verification and validation. The reason being that
unit and integration testing can be considered as verification steps where it is verified
whether the code is a s per the module and module interface specifications. On the other
hand, system testing can be considered as a validation step where it is determined
whether the fully developed code is as per its requirements specification.
Verification does not require execution of the software, whereas validation requires
execution of the software.
Verification is carried out during the development process to check if the development
activities are proceeding alright, whereas validation is carried out to check if the right as
required by the customer has been developed.
We can therefore say that the primary objective of the verification steps are to determine
whether the steps in product development are being carried out alright, whereas
validation is carried out towards the end of the development process to determine
whether the right product has been developed.
While verification is concerned with phase containment of errors, the aim of validation is
to check whether the deliverable software is error free.
To achieve high product reliability in a cost-effective manner, a development team needs
to perform both verification and validation activities. The activities involved in these two
types of bug detection techniques together are called the ―V and V‖ activities.
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.
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;
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.
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.
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
Figure 10.5: CFG for (a) sequence, (b) selection, and (c) iteration type of constructs.
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
control flow graph is traversed atleast once.
Theorem 10.1 Branch coverage-based testing is stronger than statement coverage-based testing.
Proof: We need to show that (a) branch coverage ensures statement coverage, and (b) statement
coverage does not ensure branch coverage.
(a) Branch testing would guarantee statement coverage since every statement must belong to
some branch (assuming that there is no unreachable code).
(b) To show that statement coverage does not ensure branch coverage, it would be sufficient to
give an example of a test suite that achieves statement coverage, but does not cover at least one
branch. Consider the following code, and the test suite {5}.
if(x>2) x+=1;
The test suite would achieve statement coverage. However, it does not achieve branch coverage,
since the condition (x > 2) is not made false by any test case in the suite.
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.
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.
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:
Brute force method
This is the most common method of debugging but is the least efficient method. Single stepping
using a symbolic debugger is another form of this approach, where the developer mentally
computes the expected result after every source instruction and checks whether the same is
computed by single stepping through the program.
Backtracking
This is also a fairly common approach. In this approach, starting from the statement at which an
error symptom has been observed, the source code is traced backwards until the error is
discovered. Unfortunately, as the number of source lines to be traced back increases, the number
of potential backward paths increases and may become unmanageably large for complex
programs, limiting the use of this approach.
Cause elimination method
In this approach, once a failure is observed, the symptoms of the failure (i.e., certain variable is
having a negative value though it should be positive, etc.) are noted. Based on the failure
symptoms, the causes which could possibly have contributed to the symptom is developed and
tests are conducted to eliminate each. A related technique of identification of the error from the
error symptom is the software fault tree analysis.
Program slicing
This technique is similar to back tracking. In the backtracking approach, one often has to
examine a large number of statements. However, the search space is reduced by defining slices.
A slice of a program for a particular variable and at a particular statement is the set of source
lines preceding this statement that can influence the value of that variable [Mund2002]. Program
slicing makes use of the fact that an error in the value of a variable can be caused by the
statements on which it is data dependent.
Debugging Guidelines
Debugging is often carried out by programmers based on their ingenuity and experience. The
following are some general guidelines for effective debugging:
Many times debugging requires a thorough understanding of the program design. Trying
to debug based on a partial understanding of the program design may require an
inordinate amount of effort to be put into debugging even for simple problems.
Debugging may sometimes even require full redesign of the system. In such cases, a
common mistake that novice programmers often make is attempting not to fix the error
but its symptoms.
One must be beware of the possibility that an error correction may introduce new errors.
Therefore after every round of error-fixing, regression testing (see Section 10.13) must be
carried out.
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.
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
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.
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
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.