Unit 3 Software Test QB With Answers
Unit 3 Software Test QB With Answers
UNIT III
TEST ADEQUACY CRITERIA AND TEST ENHANCEMENT
PART A
1. Define test adequacy criteria. Give an example. Also mention the application
scope of adequacy criteria,
Adequacy is measured for a given test set designed to test P to determine whether or
not P meets its requirements. This measurement is done against a given criterion C. A
test set is considered adequate with respect to criterion C when it satisfies C.
Example:
Consider a Program sumProduct must meet the following requirements:
R1 Input two integers, say x and y , from the standard input device.
R2.1 Find and print to the standard output device the sum of x and y if x<y .
R2.2 Find and print to the standard output device the product of x and y if x≥y.
Suppose now that the test adequacy criterion C is specified as:
C : A test T for program ( P, R ) is considered adequate if for each requirement r
in R there is at least one test case in T that tests the correctness of P with respect
to r .
Obviously, T={t: <x=2, y=3> is inadequate with respect to C for program
sumProduct. The lone test case t in T tests R1 and R2.1, but not R2.2.
Application scope of adequacy criteria:
✓ Helping testers to select properties of a program to focus on during test.
✓ Helping testers to select a test data set for a program based on the selected
properties.
✓ Supporting testers with the development of quantitative objectives for testing
Decision coverage (also known as branch coverage) requires that each possible
outcome of a decision (e.g., the true or false result of an entire if or while statement) is
tested. It focuses on testing each path that a program’s flow can take, ensuring that
both true and false results of the decision as a whole are covered. It ensures all
branches of the control flow are tested.
Example: For the decision (A && B), decision coverage requires testing only the
overall decision’s true and false outcomes, not each individual condition separately.
Condition coverage generally requires more test cases than decision coverage because
it tests individual conditions within compound decisions.
7. Outline the principles of mutation testing? List the two major assumptions of
mutation testing.
The principles of mutation testing guide the creation and analysis of mutants to
evaluate the effectiveness of a test suite. These principles ensure that mutation testing
accurately measures a test suite's ability to detect faults by simulating potential real-
world defects. Two key principles are
Competent Programmer Hypothesis
This principle assumes that developers generally write code that is close to correct,
containing only small, minor errors rather than severe or illogical mistakes. Mutants
are created by making small, realistic changes to the code, simulating the types of
mistakes competent programmers might make (e.g., changing operators, modifying
conditions).
PART B
11. Illustrate with an example of test adequacy criteria based on control flow.
Adequacy criteria based on control flow in software testing are focused on the
execution paths, branches, and structures within a program's flow of control. These
criteria determine the completeness of testing by analyzing how thoroughly test cases
exercise the various control structures in the code. The most common control flow
adequacy criteria include statement coverage, branch coverage, path coverage,
condition coverage, and loop coverage.
Statement Coverage
This is the most basic control flow criterion. It requires that every executable
statement in the program be executed at least once by the test cases. This ensures that
all parts of the code are at least touched, but it does not guarantee thorough testing of
decisions or conditions.
Any program written in a procedural language consists of a sequence of statements.
Some of these statements are declarative, such as the #define and int statements in C,
while others are executable, such as the assignment, if, and while statements in C and
Java.
Basic block is a sequence of consecutive statements that has exactly one entry point
and one exit point.
The statement coverage of T with respect to (P, R) is computed as Sc/(Se-Si) , where
Sc is the number of statements covered, Si is the number of unreachable statements,
and Se is the total number of statements in the program, i.e. the size of the coverage
domain.
T is considered adequate with respect to the statement coverage criterion if the
statement coverage of T with respect to (P, R) is 1.
Block Coverage
The block coverage of T with respect to (P, R) is computed as Bc/(Be -Bi) , where Bc is the
number of blocks covered, Bi is the number of unreachable blocks, and Be is the total number
of blocks in the program, i.e. the size of the block coverage domain. T is considered adequate
with respect to the block coverage criterion if the statement coverage of T with respect to (P,
R) is 1.
Path Coverage
Path coverage requires that every possible path through the program's control flow is
executed. This is a more exhaustive criterion because it considers all possible paths, including
different combinations of decisions. Path coverage can be challenging in complex programs,
as the number of paths increases exponentially with the number of decision points.
To achieve path coverage, you need to test all possible paths.
The example illustrates how and why decision coverage might help in revealing an error that
is not revealed by a test set adequate with respect to statement and block coverage. The
decision coverage of T with respect to (P, R ) is computed as Dc/(De -Di) , where Dc is the
number of decisions covered, Di is the number of infeasible decisions, and De is the total
number of decisions in the program, i.e. the size of the decision coverage domain. T is
considered adequate with respect to the decisions coverage criterion if the decision coverage
of T with respect to (P, R ) is 1.
The domain of decision coverage consists of all decisions in the program under test. Note that
each if and each while contribute to one decision whereas a switch contribute to more than
one.
A decision can be composed of a simple condition such as x<0 , or of a more
complex condition, such as (( x<0 AND y<0 ) OR ( p≥q )).
AND, OR, XOR are the logical operators that connect two or more simple
conditions to form a compound condition.
A simple condition is considered covered if it evaluates to true and false in one or
more executions of the program in which it occurs. A compound condition is
considered covered if each simple condition it is comprised of is also covered.
Decision coverage is concerned with the coverage of decisions regardless of whether or not a
decision corresponds to a simple or a compound condition. Thus, in the statement
there is only one decision that leads control to line 2 if the compound condition inside the if
evaluates to true. However, a compound condition might evaluate to true or false in one of
several ways.
The condition at line 1 evaluates to false when x≥0 regardless of the value of y.
Another condition, such as x<0 OR y<0, evaluates to true regardless of the value of
y, when x<0.
Condition Coverage
Condition coverage is more detailed than branch coverage. It requires that each individual
condition in a decision is evaluated to both true and false. This criterion applies when there
are multiple conditions in a decision statement, such as if (a && b).
Consider a compound condition with two or more simple conditions. Using condition
coverage on some compound condition C implies that each simple condition within C
has been evaluated to true and false.
However, does it imply that all combinations of the values of the individual simple
conditions in C have been exercised?
Adequacy criteria based on data flow in software testing focus on the paths of
variables in a program, particularly how they are defined, used, and modified. These
criteria ensure that test cases thoroughly cover the interactions of variables with the
program's logic, identifying issues like uninitialized variables, unnecessary assignments,
or improper usage.
Any path starting from a node at which variable x is defined and ending at a node at which
x is used, without redefining x anywhere else along the path, is a def-clear path for x.
Path 2-5 is def-clear for variable z defined at node 2 and used at node 5. Path 1-2-5 is NOT
def-clear for variable z defined at node 1 and used at node 5. Thus, definition of
z at node 2 is live at node 5 while that at node 1 is not live at node 5.
All-Defs Coverage
This criterion requires that each definition of a variable is tested with at least
one test case that follows a path where that definition is used (either in computation or
as part of a decision). Not every use of the variable needs to be tested, but at least one
for each definition must be covered.
All-Uses Coverage
This criterion ensures that for every definition of a variable, test cases cover
all uses (both C-Use and P-Use) of that variable, including all possible paths from the
definition to the use.
All-DU-Paths Coverage
This criterion is more stringent than all-uses coverage. It requires that test
cases not only cover all possible uses of a variable but also every distinct path from
the variable's definition to its use. This helps to ensure that every possible interaction
between a variable’s definition and its subsequent usage is tested.
All-P-Uses/Some-C-Uses Coverage
This criterion requires that all predicate uses of variables are covered (all
decisions or conditions where a variable is involved are tested), while some
computational uses are tested. This ensures that variables affecting decisions are
thoroughly tested.
All-C-Uses/Some-P-Uses Coverage
This is the reverse of the previous criterion. It ensures that all computational uses
of variables are covered (variables used in expressions or calculations), while some of the
predicate uses are tested.
The principles of mutation testing establish a foundation for generating mutants and
analyzing test effectiveness. These principles help ensure that mutation testing is
meaningful, efficient, and provides useful insights into the quality of the test suite.
Killable Mutant
• A mutant is considered “killed” if a test case causes it to behave differently
from the original program, resulting in different outputs.
• This principle allows mutation testing to evaluate a test suite’s effectiveness
by measuring how many mutants it can kill, highlighting gaps in test coverage
where improvements may be needed.
Equivalent Mutant
• Equivalent mutants are mutants that behave identically to the original program
for all possible inputs. They cannot be killed by any test case and are generally
undesirable in mutation testing.
• While it is challenging to detect equivalent mutants automatically, mutation
testing seeks to minimize their impact. Identifying and removing these
mutants helps improve the efficiency and accuracy of mutation testing results,
as equivalent mutants artificially reduce the mutation score.
Selective Mutant
• Instead of generating all possible mutants, selective mutation focuses on
creating only those mutants that are most likely to reveal weaknesses in the
test suite.
• Selective mutation reduces the computational costs of mutation testing by
using a subset of mutation operators that are known to represent common
types of errors. This makes the process more efficient while still providing
useful insights into the quality of the test suite.
Mutation score
• The mutation score is calculated as the ratio of killed mutants to the total
number of non-equivalent mutants, providing a measure of the test suite’s
effectiveness.
• A high mutation score indicates that the test suite is robust and capable of
detecting a wide range of faults, while a low mutation score suggests that the
test suite may need additional test cases to cover untested scenarios.
Realistic Fault Simulation
• This principle emphasizes that mutants should reflect realistic bugs that could
be introduced into the code, such as off-by-one errors or incorrect operator
usage.
• By focusing on realistic faults, mutation testing becomes more practical and
relevant, enhancing its value as a measure of test effectiveness for real-world
applications.
Test Suite Effectiveness
• Mutation testing is designed to measure the effectiveness of a test suite by
determining whether it can detect and isolate bugs represented by the
generated mutants.
• This principle underscores that mutation testing is a tool to assess and improve
the quality of the test suite, rather than the correctness of the code itself.
Let S1 and S2 denote two sets of mutation operators for language L. Based on the
effectiveness criteria, we say that S1 is superior to S2 if mutants generated using S1
guarantee a larger number of errors detected over a set of erroneous programs.
1. Arithmetic Operators
Simulate common arithmetic errors.
Examples: Replace + with -, * with /, % with *, etc.
Mutation Example:
// Original code
int result = a + b;
// Mutant
int result = a - b;
2. Relational Operators
Simulate logic errors in conditions.
Examples: Replace < with <=, > with <, == with !=, etc.
Mutation Example:
// Original code
if (x > y) { /* ... */ }
// Mutant
if (x >= y) { /* ... */ }
3. Logical Operators
Test logical conditions by changing logical operators.
Examples: Replace && with ||, || with &&.
Mutation Example:
// Original code
if (a > 5 && b < 10) { /* ... */ }
// Mutant
if (a > 5 || b < 10) { /* ... */ }
4. Conditional Operators
Test expressions within conditional statements.
Examples: Change the ternary condition ? :.
Mutation Example:
// Original code
int max = (a > b) ? a : b;
// Mutant
int max = (a > b) ? b : a;
5. Unary Operators
Simulate errors with incrementing and decrementing.
Examples: Change ++ to --, + to - for unary operations.
Mutation Example:
// Original code
x++;
// Mutant
x--;
6. Assignment Operators
Test errors with assignments and compound operators.
Examples: Replace += with -=, *= with /=.
Mutation Example:
// Original code
a += b;
// Mutant
a -= b;
7. Constant Replacement
Simulate errors by altering constant values.
Examples: Replace constants with 0, 1, or another value.
Mutation Example:
// Original code
int maxAttempts = 5;
// Mutant
int maxAttempts = 1;
8. Variable Replacement
Check for errors due to incorrect variable usage.
Examples: Replace one variable with another variable of the same type.
Mutation Example:
// Original code
int result = a + b;
// Mutant
int result = a + c;
9. Statement Deletion
Simulate missing code lines, such as skipped initializations or increments.
Examples: Delete critical lines like increment statements in loops.
Mutation Example:
// Original code
x = x + 1;
int y = x * 2;
// Mutant (deleted statement)
int y = x * 2;
Consider a code
boolean isPrime(int number) {
if (number <= 1) return false;
for (int i = 2; i < number; i++) {
if (number % i == 0) return false;
}
return true;
}
Using fault-based mutation, we can introduce specific faults that are common in
similar code:
Boundary Condition Fault: Modify the loop condition to test for off-by-one errors, a
common boundary fault.
Mutant:
for (int i = 2; i <= number; i++) { // Changed < to <=
if (number % i == 0) return false;
}