Modern Software Testing Techniques
Modern Software Testing Techniques
Testing Techniques
A Practical Guide for Developers
and Testers
—
István Forgács
Attila Kovács
Modern Software
Testing Techniques
A Practical Guide
for Developers and Testers
István Forgács
Attila Kovács
Modern Software Testing Techniques: A Practical Guide for Developers
and Testers
István Forgács Attila Kovács
Budapest, Hungary Budapest, Hungary
Acknowledgments�������������������������������������������������������������������������������xi
Introduction���������������������������������������������������������������������������������������xiii
Abbreviations�����������������������������������������������������������������������������������xvii
iii
Table of Contents
Stateless Modeling���������������������������������������������������������������������������������������������59
Use Case Testing�������������������������������������������������������������������������������������������68
Stateful Modeling������������������������������������������������������������������������������������������������72
FSM and EFSM-Based Modeling�������������������������������������������������������������������74
How to Select States?�����������������������������������������������������������������������������������77
Model Maintenance���������������������������������������������������������������������������������������80
How to Create a Stateful Model – Example���������������������������������������������������81
Efficiency, Advantages, and Disadvantages���������������������������������������������������88
Stateless and Stateful Together – Action-State Testing��������������������������������������90
The Action-State Model���������������������������������������������������������������������������������93
Test Selection Criteria for Action-State Testing���������������������������������������������98
Creating Action-State Model������������������������������������������������������������������������100
Comparison with Stateful Modeling������������������������������������������������������������108
How a Real Bug Can Be Detected?�������������������������������������������������������������������112
Summary����������������������������������������������������������������������������������������������������������115
iv
Table of Contents
Chapter 5: Conclusion�����������������������������������������������������������������������229
Appendixes���������������������������������������������������������������������������������������233
Glossary��������������������������������������������������������������������������������������������247
References����������������������������������������������������������������������������������������251
Index�������������������������������������������������������������������������������������������������257
v
About the Authors
István Forgács, PhD, was originally a
researcher at the Computer and Automation
Research Institute of the Hungarian Academy
of Sciences. He has had more than 25 scientific
articles published in leading international
journals and conference proceedings. He
is the co-author of the book Agile Testing
Foundations: An ISTQB Foundation Level
Agile Tester guide and the book Practical Test
Design: Selection of Traditional and Automated Test Design Techniques. His
research interests include test design, agile testing, model-based testing,
debugging, code comprehension, and static and dynamic analysis. He left
his academic life in 1998 to be a founder of Y2KO, the startup company
that offered an efficient solution to the Y2K project. He is the founder
and Chief Executive Officer of 4Test-Plus and is a former CEO of 4D Soft.
He is an author of the Advanced Test Analyst Working Group and former
member of the Agile Working Group of ISTQB. István is the creator and
key contributor of the only two-phase model-based test automation tool
Harmony.
vii
About the Authors
viii
About the Technical Reviewer
Kenneth Fukizi is a software engineer,
architect, and consultant with experience in
coding on different platforms internationally.
Prior to dedicated software development, he
worked as a lecturer and was then head of
IT at different organizations. He has domain
experience working with technology for
companies mainly in the financial sector.
When he’s not working, he likes reading up on emerging technologies and
strives to be an active member of the software community.
Kenneth currently leads a community of African developers through a
startup company called AfrikanCoder.
ix
Acknowledgments
We are grateful to all of those whom we have had the pleasure to work
with during this book-writing project: colleagues, friends, the Apress and
Springer Nature teams, and especially Kenneth Fukizi.
We are grateful to Bernát Kalló for continuously helping us with good
pieces of advice when Harmony’s test design features were implemented.
István: I would like to thank my wife Margó who helped me a lot and
made everything to remain healthy and young.
Attila: I want to express my gratitude to my students and colleagues,
as well as to my children, Bendegúz and Barnabás, my parents, and my
beloved partner.
xi
Introduction
Oh my gosh, yet another book about software testing…
Indeed, many books have already been written about software
testing. Most of them discuss the general framework of testing from
different perspectives. Of course, in a test project, the methodological and
organizational aspects are essential. However, excellent test organization
is only one component of testing effectiveness. You need software that
has only a few or no bugs. This can be achieved if the tests can catch all or
almost all the faults.
The goal is to create efficient and effective tests. All comprehensive
books about software testing that include test design contain the same
story: simple examples about use case testing, equivalence partitioning
(EP), boundary value analysis (BVA), state transition testing (STT), etc.
The present situation of software test design is like classical mechanics:
almost nothing has changed since Newton. Current materials about
software testing and especially about software test design are either trivial
or contradictory or even obsolete. The existing test design techniques are
only best practices; the applied techniques are nonvalidated regarding
bug-revealing capability and reliability. For example, current EP and BVA
techniques give nonreliable results even in the case of a single predicate
stating that two or three test cases are enough. The situation with use
case testing is not better; that is, several bugs remain undetected. Modern
testing needs more efficient test techniques and test models.
In this book, we show a basic classification of the software bugs from
the viewpoint of functional specification. We show why traditional test
design techniques are ineffective, and we present reliable and efficient
solutions for revealing different types of faults. We are convinced that new
xiii
Introduction
xiv
Introduction
In our exercise platform, you are not only able to check your
knowledge, but the platform also explains how you might improve it, that
is, which tests are missing. We suggest trying to solve some exercises by
first applying the traditional and then the new techniques. You will see the
difference. By applying the new techniques, the test cases you design will
find almost all the bugs.
This book is both for developers and testers. Chapter 1 is a clear and
short summary of software testing; our developer and tester readers will
find it useful. It contains sections explaining why risk analysis is obligatory,
how to classify bugs practically, and how fault-based testing can be used
for improving test design. The last part of this book contains a chapter
on how developers and testers can help each other and work as an
excellent team.
If you are interested in studying traditional software test design more
deeply, we suggest reading our previous book Practical Test Design. That
book contains nontrivial examples, explanations, and techniques, and we
think that the previous book is an excellent warm-up to this one. On the
other hand, this book can be read independently from the previous one.
We hope we can support your career (as a developer or tester) with this
book. Our goal is not just to sell books but to improve the overall quality of
software, where satisfied customers happily use the applications offered
to them.
xv
Abbreviations
AST action-state testing
BVA boundary value analysis
CI/CD continuous integration/continuous deployment
CPH competent programmer hypothesis
CTH competent tester hypothesis
DAG directed acyclic graph
DevOps development and operations
DP defect prevention
EFSM extended finite state machine
EP equivalence partitioning
F false
FSM finite state machine
ISO International Organization for Standardization
ISTQB International Software Testing Qualifications Board
LEA learn, exercise, apply
MBT model-based testing
ODT optimized domain testing
PSP personal software process
SDLC software development life cycle
xvii
Abbreviations
xviii
CHAPTER 1
Software Testing
Basics
This chapter overviews the basics of software testing from the point of view
of bugs: lifetime, classifications, pursuing processes, and various pesticides
against bugs.
Estimated Time
• Intermediates: 80 minutes.
2
Chapter 1 Software Testing Basics
3
Chapter 1 Software Testing Basics
4
Chapter 1 Software Testing Basics
analyzing the root causes, and (3) feedback on the results for process
improvement (see Forgács et al. 2019). There are numerous techniques for
preventing bugs:
• Apply checklists.
The first two are related to improving the requirements, the others
are related to improving the code, moreover, the third one improves both.
The first four are “real” preventions as they happen before coding, the
others just before testing. Refactoring is a common prevention technique
used during maintenance. Defect prevention is a cheap solution while
defect correction is more expensive. That’s why defect prevention is valid;
moreover, it is obligatory. Clearly, the main target of any quality assurance
task is to prevent defects.
Fault detection can be made ad hoc and can be semistructured
or structured. The most common structured ways are the black-box
(specification-based) and white-box (structure-based) methods.
In black-box testing, the tester doesn’t need to be aware of how the
software has been implemented, and in many cases, the software source
is not even available. Equivalently, the tester knows only the specification.
What matters is whether the functionality follows the specification or
not. Black-box testing usually consists of functional tests where the tester
enters the input parameters and checks whether the application behaves
5
Chapter 1 Software Testing Basics
correctly and properly handles normal and abnormal events. The most
important step for producing test cases in black-box testing is called
test design.
In contrast, white-box testing is performed based on the structure of
the test object, or more specifically, the tester knows and understands
the code structure of the program. Regarding the code, white-box testing
can be done by testers, but it’s more often done by the developers on their
own. The process of producing white-box testing is called test creation (test
generation).
We note that both black-box and white-box testing can be applied at
any test level. At the unit level, a set of methods or functions implementing
a single functionality should be tested against the specification. At the
system or acceptance level, the whole application is tested. Black-box
testing can be successfully performed without programming knowledge
(however, domain knowledge is an advantage), but white-box testing
requires a certain level of technical knowledge and developers’
involvement. Writing automated test code for black-box and white-box
testing needs programming skills. Nowadays, codeless test automation
tools allow any tester to automate tests. There are plenty of tools
supporting test automation (both free and proprietary). Test automation is
essential for continuous integration and DevOps.
When testing is performed based on the tester’s experience, in the ad
hoc case, we speak about error guessing; in the semistructured case, we
speak about exploratory testing. A special case of the latter is called session-
based testing (Bach 2000).
Besides the mentioned software testing types, there are various
methods for fault detection, such as graph-based approaches (searching
for erroneous control flow dynamic), classifiers (based on machine
learning or Bayesian aiming at identifying abnormal events), and
data-trace pattern analyzers. But none of these methods have been proven
to be efficient in practice yet (however, in some “limited” situations, they
can be applied).
6
Chapter 1 Software Testing Basics
In this book, we primarily focus on the first and most important step
in the fight against bugs: test design. We consider test design as a defect
prevention strategy. Considering the “official” definition, test design is the
“activity of deriving and specifying test cases from test conditions,” where
a test condition is a “test aspect of a component or system identified as a
basis for testing.” Going forward, the test basis is “the body of knowledge
used as the basis for test analysis and design.”
Let’s make it clearer. Requirements or user stories with acceptance
criteria determine what you should test (test objects and test conditions),
and from this, you have to figure out the way of testing; that is, design the
test cases.
One of the most important questions is the following: what are the
requirements and prerequisites of successful test design? If you read
different blogs, articles, or books, you will find the following:
Do you agree? If you don’t have enough time or money, then you will
not design the tests. If there is no testing experience, then no design is
needed, because “it doesn’t matter anyway.” Does everyone mean the
same thing when they use the terms “coverage” and “confidence level”? If
you are agile, you don’t need to spend time designing tests anymore. Is it
not necessary to design, maintain, and then redesign automated tests?
7
Chapter 1 Software Testing Basics
8
Chapter 1 Software Testing Basics
Classification of Bugs
Software faults can be classified into various categories based on their
nature and characteristics.
Almost 50 years ago, Howden (1976) published his famous paper
“Reliability of the path analysis testing strategy.” He showed that
“there is no procedure which, given an arbitrary program P and output
specification, will produce a nonempty finite test set T, subset of the input
domain D, such that if P is correct on T, then P is correct on all of D. The
reason behind this result is that the nonexistent procedure is expected to
work for all programs, and thus, the familiar noncomputability limitations
are encountered.” What does it mean? In simpler terms, the sad reality is
9
Chapter 1 Software Testing Basics
10
Chapter 1 Software Testing Basics
1. Domain error
2. Computation error
11
Chapter 1 Software Testing Basics
when both y and x are equal to 2, the result will be 4 for both paths. Our
technique for finding the computation errors is not (weak) reliable;
however, in practice, it can find most of the bugs.
Software Testing
Testing Life Cycle
This subsection is a review. If you are an experienced software tester, you
can skip it, except “Test Analysis”. If you are a developer, we suggest reading
it to get acquainted with the viewpoints and tasks of a tester.
You cannot design your tests if you don’t understand the whole test
process. We mentioned that the selected test design techniques strongly
depend on the results of the risk analysis. Similarly, test creation at the
implementation phase is an extension of the test design. Figure 1-1 shows
the relevant entities of the traditional testing life cycle including the test
design activities.
12
Chapter 1 Software Testing Basics
Test Planning
The test planning process determines the scope, objective, approach,
resources, and schedule of the intended test activities. During test
planning – among others – the test objectives, test items, features to be
tested, the testing tasks, the test approach, human and other resources, the
degree of tester independence, the test environment, entry and exit criteria
to be used, and any risks requiring contingency planning are identified.
A test policy defines the testing philosophy and the goals that the
organization wishes to achieve through testing activities, selecting the
frames that testing parties should adhere to and follow. It should apply to
both new projects and maintenance work.
13
Chapter 1 Software Testing Basics
Test Analysis
The test engineering activity in the fundamental test process begins mainly
with the test analysis. Test analysis is the process of looking at something that
can be used to derive quality information for the software product. The test
analysis process is based on appropriate project documents or knowledge,
called the test basis, on which the tests are based. The most important thing
in test analysis is the opportunity to better understand the problem we are
working on and to anticipate possible problems that may occur in the future.
14
Chapter 1 Software Testing Basics
The test analysis phase has three main steps before the review:
15
Chapter 1 Software Testing Basics
16
Chapter 1 Software Testing Basics
Figure 1-2. Optimum of the correcting cost and the testing cost
This means that it’s not a good strategy to reduce testing and testing
costs as you should consider these two factors together. The main question
is the following. Does this cost optimum result in an acceptable production
code quality? Suppose that you reached the optimum, yet the quality of
the code is not acceptable. Then you should correct some annoying bugs
by which the total correcting cost increases. You do not have any choice
as you should fix the bugs until the quality of the code is acceptable;
otherwise, your company will be negatively impacted. Consequently, if
you reach the optimum, then your code is fine. However, reaching this
optimum is difficult.
Now we can answer the question of why risk analysis is needed. Let’s
consider two implemented functions and assume that the first one is
more complex (in some aspect). Hence, it contains faults with a higher
probability. If we test both at the same expense and thoroughness, then
after testing, more faults will be left in the first code. It means that the total
correcting cost will be higher.
We can use similar arguments for other test objects. Suppose that
we have two functions having the same complexity and testing cost (the
distribution of faults is similar), but the first one is riskier. For example,
this function is used more often, or the function is used in a “critical”
17
Chapter 1 Software Testing Basics
flow. Therefore, more bugs will be detected and fixed in this function with
a higher correcting cost during the life cycle. Note that we are speaking
about the detection of bugs here, not about the presence of bugs.
Roughly speaking, more complex code and higher risk raise the
bug-fixing costs if the testing effort/cost remains unchanged. Since this
additional bug fixing occurs later in the life cycle, the code quality at
release will be poorer. Therefore, it is important that a riskier or more
complex code part should be tested with more thoroughness, that is, with
higher testing costs. This higher testing cost is inevitable to achieve the
optimal total cost as shown in Figure 1-3.
Figure 1-3. Converging to the optimal cost. The total cost optimum
of a high-risk function (HRF) is above the total cost optimum of a
low-risk function (LRF). However, this optimum can be reached via
spending more money on testing (multiple designs, etc.)
It means that for riskier or more complex code, you should select
stronger test design techniques. OK, but how can you reach this optimum?
This is not an easy task, and we have no complete answer. But we do
think this is possible. First, risk and complexity analyses must be done for
each software element, and the assessed risk and complexity data should
be stored. Second, the company should collect and store all the testing
18
Chapter 1 Software Testing Basics
and defect correction costs. Third, the company must define and apply
different strategies for optimizing the costs based on the measured data on
different levels of risks and complexities. This selection can be validated
and improved periodically to approach the optimum. We suggest building
the proposed model into the company’s process improvement strategy.
Finally, you should apply the most efficient techniques. Don’t use
combinatorial testing unless a cheaper choice is appropriate. Use more
techniques in parallel as they can find different bugs. A good solution
for risky code is to apply use case testing or state-based testing with
“cheap” test selection criterion, equivalence partitioning, boundary value
analysis, and combinative testing together. They are not expensive, and
you will find most of the bugs.
In the following two chapters, we introduce new test design techniques
that shift the optimum right and down. The optimum strongly depends on
the applied test design techniques.
Test Design
Test design can be performed in seven steps (see Figure 1-1):
19
Chapter 1 Software Testing Basics
20
Chapter 1 Software Testing Basics
21
Chapter 1 Software Testing Basics
22
Chapter 1 Software Testing Basics
23
Chapter 1 Software Testing Basics
24
Chapter 1 Software Testing Basics
25
Chapter 1 Software Testing Basics
Test Closure
The test closure is a complete test report that gives a summary of the test
project. It formally closes the project, collates all the test results, provides
a detailed analysis, presents metrics to clients, and adjudicates the risks
concerning the software.
Fault-Based Testing
Both developers and testers need technical and nontechnical skills.
Moreover, those skills are equally important. To get and control their
technical skills, however, developers can exercise coding on many different
online platforms. There are tasks at various levels, and the courses involve
hints and help when you need it. On the other hand, by implementing the
code, the developer can easily check the expected result, that is, comparing
the output of the code with the requirements.
This is not true for testing. In some sense, testing is more difficult than
coding as validating the efficiency of the test cases (i.e., the goodness of
your tests) is much harder than validating code correctness. In practice,
the tests are just executed without any validation (with just a few
counterexamples, see Kovács and Szabados 2016). On the contrary, the
26
Chapter 1 Software Testing Basics
27
Chapter 1 Software Testing Basics
alternatives, and thus we cannot validate the “goodness” of our test set.
Fortunately, it was shown that by testing a certain restricted class of faults,
a wide class of faults can also be found (Offutt 1992). The set of faults
is commonly restricted by two principles: the competent programmer
hypothesis and the coupling effect. We suggest reading a nice survey on
mutation testing (Jia and Harman 2011).
The competent programmer hypothesis (CPH) was introduced by
(Hamlet 1977) and (DeMillo et al. 1978), who observed that “Programmers
have one great advantage that is almost never exploited: they create
programs that are close to being correct.” Developers do not implement
software randomly. They start from a specification, and the software will
be very similar to their expectations, hence, close to the specification.
Coupling effect hypothesis means that complex faults are coupled to
simple faults in such a way that a test data set detecting all simple faults
in a program will detect a high percentage of the complex faults as well.
A simple fault is a fault that can be fixed by making a single change to a
source statement. A complex fault is a fault that cannot be fixed by making
a single change to a source statement. If this hypothesis holds, then it is
enough to consider simple faults, that is, faults where the correct code is
modified (mutated) by a single change (mutation operator). Thus, we can
apply mutation testing to get an efficient test design technique with an
appropriate test selection criterion.
As mentioned, mutation testing is the most common form of fault-
based testing, in which by slightly modifying the original code, we create
several mutants. A reliable test data set should then differentiate the
original code from the well-selected mutants. In mutation testing, we
introduce faults into the code to see the reliability of our test design.
Therefore, mutation testing is actually not testing, but “testing the tests.”
A reliable test data set must “kill” all of them. A test kills a mutant if the
original code and the mutant behave differently. For example, if the code
is y = x and the mutant is y = 2 * x, then a test case x = 0 does not kill the
mutant while x = 1 does.
28
Chapter 1 Software Testing Basics
If a mutant hasn’t been killed, then the reasons can be the following:
29
Chapter 1 Software Testing Basics
30
Chapter 1 Software Testing Basics
31
Chapter 1 Software Testing Basics
32
Chapter 1 Software Testing Basics
Testing Principles
Testing principles are fundamental guidelines and concepts that serve as
the foundation of effective and efficient software testing. These principles
help ensure that software testing is conducted systematically and
thoroughly to identify defects, verify software functionality, and control
overall software quality. In the following, we encounter the key testing
principles and their significance:
1. Testing is Possible
Software testing is possible due to the coupling effect hypothesis (CEH).
Without CEH, only exhaustive testing could be applied. However, it is
impossible to test all possible input combinations and scenarios for a
complex software system. Testing efforts should focus primarily on critical
and high-risk areas to maximize the likelihood of identifying significant
33
Chapter 1 Software Testing Basics
34
Chapter 1 Software Testing Basics
4. Continuity of Testing
Continuous development requires continuous testing and continuous
feedback. Clearly, running the same set of tests for changed requirements
will not find new defects. Testers must review and update test cases according
to the new or modified requirements. This involves the deletion of obsolete
test cases. Fortunately, modern methods such as modeling the test design
make it possible. Continuous development necessitates continuous testing
and feedback as an integral part of its iterative and agile nature. Embracing
these practices enhances software quality, responsiveness to change, and
collaboration between development teams and stakeholders, ultimately
leading to better software products and improved customer satisfaction.
Continuous feedback, which includes input from end users and stakeholders,
helps shape the software according to customer needs and expectations.
Integrating customer feedback into the development process results
in more customer-centric products. Continuous testing is an essential
component of CI/CD pipelines. It enables automated testing at every stage
of the deployment process, ensuring that only thoroughly tested and verified
code is released to production. Continuous testing aids in risk mitigation by
continuously assessing the impact of new changes on the existing code base.
It reduces the chances of introducing unintended side effects. Continuous
35
Chapter 1 Software Testing Basics
5. Defect Clustering
Defects tend to cluster around specific modules or functionalities of the
software. By identifying and addressing these high-defect areas, testing
efforts can be prioritized effectively. In the following, we explain the
reasons behind this. The uninterested reader can skip this section.
36
Chapter 1 Software Testing Basics
37
Chapter 1 Software Testing Basics
38
Chapter 1 Software Testing Basics
Two Misconceptions
It is NOT an applicable principle that testing shows the presence, not the
absence of defects quited by Dijstra, see (Buxton at al 1969). It is only for a
good excuse for lazy testers. Even if testing doesn’t result in the absence of
defects, it may assure the absence of a class of defects. Properly structured
tests and formal methods can demonstrate the absence of errors
(Goodenough, Gerhart 1975). However, it is unknown how to scale formal
software engineering. In this book, we provide a technique, by which for an
error class, a reliable test set can be constructed (apart from coincidental
correctness).
Most testers know and believe in the pesticide paradox as it’s often
some questions at ISTQB exams. Originally, Beizer (1990) wrote: “Every
method you use to prevent or find bugs leaves a residue of subtler bugs
against which those methods are ineffectual.” This paradox can be
rewritten as “If the same set of repetitive tests is conducted, the method
will be useless for discovering new defects,” or “if the same set of test cases
are executed again and again over the period of time, then these set of tests
are not capable enough to identify new defects in the system.” In simple
words: tests wear out. The meaning of these descriptions is significantly
different from what Beizer stated, but the main problem is that it is wrong.
The truth is that if you have a good test set, it remains good and you
can use it after years. Let’s assume that we have the correct software and a
reliable test set detecting any potential defect. Without loss of generality,
we can assume that the software is deterministic. Imagine that we make
all the possible modifications for the software and each modified version
remains deterministic. Some modifications introduce new bugs into the
39
Chapter 1 Software Testing Basics
software. Now let’s use our test set for all the modified pieces of code.
There are two possibilities:
Let’s consider a scenario where the software’s code changes after some
time and a bug is introduced. Because the test set detected the bug earlier
it will continue to do so since both the test set and the modified software
remain the same and the software operates deterministically. This is true
without doing all the possible modifications. Consequently, the test set
never becomes outdated or less effective than when it was initially created.
On the other hand, if the functional requirements changed, then new tests
should be designed for the modified functionality. However, this is a quite
different problem.
• Pesticide paradox.
• Testing shows the presence of defects.
1. Testing is possible.
40
Chapter 1 Software Testing Basics
3. Continuity of testing.
Summary
In this chapter, we first looked at bugs and explained why the coupling
effect hypothesis makes it possible to use test design for detecting bugs. We
have shown the requirements for a good test design through which most
bugs can be detected. We have introduced a new classification of bugs,
where a bug is either a control flow bug or a computational bug from the
requirements specification point of view.
The next part is devoted to the fundamentals of software testing,
where we briefly describe the software testing life cycle. We looked at test
planning and test analysis and showed the importance of risk analysis.
Then we moved on to test design, test implementation and execution, and
finally test closure.
We showed how mutation tests verify our designed test sets and
how to measure test efficiency. We described the connection between
requirements and software testing. Finally, we renewed the principles
of software testing, removing some and adding new ones. We have also
shown why some of the original principles are wrong or not useful.
41
CHAPTER 2
Test Design
Automation by
Model-Based Testing
In this chapter, you’ll discover the significance of automating test design,
a crucial aspect of test automation. The primary approach for automated
test design is called model-based testing (MBT), which is widely used.
MBT methods are divided into one-phase and two-phase model-based
testing. We explain the advantages of the latter approach over the
traditional one. Additionally, we categorize models into three types:
stateless, stateful, and mixed models. We illustrate that stateless models
are less effective in identifying defects. On the other hand, the stateful
solution can be challenging to apply to certain requirements. As a result,
the mixed model solution emerges as the most favorable option. This
content should be understood within the reading time.
• Beginners: 5 hours
• Intermediates: 4 hours
• Experts: 3 hours
Higher-Order Bugs
A system (or software) is called stateless if it has no memory of the previous
interactions. It means computational independence on any preceding
events in a sequence of interactions. A stateless application decouples the
computations from the states; it is dependent only on the input parameters
that are supplied. Simple examples are
44
Chapter 2 Test Design Automation by Model-Based Testing
Online Shop
Company RedShoe is selling shoes in its online shop.
OS-R1 If the total ordering price is below EUR 100, then no price reduction is given.
OS-R2 The customer gets a 4% reduction when reaching or exceeding a total price
of EUR 100. Over a value of EUR 200, the customer gets an 8% reduction.
OS-R3 If the customer is a premium VIP, then she gets an extra 3% reduction. If
the customer is a normal VIP, they get a 1% extra reduction. Normal VIPs must be
registered, and the customer is a premium VIP if in the past year, the amount of their
purchases has reached a certain limit. The system automatically calculates the VIP
status.
OS-R4 If the customer pays immediately at the end of the order, she gets an
additional 3% reduction in price.
OS-R5 The output is the reduced price to be paid. The lowest price difference
(accuracy) is 10 euro cents.
45
Chapter 2 Test Design Automation by Model-Based Testing
For the first-order bug, only the variable price is responsible: if your test
contains the value price = 200, then the bug will be detected; otherwise,
it isn’t. Boundary value analysis is reliable for this bug. Recall that a test
selection technique is reliable for a bug; if applied, it certainly detects
the bug.
For the second-order bug, you should set two variables; that is, the
variables vip and prepay must be set to find the bug. The bug is only
detected if vip ≠ premium and prepay = True. See Table 2-1.
46
Chapter 2 Test Design Automation by Model-Based Testing
Table 2-1. Decision table for the Online Shop application. You can
read the table as “if VIP status is ‘None’ and Prepay is ‘True,’ then the
reduction is 3% in case of the correct implementation earlier and zero
in case of the incorrect implementation.” The other columns can be
read similarly
VIP Status None Normal Premium
Unfortunately, boundary value analysis is not reliable for this bug. You
can see that a second-order bug can occur even in the case of a single data
fault. Having a second-order bug only means that it can be revealed by
setting two variables.
Consider an application where a free bike is given when you rent three
cars, but the bike is withdrawn if you delete one of them. A simple bug
happened by changing a parameter from False to True during a function
call. You can find the bug if you add three cars, delete one of them, and
finally add a bike. But the bug remains undetected when you add a bike
first, then three cars, and finally delete a car. This is clearly a higher-order
bug; however, here, not the input values but the order of the inputs is the
clue for detecting the bug. We note here that to detect higher-order bugs by
applying white-box methods, different dataflow-based adequacy criteria
are available in the literature (Rapps et al. 1982, Korel et al. 1988).
In the following, we enumerate some real examples of higher-order
bugs found by the authors.
The first is an email service. When first signing in, it works correctly.
However, by adding another account and signing in for the second time,
the screen is frozen (some workaround had to be performed for signing in).
47
Chapter 2 Test Design Automation by Model-Based Testing
Model-Based Testing
Model-based testing (MBT) has become more and more popular in recent
years. MBT is used for validating requirements, that is, for software defect
prevention, for shared understanding, for generating executable test
cases, and so on. In this book, we restrict our attention to automated test
design for functional testing, and we use MBT and automated test design
interchangeably.
48
Chapter 2 Test Design Automation by Model-Based Testing
One-Phase (Traditional)
Model-Based Testing
Various MBT tools employ diverse models, yet they share a common
feature: all these models are considered computer-readable. This means
that the model should be understandable for a computer program that
reads, scans, and processes the model and generates executable test cases.
The traditional model-based testing process is the following:
Step 1. Creation of the MBT model
49
Chapter 2 Test Design Automation by Model-Based Testing
50
Chapter 2 Test Design Automation by Model-Based Testing
51
Chapter 2 Test Design Automation by Model-Based Testing
52
Chapter 2 Test Design Automation by Model-Based Testing
results in four test cases, but for larger systems, this criterion would lead to
too many test cases.
Step 3. Generation of semi-abstract test cases
The third step automatically generates test cases from MBT models
based on the test selection criterion. Most articles refer to these test cases
as “abstract” as these test cases haven’t been executable yet. However,
these test cases are computer-readable. That’s why we will call them
semi-abstract test cases. These test cases involve steps that contain enough
information to be able to convert them into executable tests. Here is an
example of a test step for a web application:
Here “When” and “is” are keywords for humans to understand test
steps better. add Coke is the selector, that is, the identifier of the GUI
object, and #pressed is a command or action word. The selector connects
the test case and the application under test; the action word maps the
action to be done to the code to be executed.
The test cases consist of implementation-dependent steps such as
pressing a button or validating that some field is disabled. These model
elements are difficult to include before the implementation as the
developers usually have some freedom regarding the implementation.
Therefore, traditional modeling is usually started when the
implementation is ready. In some cases, if screens for the features are
planned, then models can be created, and tests can be generated in
parallel with the implementation.
Step 4. Generation of executable test cases
With the help of a test adaptation layer, the test cases can be concretized.
In this step, the test design automation tool generates the test code. The
commands are replaced by the test code that is available before the model is
created. However, mapping the test code and the software under test (SUT)
usually happens only when the application is ready. Here is an example:
53
Chapter 2 Test Design Automation by Model-Based Testing
54
Chapter 2 Test Design Automation by Model-Based Testing
step. Note that this is not a capture-and-replay technique as the model and
the semi-abstract test cases are ready. During capture and replay, the test
cases are generated during the test execution, and not before.
Step 5. Test execution and test result analysis
The executable test cases are executed against the SUT by a test runner.
The result of the test design automation is the executable test code. Test
execution is the next step of the test automation. Test result analysis is
outside of the entire test automation as it is made by a team to improve the
application and the whole process.
1. High-level modeling
2. Low-level modeling
The high-level model is human-readable and can be done before
implementation. The low-level model is computer-readable, and it’s
generated from the high-level model during manual test execution. The
two-phase MBT process is the following:
Step 1. Choosing appropriate test selection criteria
Here, the process changed as the first step is to select the appropriate
test selection criterion. The reason is that the modeling is driven by the test
selection criterion. It means that the missing model steps are displayed
and offered to insert.
55
Chapter 2 Test Design Automation by Model-Based Testing
or
Let’s consider our account creation example. You can see that the steps
are higher level, and the graph is more understandable. Only five actions
remained; originally there were nine (see Figure 2-2).
56
Chapter 2 Test Design Automation by Model-Based Testing
Let’s consider the validations in the models. How to model the outputs
during high-level modeling? It’s much simpler since outputs are also
abstract. For example, if we should validate a whole screen as a response
to an action, then the tester can do it later when the test is executed. The
model should only contain “=> check the screen appears.” When the test
is executed, the tester can check everything similarly to use exploratory
testing (see Step 3). However, in some cases, concrete output values can be
added to the model.
Step 3. Generating the abstract test cases
Based on the model, the abstract test cases are generated. These
test cases are only executable by humans and can be considered as “live
documentation.” The abstract tests are also examples of how the system
works, and with them, the requirement specification can be validated.
This is very important as requirement specification is usually not complete
and error-prone leading to some false or missing implementation. The
cheapest way to fix the specification problems is the validation of the
high-level model or abstract test cases. We will see concrete examples for
improving the specification later. This is an excellent defect prevention
method as an incomplete or faulty requirement leads to faulty code, but
these test cases can be used against implementation leading to fewer
defects. This is not only an efficient defect prevention but also cheap as
it’s a side effect of test design automation. It usually cannot be done by
57
Chapter 2 Test Design Automation by Model-Based Testing
applying the one-phase approach as the models are created after the
implementation; otherwise, lots of remodeling work should be done.
Step 4. Test execution and low-level model generation plus test
execution
The prerequisite of this step is the implemented SUT. The tester
executes the abstract test cases one by one, while the test design
automation tool generates the low-level model step. For example, if the
high-level model step is “add pizzas to reach EUR 40,” the tester first selects
the “shopping” feature, then selects a Pizza Chicken for 12 euros twice and
a Pizza Smoked Salmon for 16 euros. While clicking the selection and then
the add buttons (three times), the system generates model steps such as
When Shopping is #pressed
When add button for Pizza Chicken is #pressed
When add button for Pizza Chicken is #pressed
When add button for Pizza Smoked Salmon is #pressed
These model steps are the same as in the traditional model steps in
Step 3 and are computer-readable. The output is also generated on the fly.
In most cases, the output is a value or the visibility of some UI elements.
Therefore, the validation step is usually an additional selection, where
the output value or the visible UI object is selected. Other validation
commands such as “non-visible” or “active” can also be selected.
Besides the generation of the low-level model, the executable test
code is also generated, and the test step is executed immediately. If a step
fails, it can be fixed and executed again. When the test execution has been
finished, the test case is automated, and no debugging of the test steps
is needed.
The whole process is in Figure 2-3.
58
Chapter 2 Test Design Automation by Model-Based Testing
Stateless Modeling
As previously mentioned, models can be categorized as stateful, stateless,
or a combination of both. A stateless model primarily comprises user
actions (inputs) and system responses (outputs) but does not incorporate
any states. In the realm of software test design automation, stateless
modeling stands out as the most prevalent technique due to its simplicity
and applicability to a wide range of systems. This approach involves
modeling business processes to depict the dynamic aspects of the systems
59
Chapter 2 Test Design Automation by Model-Based Testing
being tested. One of the closely related test design techniques is known
as use case testing. Additionally, there exist several other techniques for
describing the system’s behavior, which can also be employed in software
testing similar to use cases. This involves navigating through the diagrams
based on a test selection criterion. Examples of such models include
BPMN, UML activity diagrams, and so forth. Although these solutions
employ different notations, they share substantial similarities in terms of
the information they encompass.
A crucial consideration lies in the fact that developing a stateless
model for test design automation should significantly deviate from the
process of modeling for system design. In this context, we aim to illustrate
these distinctions by presenting examples of both an incorrect and a
well-constructed model.
We show a simple example to demonstrate all the mentioned modeling
techniques. In this way, we can compare the error-reveling capability of
the different models.
Our example in the following is a simplified specification of our car
rental exercise from the website test-design.org.
A rental company loans cars (EUR 300) and bikes (EUR 100) for a week.
R1 The customer can add cars or bikes one by one to the rental order.
R2 The customer can remove cars or bikes one by one from the rental order.
R3 If the customer rents cars for more than EUR 600, they become eligible to
receive a complimentary bike rental as part of a discount offer:
R3a If the customer has selected some bikes previously, then one of them
becomes free.
R3b If the customer hasn’t selected any bike previously, then one free bike is
added.
60
Chapter 2 Test Design Automation by Model-Based Testing
R4 If the customer deletes some cars from the order so that the discount threshold
doesn’t hold, then the free bike will be withdrawn.
R4a When the discount is withdrawn but given again, and no bike is added,
meanwhile, the customer gets the previous discount back.
R4b When the discount is withdrawn and some bikes are added when the
discount is given again, then one of them becomes free.
61
Chapter 2 Test Design Automation by Model-Based Testing
Figure 2-4. Stateless model for car rental - not for testing
This model permits any valid test case as any add car/add bike/delete
car/delete bike sequence can be traversed in the graph. Initially, when the
cart is empty, a car or a bike can only be added.
Unfortunately, though this model may be good for system planning, it’s
unusable for testing. The problem is that this model ignores to test some
of the requirements. Even if every potential test case can be generated, we
should include a very strong test selection criterion that results in a very
high number of superfluous test cases.
Let’s consider R4b. To test this, we should add three cars and delete
one, then we should add a bike and finally add a car gain. The path in the
graph is
Cart is empty: (1) add car – car added, (2) add car – car added, (3) add
car – car added, (4) delete car – car deleted, (5) add bike – bike added, (6)
add car – car added
62
Chapter 2 Test Design Automation by Model-Based Testing
63
Chapter 2 Test Design Automation by Model-Based Testing
Excellent, we covered most requirements with just two test cases. Let’s
execute the tests, and we are done. Well, unfortunately, not. These two
tests detect only 40% of the bugs, that is, 6 out of the 15 defects, and that’s
very few. Covering R2, no more defects are detected. Our example is not
too complex, and if we could find only 40%, then there are other systems
where covering the requirement specification is not enough. We examined
other exercises on our website, and the result was the same. Thus, we can
conclude that
64
Chapter 2 Test Design Automation by Model-Based Testing
The only missing action is to insert a single car when the cart
is empty. However, should there be a modification to R3, such that
acquiring a complimentary bike entails adding two cars instead of three,
the associated model would then encompass all the required actions.
Consequently, we believe that we require a nearly identical test case,
with the exception that “three cars” would be revised to “two cars” and
“two cars” would be adjusted to “one car.” With these adjustments, the
model can be regarded as comprehensive. Unfortunately, a test generator
may not generate T1 and T2 earlier as actions can be covered in many
ways. The solution is that when the “requirement model” is ready, the
generated test cases are stored and added to the test cases generated for
the whole model.
65
Chapter 2 Test Design Automation by Model-Based Testing
Considering the generated test cases, there are some invalid ones such as
since there is no bike in the cart. To resolve this problem, some conditions
should be added to the actions. In computer science, similar conditions are
called guard conditions. Unfortunately, for stateless modeling, it hasn’t been
used so far, but we think that using guard conditions is a general method
for generating executable test cases from any model. Guard conditions are
inserted to the edges as text in square brackets (see, e.g., Forgacs et al. 2019).
In our preceding test case, the guard condition is [bike >= 1]. In addition, the
number of bikes should be set after the action: bike++.
You can see that the system responses (nodes) don’t contain the
output values generally. Here, the number of bikes/cars is easy to involve,
but the total price is not easy and it’s better adding to the test cases when
they are generated. By applying the two-phase modeling approach, the
testers can validate and accept/decline the result of the test execution.
Satisfying the (minimally expected) all-edge criterion, the number of
test cases is 17 for this model. We should add our original two test cases;
thus, the total number of test cases is 19. With these 19 test cases, 80% of
the bugs were detected, that is, 3 bugs escaped. This result is acceptable
as the mutant was carefully created introducing tricky bugs. The test cases
can be found in Appendix II.
This served as a rationale for why automated test design is
essential. When developers write test code based on requirements without
employing proper test design, it leads to poor quality. Manual test design
is a time-consuming and exhausting process. Summarizing, here is the
process of how to use stateless MBT:
66
Chapter 2 Test Design Automation by Model-Based Testing
67
Chapter 2 Test Design Automation by Model-Based Testing
68
Chapter 2 Test Design Automation by Model-Based Testing
69
Chapter 2 Test Design Automation by Model-Based Testing
ATM Authentication
R1 At the beginning, the ATM system is in a state of waiting, anticipating the
insertion of a card. Once a valid card is inserted, it then awaits the entry of the PIN
code.
R2 For an invalid card, the ATM system ejects the card and waits for another one.
R3 If the user enters a valid PIN code within the first three attempts, the
authentication process is considered successful.
R4 After the user enters an invalid PIN code on the first or second try, the user will
be asked to re-enter the PIN code.
R5 If the user enters an incorrect PIN code for the third time, the card will be
blocked, and the ATM goes back to the initial (waiting for cards) state.
70
Chapter 2 Test Design Automation by Model-Based Testing
1 Insert card.
Main scenario Check validity.
2
(happy path) Ask for PIN.
3 Enter correct PIN.
4 Check correctness.
5 Authenticated, access allowed.
3a Enter wrong PIN.
Alternatives Check correctness and ask for re-
4a
entering.
2a For invalid card it is rejected.
For incorrect PIN for the 3rd
Exceptions time, the card is blocked, and the
4b
system waits for inserting
another card
Figure 2-8. Use case model except for the ATM authentication
requirements
Based on Figure 2-8, the tester can apply different models (sequence
diagrams, activity diagrams, or statecharts). As we mentioned, use cases
are excellent for the developers and for the business analysts, but as a test
generation model, use cases are not reliable for detecting higher-order
bugs and in their current form, not applicable for test design automation.
Summarizing the basic pitfalls:
1. Hard to find higher-order bugs. In this example,
covering all the direct routes from 1 to 5 or to 4b, we
will not cover all the necessary tests, for example,
1 – 2 – 3a – 4a – 3 – 4 – 5, as loops are not included in
use case testing.
2. When creating use case steps, there is not any
control or feedback with respect to how additional
steps should be easily involved.
71
Chapter 2 Test Design Automation by Model-Based Testing
That’s why use cases are by no means invented for test design
automation. Besides, as previously mentioned, they are not reliable for
detecting higher-order bugs as reliable testing may require performing
actions several times arriving at different states. Unfortunately, there is no
reliable test selection criterion for this.
Stateful Modeling
Most complex systems are stateful: the behavior of the features depends
heavily on their internal structure. Different mathematical solutions can
be used to model stateful software systems: finite state machines (FSMs),
Petri nets, timed automata, and so on. Finite state machines are one of
the main formalisms to describe stateful software. Some major drawbacks
of FSMs go back to their inherent sequential and nonhierarchical nature.
Since an FSM can only model the control part of a system, an extension
was needed to be able to model both control and data flows, for example,
communication protocols. Such systems are usually represented by an
72
Chapter 2 Test Design Automation by Model-Based Testing
73
Chapter 2 Test Design Automation by Model-Based Testing
74
Chapter 2 Test Design Automation by Model-Based Testing
Transition
Action Activity Response
Nr.
Invalid card,
1 Insert Card Check validity
eject card
Valid card,
2 Insert Card Check validity
Ask for PIN
Correct PIN,
3 Enter PIN Check correctness
Authentication successful
Incorrect PIN,
4 Enter PIN Check correctness
second try is possible
Correct PIN,
5 Enter PIN Check correctness
Authentication successful
Incorrect PIN,
6 Enter PIN Check correctness
third try is possible
Correct PIN,
7 Enter PIN Check correctness
Authentication successful
Incorrect PIN,
8 Enter PIN Check correctness
Card blocked
The advantage of the above model is that all the transitions are valid,
and any Chow-type test selection criteria can be applied. However, the
model can be huge if, for example, the user is allowed to experiment with
incorrect PINs, let us say, 20 times. Unfortunately, except for the trivial
cases, the FSM models produce large automata in terms of states and
transitions.
75
Chapter 2 Test Design Automation by Model-Based Testing
Transition Guard
Action Activity Response
Nr. Condition
Invalid card,
1 Insert card Check validity
eject card
Check validity, asks
2 Insert card Valid card
for PIN, try = 0
Enter wrong Check correctness,
3 try < 3 Incorrect PIN
PIN try += 1
Enter wrong Incorrect PIN,
4 try = 3 Check correctness
PIN Card blocked
Correct PIN,
Enter correct
5 Check correctness Authentication
PIN
successful
76
Chapter 2 Test Design Automation by Model-Based Testing
77
Chapter 2 Test Design Automation by Model-Based Testing
78
Chapter 2 Test Design Automation by Model-Based Testing
79
Chapter 2 Test Design Automation by Model-Based Testing
Model Maintenance
When the requirements are changing, and new states or transitions must
be integrated continuously, the state model easily becomes messy and
error-prone. It is especially true for the test model: missing or superfluous
transitions need to be identified, and sometimes the whole test model
needs to be redesigned.
A significant challenge in this context is the generation of new
infeasible paths when modifying the graph. When the graph undergoes
changes, it’s essential to regenerate the test cases. However, this can result
in a different test case set, and identifying the newly generated test cases
becomes crucial. Without such identification, you may need to recheck all
the test cases for feasibility, which can be time-consuming. As of our last
knowledge update in September 2023, there may not have been automated
tools readily available for automatically comparing old and new test cases
after sophisticated graph modifications. This could indeed be a complex
task, especially when dealing with substantial changes to the graph
structure.
To address this challenge, you might consider developing custom
scripts or tools that can assist in comparing old and new test cases. These
tools could analyze the changes in the graph and the corresponding effects
on the test cases, allowing you to pinpoint which test cases are new or need
revalidation. Such a custom solution would likely depend on the specific
modeling and testing environment you’re working with. Additionally, it’s
worth keeping an eye on developments in the field of software testing and
model-based testing as new tools and techniques may have emerged since
our last knowledge update that could address this issue more effectively.
Consulting with experts in software testing and model-based testing
communities or conducting research in this area could also yield insights
into potential solutions and tools that have been developed since then.
80
Chapter 2 Test Design Automation by Model-Based Testing
81
Chapter 2 Test Design Automation by Model-Based Testing
82
Chapter 2 Test Design Automation by Model-Based Testing
It’s okay as from State 4, we should traverse each action such as “add
bike” or “add car,” and we cannot do this before step #4.
Hence, we have the following states:
• no discount
• discount withdrawn
With these four states, you can more effectively test various scenarios
and cover the corresponding requirements, including R4a and R4b, while
satisfying the all-transition criterion.
Figure 2-11 shows the state transition graph.
83
Chapter 2 Test Design Automation by Model-Based Testing
84
Chapter 2 Test Design Automation by Model-Based Testing
However, to cover “(3) add car,” we should cover “add bike” previously;
otherwise there is no bike to be converted and add car twice reach (but not
exceed) 600. The order of these actions can be any, but any order will cover
R1, R3a, and R4a. With this, we showed that T1 must be covered satisfying
the all-edge criterion. The interesting reader can show it for T2 covering
R1, R3b, R4b as well.
So the requirements are covered, and the actions from each state are
also covered; thus, we can think that the model is good. However, after a
closer look at the model, we can observe some disturbing things: when the
third car is added to the cart, and no bike is present, a free bike is added as
part of the promotion. If you later decide to delete the free bike from the cart,
then we have two possibilities. The first is that the total price remains and
85
Chapter 2 Test Design Automation by Model-Based Testing
the discount is lost. The other possibility is that the total price is reduced by
100 and the discount remains. The product owner should decide which one
to be selected getting the higher profit. But the key thing is that there is no
requirement for this; that is, the requirement specification is incomplete.
Here we select the requirement where the total price remains. We should
add a requirement R5 to make the requirements complete.
A rental company loans cars (EUR 300) and bikes (EUR 100) for a week.
R1 The customer can add cars or bikes one by one to the rental order.
R2 The customer can remove cars or bikes one by one from the rental order.
R3 If the customer rents cars for more than EUR 600, then they can rent one bike for
free. In case of discount:
R3a If the customer has selected some bikes previously, then one of them
becomes free.
R3b If the customer hasn’t selected any bike previously, then one free bike is
added.
R4 If the customer deletes some cars from the order so that the discount threshold
doesn’t hold, then the free bike will be withdrawn.
R4a When the discount is withdrawn but given again, and no bike is added
meanwhile, the customer gets the previous discount back.
R4b When the discount is withdrawn and some bikes are added, when the
discount is given again, then one of them becomes free.
R5 If the customer deletes the free bike, then no money discount is given. Adding
the bike back the discount is given again.
Note that if there are more bikes, one converted, then deleting a
bike is not the free one. Though this is not in the specification, it’s quite
reasonable. Now the requirement specification seems to be complete
thanks to the excellent defect prevention capability of the stateful
modeling. Now we extend our model to include R5. Fortunately, it’s
enough to add actions “delete bike” and “add bike” from and to the
86
Chapter 2 Test Design Automation by Model-Based Testing
states “discount, bike converted” and “discount, bike freely added” to the
state “no discount,” respectively. The new “add bike” actions cannot be
traversed from the initial state only if we deleted the bike at the discount
states previously. The extended model is shown in Figure 2-13.
Outputs
The model doesn’t include outputs. Here we should include abstract
outputs not to go outside traditional state machines. Therefore two-phase
MBT should be applied. We leave Inserting the abstract outputs to the
interesting readers.
Guard Conditions
As in the case of the stateless method, invalid paths may be generated.
For example, there is an action add car from no discount to itself. However,
this is only feasible if the number of cars is less than two. Thus, a guard
condition is required:
numberOfCars < 2.
It’s simple; however, if there were more vehicle types with different
prices, the guard condition should be
87
Chapter 2 Test Design Automation by Model-Based Testing
But the total price is output; thus, testers should code it according
to the requirements. It’s not a real solution as outputs will only be
implemented in the application. Thus, we should use concrete input
values and calculate the result as we did in the former guard condition. If
there were more vehicles, we would select some concrete ones such as
Requirement Traceability
As mentioned, requirement traceability is a must even if covering the
requirements is not enough. But it’s not an easy task. Let’s consider R4b:
When the discount is withdrawn and some bikes are added, when the
discount is given again, then one of them becomes free.
88
Chapter 2 Test Design Automation by Model-Based Testing
The test cases can be found in Appendix III, and you can compare
them with the tests for the stateless method. Note that this is not for just
this example as a stateful solution found almost every bug in each exercise
on our website.
The only undetected bug can be found in the following test case:
This is a test case that doesn’t necessarily test the requirement. The last
step can be covered in another test case such as
• Add car
• Delete car
• …
89
Chapter 2 Test Design Automation by Model-Based Testing
For some systems, it’s not an easy task to involve states. In these cases,
the stateless solution is simpler and leads to the same result considering
defect detection. That may be the main reason why state transition testing
is not widely used among testers and many fewer tools implementing it
exist. Such tools are Opkey and Conformiq Creator.
90
Chapter 2 Test Design Automation by Model-Based Testing
Action
Response
Test state
• Add car
• Delete car
• Add bike
• Delete bike
91
Chapter 2 Test Design Automation by Model-Based Testing
Here, the response should include a login with the new password, and
that login with the old one is not possible.
Making the model is comfortable. We start from an initial state and
investigate which appropriate actions can be performed. Then, based on
a requirement, we add an action for which the system gives a response
92
Chapter 2 Test Design Automation by Model-Based Testing
and arrives at a particular test state. The most critical activity of the tester
is to select the appropriate test state. When the step is ready, we can add
other steps.
Action-state testing is very flexible. You can omit states in which
case the method is reduced to stateless testing. You can omit responses
resulting in simple action-state pairs. Responses can be ignored if they
were formerly checked, or it is better to check at a later step. In some cases,
there are lots of results to validate. For example, when a new screen with
lots of data appears. In this case, you can add more responses instead of
one with a very complex description. You can also omit both responses
and states, for example, by pressing a button to start a new test case.
93
Chapter 2 Test Design Automation by Model-Based Testing
Figure 2-15. The action-state graph model. The nodes are the
actions; the edges denote the precedence relation in the test sequence
This graph expresses that step 2 follows step 1, step 3 follows step 2,
and both step 4 and step 5 follow step 3.
In order to make the modeling easier, the model is textual, but the
result is mixed, graphical, and textual. For better comprehension, we apply
graph terminology. We use syntax close to the one implemented in the test
design automation tool Harmony. The action-state model can be created
as follows:
• Graph and step
Steps are considered as nodes in the graph. Model steps
are created as simple text where all steps start with an
action that can be followed by a response (or more)
such as action ⇒ response. The arriving state is written
in capital. If step 2 is executed immediately after step 1,
then step 2 is a child of step 1, and it is written with the
94
Chapter 2 Test Design Automation by Model-Based Testing
The graph shows that the second step (second line) will
be executed right after the first step.
• Label
95
Chapter 2 Test Design Automation by Model-Based Testing
• Fork
96
Chapter 2 Test Design Automation by Model-Based Testing
• Join
97
Chapter 2 Test Design Automation by Model-Based Testing
• Initial state
• Model hierarchy
Testers can easily create almost any graph or model they want without
graph drawing knowledge. Modification of the graph is also easy. Testers
can move a node with the whole step to another place.
98
Chapter 2 Test Design Automation by Model-Based Testing
99
Chapter 2 Test Design Automation by Model-Based Testing
100
Chapter 2 Test Design Automation by Model-Based Testing
By applying the two-phase MBT, the tester should only validate these
outputs instead of calculating them in advance. Using a tool such as
Harmony, the validation includes the acceptance of the results. During the
acceptance, the low-level model steps and the test code are generated.
Let’s continue with covering R3b. It can only be covered with a
separate test case. Thus, we need a fork after the first step. One branch is
for testing R3a, and we add another branch where we add the second and
the third car, without adding a bike:
101
Chapter 2 Test Design Automation by Model-Based Testing
Now, let’s cover R4a and R4b, and when we do this, we also cover R2. For
R4a, we delete a car, delete the bike, and add a car again. Deleting the bike
will result in reaching the state “discount, bike freely added.” For R4b, we also
delete a car, but add a bike, before we add the car back. Here is the model:
102
Chapter 2 Test Design Automation by Model-Based Testing
Finally, let’s cover R5. It’s easy, just delete and add a bike starting from
the state “discount, bike freely added.” Oops! What happens when we add
the bike back? What is the resulting state? We can go back to the previous
state, or we can go to the “discount, bike converted” state. Both can be
reasonable:
The fact is that at present, our requirements are still not complete. To
make it complete, we select (2) and slightly modify R5 in the following way:
103
Chapter 2 Test Design Automation by Model-Based Testing
the same step, only in some subsequent steps. Here, this state can be
validated by removing a car. In this case, the expected result is that the bike
remains and the total price is 700 (instead of removing the bike). Here, it is
advisable to add a more specific response. The model is as follows:
Three test cases are generated from this model, and the related
statechart is as shown in Figure 2-16.
104
Chapter 2 Test Design Automation by Model-Based Testing
105
Chapter 2 Test Design Automation by Model-Based Testing
T3
add car – car added
add car – 2nd car added
add car – 3rd car added
delete car – 3rd car deleted
add bike – bike added
add car – 3rd car added
The key difference is that by executing only these three test cases, 73%
of the bugs have been detected; that is, only four bugs remain undetected.
This is a much better result than the 40% of the stateless solution, but it’s
definitely not enough to get high-quality software.
Based on this, the missing actions, responses, and states can be added.
For example, from “no discount” actions, “delete car” and “delete bike”
should be added. For simplicity and understandability, we add steps
omitting responses. In this way, you can easily differentiate the new steps.
On the other hand, the actions are enough to validate our test cases as we
use mutation testing; that is, we test against mutants. The final model is on
the next page.
106
Chapter 2 Test Design Automation by Model-Based Testing
107
Chapter 2 Test Design Automation by Model-Based Testing
As all four possible actions (add car, add bike, delete car, delete bike)
can be added in sequence, the number of test cases remains only eight.
With these test cases, all the bugs have been found. Recall the missing test
of stateful modeling:
• Delete a car
• Delete a car
This contains an additional test step “add bike,” but this test is as
efficient as the shorter one. The test cases can be found in Appendix IV.
When you create steps, you should check the state the action arrives at. Sometimes
the state can be validated by adding more steps. Don’t forget to add these steps to
the model.
108
Chapter 2 Test Design Automation by Model-Based Testing
Here the action is covered three times. However, this is not the FSM
solution where there are different states after each wrong PIN is entered.
Thus, if the number of trials was 10, then we can add an action: “add
wrong PIN eight times.” If the test case is invalid, the only cause is that
some action goes to a different state than it should be. Therefore, it’s also a
validation possibility.
The other difference is that states are optional. If the risk is low or the
states are difficult to create, then you can use a stateless solution. Newbies
can also use this solution in the beginning. It is also possible that you write
one step with a state and the subsequent step without a state.
The third difference is that you can generate an alternative model, that
is, a statechart to validate your model. Other models can only be checked
by reviewing themselves.
The fourth difference is that the modeler can write simple text
extended by tabulating and forking. It’s much simpler, and the graph is
more understandable. Here is a comparison of the traditional stateless
model and the two-phase action-state model. We compare the login
feature as shown in Figures 2-17a and 2-17b.
109
Chapter 2 Test Design Automation by Model-Based Testing
110
Chapter 2 Test Design Automation by Model-Based Testing
The model in Figure 2-17b clearly shows the three test cases. The
traditional models need to be traversed by your eyes. In more complex
situations, it’s very difficult to figure out the test cases. For the action-
state model for even more complex features, the graph remains clear and
understandable.
Finally, the last difference is that stateful modeling considers actions
as states as different entities. A state may have several incoming actions,
which are handled together. The states are not validated. In action-state
testing, we handle each (action, response, state) triple together. We can
detect if a state should be validated and whether an additional step is
necessary to do it. That’s why we can detect more bugs with this technique.
Now we summarize the advantages and disadvantages of action-state
modeling:
Advantages of action-state testing:
111
Chapter 2 Test Design Automation by Model-Based Testing
112
Chapter 2 Test Design Automation by Model-Based Testing
There are actions that will probably modify the editor’s state such as
Actions 1 and 2 result in the same state. Similar is the case for 3 and 4.
Undo and redo should be done at each state. We think that (d) and (e) can
be tested for other features. We made the following model:
113
Chapter 2 Test Design Automation by Model-Based Testing
We omit most of the responses for clarity. You can see that we have
18 test cases so that we avoid combinations. Let’s execute test MB9 where
the initial state is having two lines with track changes off:
1. set track changes on
114
Chapter 2 Test Design Automation by Model-Based Testing
Summary
For practical systems, mainly linear test design techniques are applicable.
Roughly speaking, a test design technique is linear if the number of
test cases is doubled when the implemented code is doubled. We have
seen that covering requirement specifications is usually not enough.
We consider the all-transition (all-edge criterion) as a minimum but we
suggest all-transition+ as a better linear alternative. We investigated all
three classes of modeling:
• Stateless
• Stateful
• Mixed (action-state)
115
Chapter 2 Test Design Automation by Model-Based Testing
We can see that using the linear all-edge criterion is almost always
appropriate for stateful and action-state solutions. Considering all these
facts, we can conclude that action-state modeling is the best, stateful is in
the middle, and stateless modeling is the weakest among model classes.
Now, let’s compare the work needed for ad hoc or exploratory testing.
We prepared a small case study in which experienced testers tested the
original car rental exercise that is only slightly different from our example.
After reading and understanding the requirements, the testers created
the test cases. The monitoring application counted the number of test
steps. That car rental exercise also contains 15 mutants, and when a tester
finished the testing process, the application showed the percentage of the
killed mutants. Here is the result as shown in Table 2-3.
116
Chapter 2 Test Design Automation by Model-Based Testing
#1 67 144
#2 73 100
#3 80 81
#4 93 142
#5 53 47
#6 87 52
#7 67 79
#8 67 63
#9 53 36
#10 60 57
Average 70 80.1
The result is that exploratory testing reveals even fewer bugs than
stateless modeling. On the other hand, it’s much faster for the first time,
but executing the tests every day will not be faster after a while.
Finally, by giving the requirements to GPT-4, it generated 13 test cases,
the number of test steps is 46 and the defect detection efficiency is 67%, the
worst of all solutions. Testers don’t need to be afraid to become jobless.
Now let’s consider the MBT approaches. Currently, there are only
one-phase and two-phase MBTs. Table 2-4 is comparing these two.
117
Chapter 2 Test Design Automation by Model-Based Testing
Based on this table, we can conclude that two-phase MBT is way better
than one-phase MBT. As both MBT approaches can be used for all the
model classes, our final conclusion is that the best method is to apply two-
phase MBT with action-state modeling.
118
CHAPTER 3
Domain Testing
This chapter will delve into the extension and optimization of equivalence
partitioning (EP) and boundary value analysis (BVA) techniques, offering
you insights into their enhanced and efficient application. We show
that the existing BVA methods are neither reliable nor cost-effective. By
applying our proposed technique, which can be automated in part, the
number of test cases can significantly be reduced and will detect all (or
most of ) the EP/BVA faults. This methodology is termed optimized domain
testing. Notably, it was presented under the name “general predicate
testing (GPT)” at various international conferences before the emergence
of chatGPT.
Equivalence partitioning and boundary value analysis together are
among the most popular and widely used techniques. They are easy to use.
However, in many cases, the related test selection criteria are incorrectly
used. This chapter revisits and puts EP and BVA techniques in a new
framework. We know that this is the most difficult part of our book. If your
goal is to use our methods in practice without getting to know the details,
you can skip the “Busy readers can skip” parts or jump directly to the
section “Optimized Domain Testing (ODT).” This is enough for being able
to use our automated BVA test generation method on our website. The
reading and understanding time for
• Beginners: 5 hours
• Intermediates: 4 hours
• Experts: 3 hours
Equivalence Partitioning
The equivalence partitioning test design pattern is usually attributed
to Myers (1979). The main motivation is to have a sense of complete
functional testing while avoiding redundancy. The equivalence classes
are constructed in a way that the inputs A and B belong to the same
equivalence class if and only if for input A and B, the behavior of the test
object is the same (which states that the program handles the test values
from an equivalence class similarly). If data inputs A and B test the same
behavior and the computation is wrong, then both A and B should detect
the bug.
The natural steps of equivalence partitioning are (1) to partition the
input domain D into subdomains (equivalence classes) and then (2)
design tests with values from the subdomains. The equivalence classes
(or partitions) are non-empty and disjoint, and the union of the partitions
covers the entire domain D.
The partitions are identified mainly from the requirements (functional
descriptions, constraints) for each input. Two types of equivalence
partitions must be identified: the valid and the invalid ones. Valid
partitions contain values that should be accepted by the system under test.
Invalid partitions contain values that should be handled as invalid data by
the system under test.
A partition can be any non-empty set of values: unordered, ordered,
discrete, infinite, finite, or even a singleton. The partitioning is in most
cases multidimensional; that is, the behavior of the system depends on
more input parameters simultaneously. If the behavior of an element
120
Chapter 3 Domain Testing
Note that by applying this technique, only abstract tests are produced.
Converting them into concrete, the designer should consider the content
and structure of the equivalence partitions.
The category-partition method by Ostrand and Baker (1988) is a
practical method illustrating the concept of decomposing functional
testing. It is a kind of generalization of equivalence partitioning. We note
here that from development aspects, functional decomposition precedes
data decomposition. In general, the software architect needs to perform
an appropriate and sustainable functional decomposition according to
the requirements. During the functional decomposition, attention should
be paid to the data decomposition since it influences the number of tests
drastically. Moreover, optimal data decomposition reduces the amount of
data flow and supports maintenance in the long term as well. Note that we
are living in a data-oriented world.
Consider, for example, the NextDay function of Jorgensen (2008). Here,
for a given day, month, and year, the next day should be computed. For
example, NextDay(29,2,2024) = (1,3,2024).
The equivalence partitions for the data elements day, month, and year
are the following:
Partitions for the days (set of integers): D1 = {1, 2, …, 27}, D2 = {28}, D3
= {29}, D4 = {30}, D5 = {31}. Partitions for the months (set of integers) are
all the months from January to December. Partitions for the years without
functional decomposition are all the years.
121
Chapter 3 Domain Testing
If the tester combines the data elements from days, months, and years,
there will be plenty of tests needed.
Applying the functional decomposition (we show one
possibility), first the functions MonthLength(Month, Year) and the
LenghtOfFebruary(Year) should be developed and tested. When Month
= 2, then the LenghtOfFebruary(Year) function is called. This function
can be tested by four test cases, that is, one test for years evenly divisible
by 4, one for years that are divisible by 100, one for years divisible by 400,
and one for none of the years above. MonthLength should be tested for all
the different partitions of the months, that is, for months from January to
December. However, February has been tested four times already; hence,
only the remaining 11 months should be tested.
Then, for NextDay three further valid equivalence partitions should be
tested, as shown in Table 3-1.
122
Chapter 3 Domain Testing
123
Chapter 3 Domain Testing
124
Chapter 3 Domain Testing
Price Calculation
PC-R1 The customer gets a 10% price reduction if the price of the goods reaches
200 euros.
PC-R2 The delivery is free if the weight of the goods is under 5 kilograms. Reaching
5 kg, the delivery price is the weight in euros; thus, when the products together are
6 kilograms, then the delivery price is 6 euros. However, the delivery remains free if
the price of the goods exceeds 100 euros.
PC-R3 If the customer prepays with a credit card, then they get a 3% price
reduction for the reduced price of the goods.
PC-R4 The output is the price to be paid. The minimum price difference is 0.1 euro;
the minimum weight difference is 0.1 kg.
125
Chapter 3 Domain Testing
126
Chapter 3 Domain Testing
In T5, the total price is 182.7, instead of 183.3. Clearly, the incorrect
price reduction of 12.7% and forgotten credit card reduction result
in undetectable faults. The probability that this happens, however, is
very low.
We don’t know the partitions exactly, but as you can see, there is no
need to know them.
Equivalence Partitioning
and Combinatorial Testing
Higher-order bugs can occur as we have seen in the faulty Python
implementation of the Online Shop example before. In that case, a
special input combination is needed to detect the defects. When using
combinatorial testing within the context of equivalence partitioning,
the goal is to select representative combinations of inputs from different
equivalence classes to create a comprehensive set of test cases. This
approach ensures that various combinations of inputs are tested without
necessarily testing all possible combinations, which could be impractical
or time-consuming. Here’s a basic example to illustrate the concept:
Suppose you’re testing a simple login form that takes a username
and a password. Equivalence partitioning would involve the two classes:
username and password. Combinatorial testing in this scenario would
involve selecting representative combinations from these equivalence
classes. We should combine existing username valid and invalid
127
Chapter 3 Domain Testing
128
Chapter 3 Domain Testing
129
Chapter 3 Domain Testing
130
Chapter 3 Domain Testing
Domain Analysis
Equivalence partitioning is used to reduce the number of test cases
while ensuring adequate coverage of different input values. It involves
dividing the input domain into groups or partitions, where each partition
is expected to behave similarly. Test cases are then chosen from each
partition to represent the entire group. The criterion is to test one
representative value from each partition to identify potential defects that
might exist within that partition.
Domain analysis, on the other hand, is a broader process that focuses
on understanding and defining the complete set of inputs, outputs, and
behaviors of a system. It involves analyzing the various possible values
and ranges that inputs can take, as well as the potential outputs and
responses of the system. Domain analysis helps in identifying the entire
domain of possible inputs and outputs, which is crucial for effective
testing and requirements specification. It aids in creating a comprehensive
understanding of the system’s behavior, which is useful for designing
test cases, requirements validation, and ensuring that the system meets
its intended functionality. Domain analysis is particularly important in
industries with specialized needs, such as healthcare, finance, aerospace,
and others where domain-specific regulations, processes, and constraints
play a significant role. By tailoring testing efforts to the domain’s
intricacies, software testers can more effectively ensure that the application
meets the unique demands and expectations of its intended users.
In practical scenarios, equivalence partitioning (EP) is seldom utilized
in isolation. Often, logical relationships exist among requirements. The
likelihood of encountering potential defects increases “near the border” of
equivalence partitions. This is primarily due to the discrepancy between
implemented and correct boundaries. The challenge lies in selecting
appropriate test cases that focus on these boundaries. Unfortunately,
numerous textbooks, blogs, and software testing courses provide
inadequate solutions for boundary value analysis (BVA). Throughout the
131
Chapter 3 Domain Testing
132
Chapter 3 Domain Testing
The ON and OFF points must be “as close as possible” to each other.
As mentioned, BVA needs some order on the input domain; that is, all
the elements of the domain can be compared (a < b). For example, if a
partition consists of integers, then the distance of the two successive points
is one. In book prices, where the minimum price difference is EUR 0.01,
the distance between the neighboring data points is one euro cent.
The basic idea of domain testing was introduced by White and Cohen
(1980) for linear predicates. Later, it was generalized for various domains
by Clarke et al. (1982) and Jeng et al. (1994). They showed the usefulness
of the method but did not examine its reliability. In this book, we show the
reliability of the simplest but the most widespread case of domain testing.
133
Chapter 3 Domain Testing
In the following, we always assume that both the predicate and the
predicate interpretations are linear; that is, we do not consider cases like
var = x^2
IF var < 100 THEN …
where the predicate interpretation is nonlinear. Similarly, we do not
consider the cases where several variables influence the boundary value in
common, like
IF ∑ i ci ∗ var i < const THEN …
n
For those cases, we refer the interested reader to the paper of Jeng
et al. (1994).
Assume that some specification states something about humans,
older than 42 years of age, where the years are counted as integers. Correct
implementations can be
IF age > 42 THEN something
or
IF age ≥ 43 THEN something
Potential faults can be any other implementation of the predicates.
Here (and throughout this chapter), we assume that the examined
predicate is in the simplest possible form. (Indeed, considering our
previous example, “IF age > 40 AND age ≠ 41 AND age ≠ 42 THEN …”
would be mathematically correct, but it is against the CPH.)
In Table 3-3, we show the fault detection capabilities of BVA for both
relation and data faults. Shaded boxes mean that a fault has been detected
for a given test data at that column.
134
Chapter 3 Domain Testing
Wrong predicates are mutants. Mutants #8 and #10 are weaker than
mutants #7 and #9, respectively. This means that no test case exists for
which the stronger mutants are killed but the weaker aren’t. Despite this,
we keep these mutants for completeness.
135
Chapter 3 Domain Testing
In the preceding table, T denotes true and F denotes false (T/F can
be true or false, depending on the value of δ). Computer experiments
show that in case of both data and predicate flaws in an atomic predicate,
the preceding four data points are always able to reveal the bug. As an
example, let us consider the erroneous code snippet:
IF Age ≥ 44 THEN …
Here the data point ON reveals the bug. You can see that altogether,
four data points are necessary to detect ALL possible faults.
Should the tester create the test data points for both ON and OFF
(termed as the two-point boundary value analysis) and the code contains
inaccuracies, there exist solely two instances in which the fault might go
unnoticed. This implies an approximate 83% capacity to uncover bugs.
Similarly, in the case of the three-point BVA (comprising the ON data point
and its two neighboring values), the bug-revealing capability stands at
approximately 92%.
Let us analyze the fault detection capability of an atomic predicate
with a closed border (Table 3-4).
136
Chapter 3 Domain Testing
You can see that four data points are also necessary here to detect
all the possible boundary-related faults. Similar is true for the other
relations < and ≤. For atomic predicates with relations “=” (equality) or “≠”
(inequality), three test cases are enough (Table 3-5 shows only the case “=,”
and the other case is similar).
137
Chapter 3 Domain Testing
Thus, it is enough to have an ON and two OUT data points that are
on different sides of the border. Note that the ON point will detect the
incorrect versions 2, 4, 6, 7, 8, 9, and 10; OUT1 will detect versions 4, 5,
and 6; and OUT2 will detect faults in versions 2, 3, and 6. We leave it to
the reader to verify that the ON, OUT1, and OUT2 data points effectively
expose all bugs in cases where both data and predicate faults are
introduced.
138
Chapter 3 Domain Testing
Finally, for an atomic predicate with the logical operator “≠” we need
an OFF and two IN points that are on different sides of the border.
Busy readers can skip. Table 3-6 summarizes the data points needed
for atomic predicates (Ɛ is the accuracy). In the following, we use the
notation >>C (<<C) which means that the value is significantly larger
(smaller) than C, that is, larger (smaller) than x + Ɛ (x – Ɛ). The relations <,
> have their usual meanings.
139
Chapter 3 Domain Testing
140
Chapter 3 Domain Testing
Factorial
Fac-R1 If the input integer value x is less than 0, then an appropriate error message
must be printed.
Fac-R2 If 0 ≤ x < 20, then the exact value of x! must be printed.
Fac-R3 If 20 ≤ x ≤ 200, then an approximate value of x! must be printed in floating
point format using some approximate numerical method (Stirling formula).
Fac-R4 The admissible error is allowed to be 0.1% of the exact value.
Fac-R5 If x > 200, the input can be rejected by printing an appropriate error
message.
141
Chapter 3 Domain Testing
Closed Borders
Let’s consider requirement PC-R2 from our earlier example of price
calculation:
PC-R2 The delivery is free if the weight of the goods is under five kilograms.
Reaching 5 kg, the delivery price is the weight in euros; thus, when the products
together are 6 kilograms, then the delivery price is 6 euros. However, the delivery
remains free if the price of the goods exceeds 100 euros.
142
Chapter 3 Domain Testing
We can test the two atomic predicates separately so that the other
predicate should be true. In this way, we had 2 x 4 = 8 test cases.
Fortunately, fewer tests are enough. Indeed, the test set
TestSet = {TON[100, 5], TOFF2[100, 4.9], TOFF1[100.1, 5], TIN[6, 20],
TOUT1[120, 6], TOUT2[80, 3]}
is appropriate as common ON (100,5) and IN (6, 20) points help to reduce
the number of the test cases to 6 (see also Figure 3-2).
Observe that none of the test data can be left out or merged. For
example, the test set
Test Set = {[100, 5], [100.1, 4.9], [6, 20]}
will not detect the “mutant” weight ≥ 4.9 and price ≤ 100. The reason is
that the data point [100.1, 4.9] is not a real OFF point as it is not the closest
point to the ON point.
143
Chapter 3 Domain Testing
In general, for a compound predicate with two closed borders, the IN/
ON/OUT1/OUT2/OFF1/OFF2 data points are always appropriate (see
Table 3-7) revealing a single fault. According to the result of Offutt, multiple
faults can also be detected with high probability. The values can be
abstract; that is, for “<< 100” all the values less than 100 – ε can be chosen
(ε is the accuracy). In the code under test, we introduced a new mutation
operator “or” mutating the “and.” You can see that all the “mutants” are
killed by at least one of the data points (light gray cells).
Busy readers can skip
Table 3-7. Test data for a compound predicate with single faults.
δ is any positive (>1) multiple of the accuracy Ɛ. Weak mutants are
omitted.
Mutant Correct/wrong Data 1 Data 2 Data 3 Data 4 Data 5 Data 6
version predicate (single
Nr. mutation)
144
Chapter 3 Domain Testing
145
Chapter 3 Domain Testing
15 p ≤ 100 + Ɛ and T T F T F F
w≥5
16 p ≤ 100 - Ɛ and F F F T F F
w≥5
17 p ≤ 100 and F F F T F F
w≥5+Ɛ
18 p ≤ 100 and T F T T F F
w≥5–Ɛ
146
Chapter 3 Domain Testing
Figure 3-3. Data points for one closed and one open border in
compound predicates
147
Chapter 3 Domain Testing
Boundary test data for price: ON = 99.9, OFF1 = 100, OUT1 = 150, IN =
50, assuming weight > 5.
Boundary test data for weight: ON = 5.1, OFF2 = 5, OUT2 = 2, IN = 6,
assuming price < 100.
Here we need six data points again for the
TestSet = {TOFF1[100, 6], TOUT1[150, 6], TOFF2[80, 5], TOUT2[80, 2], TON [99.9,
5.1], TIN[50, 6]}. See Figure 3-4.
Other Cases
If we have a compound predicate where one of the atomic predicates is a
logical value (true or false) such as
IF price ≤ 100 AND VIP THEN
then we have the four test cases for the first predicate and only two (IN
= true, false) for the second. However, only five test cases are enough as the
two IN points can be merged:
TestSet = {[50, F], [50, T], [101, T], [100, T], [150, T]}.
148
Chapter 3 Domain Testing
S
ummary
Table 3-8 summarizes the reliable test data for some compound predicates.
Here Var1 and Var2 are (independent) variables of the compound
predicate that determine the boundaries of the domain to test. The
accuracies are denoted by Ɛ1 and Ɛ2, respectively. You can see that in some
cases the number of test cases is six and in some other cases is just five.
Busy readers can skip
Var1 ≤ Const1 and Var2 ≤ IN [Var1 < Const1, Var2 < Const2],
Const2 ON [Var1 = Const1, Var2 = Const2],
OFF1 [Var1 ≤ Const1, Var2 = Const2 + Ɛ2],
OFF2 [Var1 = Const1 + Ɛ1, Var2 ≤ Const2]
OUT1 [Var1 >> Const1, Var2 ≤ Const2]
OUT2 [Var1 ≤ Const1, Var2 >> Const2]
Var1 ≤ Const1 and Var2 < IN [Var1 < Const1, Var2 << Const2],
Const2 ON [Var1 = Const1, Var2 = Const2 – Ɛ2],
OFF1 OFF2 [Var1 ≤ Const1, Var2 = Const2],
OUT1 [Var1 = Const1 + Ɛ1, Var2 < Const2],
OUT2 [Var1 ≤ Const1, Var2 > Const2]
[Var1 >> Const1, Var2 < Const2]
(continued)
149
Chapter 3 Domain Testing
Var1 < Const1 and Var2 < IN [Var1 << Const1, Var2 << Const2],
Const2 ON, OFF1 OFF2 [Var1 = Const1 – Ɛ1, Var2 = Const2 –
OUT1 OUT2 Ɛ2],
[Var1 = Const1, Var2 < Const2],
[Var1 < Const1, Var2 = Const2],
[Var1 > Const1, Var2 < Const2],
[Var1 < Const1, Var2 > Const2]
Var1 = Const1 and Var2 ≠ IN1 [Var1 = Const1, Var2 < Const2],
Const2 IN2 OUT1 [Var1 = Const1, Var2 > Const2],
OUT2 ON/OFF [Var1 < Const1, Var2 < Const2],
[Var1 > Const1, Var2 > Const2],
[Var1 = Const1, Var2 = Const2]
Var1 < Const and Var2 = ON [Var1 = Const1 – Ɛ1, Var2 is TRUE],
TRUE IN [Var1 << Const1, Var2 is TRUE],
OFF1 [Var1 = Const1, Var2 is TRUE],
OFF2 [Var1 < Const1, Var2 is FALSE],
OUT [Var1 > Const1, Var2 is TRUE]
Note that the negation (NOT) operator doesn’t change the necessary
data points. If the result of the operations for the correct and the faulty
predicates were different, then after negation, the result will also be
different. The logical OR operator can be transformed to AND by applying
the De Morgan rule. For example, Var1 ≤ Const1 OR Var2 ≤ Const2 is
equivalent to NOT(Var1 > Const1 AND Var2 > Const2).
150
Chapter 3 Domain Testing
Var < Const ON reveals the predicate faults =, ≥ and >, and the data faults
Const – Ɛ
OFF reveals the predicate fault ≤, and the data faults Const + Ɛ
OUT reveals the predicate fault ≠
IN reveals the double fault “= Const – Ɛ”
Var ≤ Const ON reveals the predicate faults <, >, ≠, and the data faults
Const – Ɛ
OFF reveals the predicate fault ≥ and the data faults Const + Ɛ
OUT reveals the double fault “≠ Const + Ɛ”
IN reveals the faulty operator “=”
Var = Const ON reveals <, >, ≠ faults and the data faults Const ± Ɛ
OUT1 (i.e., Var < Const) reveals the fault ≤
OUT2 (i.e., Var > Const) reveals the fault ≥
Var ≠ Const IN1 (i.e., Var < Const) reveals the faults ≥, >, =
IN2 (i.e., Var > Const) reveals the faults ≤, <, =
OFF (i.e., Var = Const) reveals the data faults Const ± Ɛ
Var = TRUE OUT reveals the fault Var is FALSE
Var = FALSE OUT reveals the fault Var is TRUE
151
Chapter 3 Domain Testing
The cases Var > Const and Var ≥ Const are analogous to the cases Var <
Const and Var ≤ Const.
The following statement follows:
152
Chapter 3 Domain Testing
153
Chapter 3 Domain Testing
Let’s consider any of the four atomic predicates, such as x1 ≤ B1. Here
the ON point is ON2, the OFF point is OFF2, the OUT point is OUT2, and
IN point is ON1. Based on the symmetrical arrangement of the points, we
can perform the same analysis for the other three predicates. Of course,
the test data are not unique. In higher dimensional intervals, we need 4N +
2 tests: 2N OFF, 2N OUT, and 2 ON points.
In the case of open boundaries, the same number of tests can be
applied. Comparing our outcome with the result of Jorgensen (2008), we
can see that his suggested weak normal and weak robust methods are
unreliable having 4N+1 and 6N+1 data points and his suggested normal
worst-case and robust worst-case methods (with 5N and 7N data points) are
unnecessary. The difference is that our test selection was well-engineered.
Altogether, our method is linear (in the number of atomic predicates) and
(assuming single faults) is reliable.
154
Chapter 3 Domain Testing
Boundary-Based Approach
In this subsection, we describe a simple three-step method by which you
can easily design code and the related test cases for a given specification.
In this approach, you don’t need to determine the EPs, only the
boundaries. The steps are the following:
Step 1. Understand the requirements or user stories. Define
the abstract test cases for each requirement one by one using the
determination of the ON/OFF/IN/OUT data points.
Reading through the requirements carefully is a necessary step for
each test design technique. You should fully understand the specification
to be able to make reliable and correct test cases.
155
Chapter 3 Domain Testing
Free delivery is applicable if the weight of the goods remains below 5 kilograms and
the total price of the items exceeds 100 euros.
156
Chapter 3 Domain Testing
or
Clearly, both solutions are inside the CPH. However, in the second
case, there are more places for faults. Therefore, we should test the second
predicate as each atomic predicate can be erroneous. In general, during
the test design, the tester should concentrate on the most general set of
predicates.
These were the manual steps you should do. From here, the method
can be automated. The first automation step is to establish the abstract
test cases, that is, to create the ON/OFF/IN/OUT points according to our
extended BVA.
Step 3. The test cases are specified, that is:
(a) The abstract test cases are merged. For example, if we have two
test cases:
• T2 = [1, <<100, 8]
then the merged test case can be T = [1, 20, 8]. There are many ways
for merging, and for more complex cases, it’s almost impossible to do
it manually. We think that our heuristic algorithm gives near-optimum
results.
157
Chapter 3 Domain Testing
(b) The undetermined input values are specified. This can also be
automated.
(c) Finally, the expected results are calculated for the specified points.
To summarize, almost the whole method can be automated resulting
in the “minimum” number of test cases (for refactored code and assuming
CPH). We know that this process is error-prone when done manually;
however, the automated version is available on our website.
Online Bookstore
OB-R1 When purchasing new books, customers possessing loyalty cards are
entitled to a 10% reduction in the new book’s price.
OB-R2 Customers purchasing new books worth a minimum of EUR 50 receive a
10% price reduction from the new book’s price.
OB-R3 Customers with loyalty cards benefit from a 15% price reduction on new
books if the purchase price is equal to or greater than EUR 50.
OB-R4 Customers who buy used books receive a 5% price discount if the used
book’s price exceeds EUR 60, provided they also purchase new books totaling more
than EUR 30.
OB-R5 The smallest permissible price difference between items is one euro cent.
Suppose that the test design prerequisites are as shown in Table 3-10.
158
Chapter 3 Domain Testing
159
Chapter 3 Domain Testing
160
Chapter 3 Domain Testing
Step 3. The test cases must be fully specified and reduced if possible.
We start from 21 test cases, and we are looking for mergeable tests.
Then, for the merged abstract test cases, we determine concrete ones. In
Table 3-11, we have shaded the test cases, where the same color indicates
the possibility for reduction.
Table 3-11. Abstract tests for the online bookstore application before
optimization
Serial Number VIP Book Price Secondhand Book Price Test ID
#1 true 49.99 T1
#2 true <<50 T2
#3 false <50 T3
#4 true 50 T4
#5 true >50 T5
#6 false 50 T6
#7 false 49.99 T7
(continued)
161
Chapter 3 Domain Testing
#8 false >50 T8
#9 false <<50 T9
#10 true ≥ 50 T5
#11 true 50 T4
#12 false 50 T6
#13 true 49.99 T1
#14 true >50 T5
#15 true <<50 T2
#16 30 60.01 T3
#17 30.01 60 T2
#18 30.01 60.01 T10
#19 <<30 >60 T9
#20 >30 <<60 T7
#21 >> 30 >>60 T5
We can merge #1 and #13 as they are identical. We can merge #5, #10,
#14, and # 21 as the first three are true, the fourth can be set to true, and all
of them can be >50, for example, 51. For similar reasons, we can merge the
pairs (#2, #15, #17), (#3, #16), (#4, #11), (#6, #12), (#7, #20), and (#9, #19).
You can see that we can eliminate 11 test cases; thus, only 10 tests remain.
The undetermined values can be then anything. Finally, the output values
are computed. Table 3-12 contains the optimized list of test cases.
162
Chapter 3 Domain Testing
Table 3-12. Concrete tests for the online bookstore application after
handy optimization
Tests VIP Price Secondhand Book Price Total
You can check the result at the authors’ site, where we applied
mutation testing to create 30+ mutants. Putting all together, instead of 21
test cases, we have only 10; that is, the reduction rate is 52%.
Rule-Based Approach
Here, we show an alternative approach. This method also requires three steps:
1
Although the problem of minimizing Boolean functions is NP-complete in
general, in practice the Karnaugh maps or the Quine–McCluskey algorithm (or
other heuristics) can be used.
163
Chapter 3 Domain Testing
You can see that the two methods are similar; only one step is slightly
different.
Let us see a very simple example. In this case, we do not need any
optimization.
Del-R1 The delivery is free if the weight of the goods is under five kilograms.
Del-R2 When the weight reaches 5 kg, the delivery price is the weight in euros;
thus, when the products together are 6 kilograms, then the delivery price is 6 euros.
Del-R3 The delivery remains free if the price of the goods exceeds 100 euros.
164
Chapter 3 Domain Testing
165
Chapter 3 Domain Testing
or
166
Chapter 3 Domain Testing
Step 2. Regarding the first expression, we generate the tests from the
decision table, Table 3-13. The different abstract tests are [card ownership,
new price]:
Regarding Rule5, the abstract tests are [new price, used price]:
167
Chapter 3 Domain Testing
Table 3-14. Abstract tests for the online bookstore application before
optimization
Test Data Expected
Result
(Delivery Price)
Serial New Price Used Price VIP Card
Number (EUR) (EUR) Owner
#1 < 50 T
#2 < 50 F
#3 <<50 T
#4 <<50 F
#5 49.99 T
#6 49.99 F
#7 50 T
#8 50 F
#9 ≥ 50 T
#10 ≥ 50 F
#11 > 50 T
#12 > 50 F
#13 < 30 > 60
#14 30 60.01
#15 30.01 60
#16 30.01 60.01
#17 > 30 < 60
#18 >>30 >>60
168
Chapter 3 Domain Testing
Step 3.
Instead of merging these abstract tests by hand, we apply our tool. This
results in the following concrete tests (Table 3-15).
Table 3-15. Concrete tests for the online bookstore application after
computer optimization
Test Data Expected Result
(Delivery Price)
Tests New Price Used Price (EUR) VIP Card Owner
(EUR)
T1 55 60 T 106.75
T2 50 20 F 65
T3 30 70 T 97
T4 10 100 F 110
T5 49.99 0 F 49.99
T6 49.99 1000 T 994.99
T7 50 5 T 47.5
T8 60 5 F 59
T9 30.01 60.01 F 87.02
Hence, nine test cases are enough for testing the online bookstore
application. We note that the concrete test set is not unique.
We note as well that other tests may be needed for testing the whole
application; our method concentrates on the BVA tests only.
169
Chapter 3 Domain Testing
170
Chapter 3 Domain Testing
171
Chapter 3 Domain Testing
PVD-R1-2:
IF service ≥ 30 AND age ≥ 60 THEN…
PVD-R1-3:
In order not to combine with other extra days, we should add three
more atomic predicates:
IF service ≥ 15 AND age < 45 AND age ≥ 18 AND service < 30 THEN…
Here we have four borders forming a rectangle. There are four OFF,
four OUT, and two ON points (that are also IN points):
172
Chapter 3 Domain Testing
173
Chapter 3 Domain Testing
Table 3-16. Abstract test set for the paid vacation days after
optimization. The partitioning was made implicitly using the
boundary-based approach.
Serial Age Service Serial Age Service
Number Number
Calculating the concrete 18 test cases based on the abstract tests is very
simple. Here is a possible solution:
As we mentioned, by the boundary-based approach, the partitions are
established implicitly. All the boundary bugs in any program constructions
following the design
IF age < 18 AND service < 30 THEN
IF age ≥ 60 AND service < 30 THEN…
IF service ≥ 30 AND age < 60 AND age ≥ 18 THEN…
IF service ≥ 30 AND age ≥ 60 THEN…
174
Chapter 3 Domain Testing
IF service ≥ 15 AND age < 45 AND age ≥ 18 AND service < 30 THEN…
IF age ≥ 45 AND service < 30 AND age < 60 THEN…
can be revealed with the concrete test set based on Table 3-17. The
remaining partition is also tested as it is adjacent to the tested ones.
Clearly, the borders of the remaining partitions are tested with ON, OFF,
IN, and OUT points. From the viewpoint of the remaining partition,
the points are changed as follows: ON → OFF, OFF → ON, IN → OUT,
OUT → IN.
Table 3-17. Concrete test set for the paid vacation days
Serial Number Age Service Serial Number Age Service
#1 16 15 #10 44 29
#2 16 30 #11 45 18
#3 17 29 #12 55 31
#4 17 31 #13 59 29
#5 18 2 #14 59 30
#6 18 15 #15 60 29
#7 18 31 #16 60 30
#8 42 30 #17 62 25
#9 44 14 #18 62 32
175
Chapter 3 Domain Testing
We refer to this later as version 1. However, this is not the only possible
partitioning. Consider, for example, another (still valid) partitioning
(version 2):
176
Chapter 3 Domain Testing
Table 3-18. Abstract test sets for versions 1 and 2 for the paid
vacation days after optimization. The partitioning was made using
the rule-based approach.
Abstract Test Data for Version 1 Abstract Test Data for Version 2
Partitioning Partitioning
Serial Age Service Serial Age Service
Number Number
177
Chapter 3 Domain Testing
You can see that the two abstract test sets are different. Consider the
following correct and erroneous code:
Correct:
178
Chapter 3 Domain Testing
Erroneous:
Let the test set Tpvd1 = {(16,13), (16,15), (17,13), (17,15), (18,13),
(18,15), (44,14), (44,29), (44,30), (44,32), (45,13), (45,28), (45,31), (59,29),
(59,30), (60,29), (60,30), (61,10), (61,31)} based on Table 3-18 Version 1.
Tpvd1 reveals the bug in the preceding code since the correct and the
faulty code was derived from the version 1 partitioning. More precisely, the
test data (44, 30) reveals the bug.
Let Tpvd2 = {(16,13), (16,15), (17,13), (17,15), (18,10), (18,15), (18,31),
(44,14), (45,13), (45, 15), (59,14), (59,29), (59,30), (60,13), (60,29), (60,30),
(61,13), (61,15), (61,31)} based on Table 3-18 Version 2. The test set Tpvd2 is
not able to reveal the bug in the previous buggy code. Similarly, if the correct
and the faulty code follows the design based on the version 2 partitioning, like
def vacationCorrect2(age,service):
vacationDays = 22
if age < 18:
vacationDays += 5
if age >= 18 and age < 60 and service >= 15 and service < 30:
vacationDays += 2
179
Chapter 3 Domain Testing
if age >= 18 and age < 60 and service >= 30:
vacationDays += 5
if age >= 45 and age < 60 and service < 15:
vacationDays += 2
if age >= 60 and service < 30:
vacationDays += 5
if age >= 60 and service >= 30:
vacationDays += 8
return vacationDays
def vacationFaulty2(age,service):
vacationDays = 22
if age < 18:
vacationDays += 5
if age >= 18 and age < 60 and service >= 15 and
service < 30:
vacationDays += 2
if age >= 18 and age < 60 and service >= 30:
vacationDays += 5
if age >= 45 and age < 60 and service <= 15:
vacationDays += 2
if age >= 60 and service < 30:
vacationDays += 5
if age >= 60 and service >= 30:
vacationDays += 8
return vacationDays
then the concrete test set Tpvd2 reveals the bug, while the test set Tpvd1
does not.
In other words, when the partitioning can be made in different ways,
then the appropriate test set should be the union of all the tests that can
be derived from different partitioning. Let us see the solution of our paid
vacation days example.
180
Chapter 3 Domain Testing
Table 3-19. Data partitioning for the paid vacation days application
Age Service Vacation Days
< 18 * +5
[18, 45) < 15
[18, 45) [15, 30) +2
[18, 45) ≥ 30 +5
[45, 60) < 15 +2
[45, 60) [15, 30) +2
[45, 60) ≥ 30 +5
≥ 60 < 30 +5
≥ 60 ≥ 30 +8
Table 3-20. Data partitioning for the paid vacation days application,
collapsed versions. The differences are in bold.
Age Service Vacation Days Age Service Vacation Days
< 18 * +5 < 18 * +5
[18, 45) < 15 [18, 45) < 15
[18, 45) [15, 30) +2 [18, 60) [15, 30) +2
[18, 60) ≥ 30 +5 [18, 60) ≥ 30 +5
[45, 60) < 30 +2 [45, 60) < 15 +2
≥ 60 < 30 +5 ≥ 60 < 30 +5
≥ 60 ≥ 30 +5+3 ≥ 60 ≥ 30 +5+3
181
Chapter 3 Domain Testing
Table 3-21. Abstract test set for the paid vacation days after
optimization
Serial Number Age Service Serial Number Age Service
182
Chapter 3 Domain Testing
Table 3-22. Concrete test set for the paid vacation days application
Test Data Expected Result
Tests Age (Integer) Service (Integer) Paid Vacation Days
T1 10 5 27
T2 16 15 27
T3 17 11 27
T4 17 16 27
T5 43 31 27
T6 18 13 22
T7 18 15 24
T8 44 14 22
T9 44 29 24
T10 44 30 27
T11 45 13 24
T12 45 15 24
T13 45 31 27
T14 59 14 24
T15 59 29 24
T16 59 30 27
T17 60 13 27
T18 60 29 27
T19 60 30 30
T20 61 10 27
T21 61 29 27
T22 61 31 30
183
Chapter 3 Domain Testing
The concrete test set reveals the bugs in our faulty implementations:
T10 shows the bug in the first and T12 in the second faulty
implementation.
184
Chapter 3 Domain Testing
You can see that test data 42 (relating to the border) reveals the bug. A
deeper analysis shows that regarding the logical operators <, ≤, >, ≥, =, ≠,
the only cases when the bugs remain hidden are when the developed code
conforms to the specification. In these cases, for any test data (even the
wrongly designed), the expected and actual results are the same. In all the
other cases, the faulty code can be recognized.
Considering the possible faults made by the tester, we may assume the
competent tester hypothesis, which states that
Testers create test cases that are close to the reliable test set for the
correct program.
This means that the tests are designed according to the specification
to be implemented and not for some different specifications. In other
words, the tester should use the correct requirements data; that is, when
the requirements say that “people with age greater than 42…,” then a
competent tester will not make the test design considering “age > 83” and
adding test cases “age = 83” and “age = 84.” However, the misinterpretation
“age ≥ 42” may occur. We assume that the tester is using a reliable test
design technique such as ODT.
In the previous subsection, we also showed that for any partitioning
the related BVA tests are reliable, the number of the tests depends on the
chosen partitioning. Thus, for safety-critical systems, the tester can design
a reliable test set for the application’s predicate system. In other words,
whatever predicate-related bug the competent tester or the competent
developer made, the ODT technique will reveal it. Thus, we think that
using this method for safety-critical systems is unavoidable.
185
Chapter 3 Domain Testing
Table 3-24. Test design analytics for the holiday booking system
Test object/condition: Holiday booking system
Trace: HB-R1, HB-R2, HB-R3
Risk and complexity: Medium
Applicable component(s): None
CI influence: None
Defensiveness: Input validation is not needed
Fault assumption: Single
Technique: Optimized domain testing
Suppose that Dominika, the developer, follows TDD. First, she designs
the equivalence partitions for the input domain:
Vacation days: ≤ 8, > 8 && ≤ 15, > 15 (3 partitions)
Vacation order (EUR): < 500, ≥ 500 && ≤ 1000, > 1000 (3 partitions)
Workday departure: True/false (2 partitions)
186
Chapter 3 Domain Testing
Vacation Days ≤8 X X
> 8 && ≤ 15 X X X X
> 15 X
Vacation Order (EUR) < 500 X X X
≥ 500 && ≤ 1000 X X X
> 1000 X
Workday Departure Is true X X
Discount (%) 20 20 - - 10 - 10 -
X means that a predicate is an input for the tool. We have eight input
predicate sets. For example, according to the gray column, the input
predicate is
187
Chapter 3 Domain Testing
IF Vacation days > 8 AND Vacation days ≤ 15 AND Vacation order <
500 AND Workday departure = True THEN …
Checking the consistency, completeness, and non-redundancy may
take five minutes. Then, based on these 8 input predicates, the ODT tool
produces 30 tests. Until this point, the process took 20 minutes as well,
including the conversion into the following concrete data with expected
output (see Table 3-26).
188
Table 3-26. Concrete test data for the holiday booking system
Serial Vacation Order WorkDay Exp. Serial Vacation Order WorkDay Exp.
Nr. Days (EUR) Dep. Output Nr. Days (EUR) Dep. Output
189
Domain Testing
Chapter 3 Domain Testing
Suppose now that the competent but tired Dominika produces the
following code (according to the Table 3-25 test design):
Running the tests against the preceding code, she finds some mistakes:
the tests #19, #20, #21, and #22 are failed. She realizes that there is a bug in
the first IF statement. Her second try
The test #20 still fails. Observing a misprint in the ELSE IF branch, the
new version is
190
Chapter 3 Domain Testing
Now all the tests are passed. The whole development and test design
process took 25 minutes. No bugs can survive. The variation in design
has led to a decrease in the test set’s size, prompting the inquiry of how
optimized domain testing (ODT) can be effectively implemented within a
continuous integration environment.
The authors asked ChatGPT to generate Python code to the
requirements above. Our test automation tool runs the ODT tests against
the AI-generated application showing that the ChatGPT code is correct.
191
Chapter 3 Domain Testing
Rollercoaster
Consider testing the entry check and round release procedures of the Rollercoaster
within the QualityLand amusement park.
RC-R1 Individuals’ heights are categorized into three groups: those under 120 cm
are prohibited from entry, those measuring between 120 and 140 cm require a seat
extender, and those surpassing 140 cm are permitted to ride.
RC-R2 If all seats are occupied, the gate is closed.
RC-R3 Should a visitor attempt to enter, and the combined weight exceeds 1000
kg, the visitor is denied access, and the gate is closed. If the cumulative weight of
the passengers falls between 700 and 1000 kg, the ride proceeds. In case the total
weight is below 700 kg, additional weight blocks are incorporated.
Upon arrival at the rollercoaster entrance, checks are conducted on height, weight,
and seat availability to determine eligibility for entry. Height is represented in
centimeters (integer values), and total weight is measured in kilograms (integer
values).
Black-Box Solution
Suppose that the tester knows nothing about the functional
decomposition; hence, the tester does not know how, when, and in which
order the height is checked, the ride is enabled or prohibited
(see Table 3-27).
192
Chapter 3 Domain Testing
193
Chapter 3 Domain Testing
IF height > 140 AND total weight ≤ 1000 AND the number of free
seats is bigger than 1
THEN visitor can enter, the gate closes
Putting the conditions to the ODT tool (a short description about using
the free ODT tool can be found in Appendix V):
194
Chapter 3 Domain Testing
195
Chapter 3 Domain Testing
Considering the CI/CD cycles, these tests must run at the beginning
and before release cycles. Note that these tests are reliable and should
recover any predicate faults.
Gray-Box Solution
Suppose now that the tester knows the functional decomposition designed
by the developer and agrees that the implementation will be coded
according to the decomposition. Suppose that the following sequence of
steps is designed:
• Step 1: Handle the cases when the visitor is not allowed
to enter.
• Step 2: Handle the cases when the visitor can enter with
a seat extender.
• Step 3: Check the need for the extra weight blocks.
196
Chapter 3 Domain Testing
The developer should separate these steps in his code. The first step
means that if a visitor is under 120 cm tall, then entering is prohibited
independently from the other data elements. Similarly, if the total weight
value exceeds 1000 kg, the entry is prohibited, and the visitor should wait
for the next ride.
First, we check the cases when the visitor is not allowed to enter:
The tests are shown in Table 3-30 (we consider here the preconditions
as well).
197
Chapter 3 Domain Testing
Note that if the entry is prohibited, the tester should find the root
cause, where three possibilities need to be checked.
The seat extender requirement can be modeled by
We can extend the previous test set resulting in the following (see
Table 3-31).
198
Chapter 3 Domain Testing
199
Chapter 3 Domain Testing
and the previous tests can be extended in the following way (see
Table 3-33).
200
Chapter 3 Domain Testing
You can see that less test case is enough since the tester has
information about the functional (and data) decomposition. However,
the TG1-TG9 tests are reliable only for the solution satisfying the
decomposition.
201
Chapter 3 Domain Testing
White-Box Solution
If the lowest level model, that is, the (refactored) code is known, the ODT
model gives always narrow and reliable solutions; however, the test set is
reliable only against the given code. In the CI/CD cycle, this test set can be
applied in the intermediate cycles. We strongly suggest using the largest test
set (i.e., the test set produced by the black-box case) at every quality milestone.
Applying the suggested optimization, large savings can be made
during the continuous integration process. If the code changes via some
modifications, the automatic computation starts against the requirements
considering anything as black-box. After successful confirmation testing,
the reduced test set (i.e., produced based on the decomposition or based
on the code) can be used in the regression testing.
202
Chapter 3 Domain Testing
Let us start the test design with simple classifications. The basic classes
are as follows (see Figure 3-6).
• All-pairs testing
• Combinatorial testing
203
Chapter 3 Domain Testing
rollercoasterBAD(210, 1100, 3)
gives the incorrect result “can enter,” seriously endangering people’s lives.
Of course, the ODT technique finds the bug. Dear reader, please
find the bug in the preceding code. You may think that it took just a few
seconds, what is the problem? Please note that it was just a simplified
example. In reality, a large code base is hard to review thoroughly. The
message of this short section is that choosing the right test design
technique is really important.
204
Chapter 3 Domain Testing
Summary
EP and BVA are among the most widely used test design techniques.
Traditional methods are unreliable: they require more test cases than
needed and are unable to reveal all the BVA defects. We showed that
for each atomic predicate with logical operators “<,” “>,” “≤,” “≥” four
test cases, and with operators “=,” “≠” three test cases are necessary and
sufficient for testing logical conditions reliably.
We extended our method for compound predicates. For predicates
having two atomic logical operators “<,” “>,” “≤,” “≥” six test cases are
reliable for any predicate bugs assuming faults. We gave the general
solution for arbitrary dimensions as well. Based on these, we showed a
reliable test selection for multidimensional ranges.
Finally, we introduced a method where several compound predicates
are present resulting in a reduced and still reliable test set. This is the case
for real applications. By applying our method, you can reduce the number
205
Chapter 3 Domain Testing
206
CHAPTER 4
Developers and
Testers Should
Constitute a
Successful Team
In this chapter, you will learn how developers and testers can help each
other by proving the concept of teamwork 1 + 1 >> 2. We describe methods
by which the developers can help testers achieve stable test automation
and other methods by which testers can reduce execution paths or test
inputs to enable the developer to detect the location of the bugs more
easily and faster. We also describe how the developers and testers can work
in teams efficiently.
Estimated Time:
• Intermediates: 1 hours
.//*[@id="render-root"]/div/ul/li[(count(preceding-
sibling::*)+1) = 3]/a
#render-root > div > ul > li:nth-child(3) > a
208
Chapter 4 Developers and Testers Should Constitute a Successful Team
You can see that the latter, the CSS selector, is easier to understand, but
both consist of the same elements, the id render-root, div, ul, li[3], and a.
From here, we use CSS selectors.
Neglecting to code unique selectors, most test automation engineers
complain about the maintainability of their test code. If the code is
modified, then the selector should also be modified. However, the problem
is even larger. Assume that we have two test cases:
209
Chapter 4 Developers and Testers Should Constitute a Successful Team
should code an appropriate attribute name such as “login,” and the code
can be modified; for example, from login to sign-in, the selector remains
stable. With this solution, the quality of the test set and consequently your
software product will be better.
Quality assurance is a team responsibility. Applying stable selectors
will increase quality and decrease costs. To do this, dear developer, you
should help the team by inserting special attributes into your code. A special
attribute identifies a UI element so that automated test cases remain stable.
You can use the traditional “id” attribute or even better use the special
attribute: “data-xxx” such as data-testid. Clearly, the “id” or the special
attribute should be unique. Unfortunately, most of the systems contain some
nonunique ids. It’s reasonable to create understandable and meaningful ids
as they can be visible in the test automation code/description. For example,
the two ids of deletion of User3 and User4 in project “First” can be
With this approach, very elegant test cases can be designed, for
example, by applying some Gherkin-like syntax:
Cool, isn’t it? The developer extends the code with a single element.
Here is an example in JavaScript:
Adding special attributes takes less than half a minute for a developer,
while finding an appropriate selector may take about five minutes for a test
automation engineer on average. You can consider this as an additional
210
Chapter 4 Developers and Testers Should Constitute a Successful Team
211
Chapter 4 Developers and Testers Should Constitute a Successful Team
The first example is about how the length of an execution path can
be reduced. The following tricky bug was found by the first author in a
test project. At some phase of the development, the “Company” used
exploratory testing in such a way that the test steps were written down. The
failed test in the following is about adding new users to the existing ones
and deleting some existing and new users. We had the following list of
test steps:
At this point, we observed the failure; that is, the email address of the
new user B became the email address of the deleted user A.
By applying exploratory or other on-the-fly testing, the code fails
after a long execution path. However, there are execution steps that are
irrelevant concerning this failure. To make bug fixing easier and faster for
the developer, the tester has to reduce the number of test steps needed to
reproduce the fault. We can also assume that this test contains superfluous
steps concerning the failure. Thus, we try to simplify it. We start from the
test step, where the fault manifested itself:
8. Start to delete account B but cancel.
212
Chapter 4 Developers and Testers Should Constitute a Successful Team
Before this step, we must add a new user account. Therefore, we can
start from the following test:
Unfortunately, these two steps don’t reveal the bug. What can we do?
Let’s consider the other test steps:
213
Chapter 4 Developers and Testers Should Constitute a Successful Team
In some other cases, the input test data is too large and should be
reduced. The method to be described is not new; the author, Zeller (1999),
called it “delta debugging.” Let’s assume that we have a large input, for
example, a set of numbers to be sorted for which the test fails. The method
is to construct two sets from a large input set in the following way:
214
Chapter 4 Developers and Testers Should Constitute a Successful Team
215
Chapter 4 Developers and Testers Should Constitute a Successful Team
Story I
In an earlier version of Harmony, the test cases are generated from an extended
Gherkin description, and the tester can execute the tests immediately after
designing a new test. The following happened to the first author. I designed lots of
tests, and when the code became ready, I tried to execute them. However, the test
cases didn’t run; instead, a message said that the tests were waiting. I hadn’t any
idea why my tests were not running. I wanted to execute the tests immediately after
designing a new test.
OK, I tried to minimize the tests for which the execution failed. First, I made a
minimum test set, then I executed it. It passed, but I was only interested in whether
the test started to run or not.
A Harmony test description consists of two parts: (1) declaration and (2) test
description. I set back the input for which Harmony tests were waiting. I halved the
input and at the same time deleted everything from the test description. The bug
remained; the tests were waiting instead of running. I studied this part to find some
unusual things. The declaration consisted of the word “deleted” (in this way with
quotation marks), which was unusual.
When I deleted the quotation marks, the tests were running. I modified the text from
quotation mark to apostrophe (“deleted”), and it worked. I successfully executed all
my test cases and wrote a message into the bug tracking system for later fixing.
216
Chapter 4 Developers and Testers Should Constitute a Successful Team
The lesson learned here is to minimize your test cases to help your
developers. Use intelligent methods by which test minimization will take
less time. It’s fun.
It occurs often that the tester is unable to reproduce a bug despite
all of his or her attempts. What a pity, the tester thinks, and the bug is
marked as non-reproducible. The good news is that almost every bug is
reproducible – only difficult to reproduce. Without reproducing the bug,
however, it’s almost impossible to find it as you cannot debug it, and so
on. The question is how to make it reproducible. First, try to make the
test as simple as possible. Run the software, and if the bug emerges, try
reproducing it immediately, executing the same steps as before.
If you try to find a simple test for which the bug emerges, try to
reproduce the same steps very carefully. If you detect a bug and on
executing the same steps again, the execution passes, it doesn’t mean that
the bug is nondeterministic. Maybe the initial state is different. Run the
application from scratch to set the same initial state. If you still cannot
reproduce it, you can start to think about what you can do (Roman 2018).
For example, the timing should also be considered.
Flaky Test
Flaky tests fail to produce the same outcome with each individual test run.
Here is a very interesting bug and how to find it.
217
Chapter 4 Developers and Testers Should Constitute a Successful Team
Story II
In an earlier version of Harmony, the textual test model is described by an extended
Gherkin syntax. In some cases, one or two characters disappeared from the text I, the
first author, just typed. It didn’t happen very often, but it was a major bug. Obviously,
the bug cannot be fixed until it is confirmed as being deterministic. However, it
occurred in different contexts, seemingly randomly. I tried to reproduce it, but I
always failed. I deleted everything, then just entered 7 characters: 1234567. The last
character, 7, disappeared. OK, job done, I thought, and I repeated the process again.
However, the text remained correct. I realized that only entering the input data is
not enough for reproducing the bug. I repeated the process again and again trying
to figure out when the bug occurred. Harmony always saves the text in the editor
without the user’s saving. Finally, I realized the following: Before the “Saved”
message appears, three dots … are displayed. If I entered a character while these
dots were displayed, the entered character disappeared (as the software didn’t
consider any input while doing some other business).
Hurray, I tried it more times, and the characters always disappeared. The lead
developer then fixed the bug in less than half an hour.
The lesson learned here is that you can detect the fault when you find
inputs for which the application always fails and when it runs correctly.
Developers cannot do anything with flaky bugs, but they can fix the bug
immediately when you make flaky test non-flaky.
218
Chapter 4 Developers and Testers Should Constitute a Successful Team
Besides the developer and tester, there are other subject matter
expert roles in the software development life cycle (SDLC), like an
architect, database expert, usability expert, security expert, and so on. All
of the mentioned parties are essential elements in the development life
cycle. However, the developer is the person who builds the application,
219
Chapter 4 Developers and Testers Should Constitute a Successful Team
or in other words, who builds both functionality and quality into the
application. The tester controls the quality, but the developer assures it.
Testers mainly focus on “what can go wrong.” Hence, the main task of
the tester is finding and reporting bugs. As such, testers must be able to
deal with conflicts that will arise when pointing out some weaknesses
of the developed application. Developers develop and build the code,
then transfer control to the testers, before receiving back the bug reports,
making diagnoses, finding the root causes, and making the necessary
corrections. Both the bug report and the feedback reception must be
received in a positive, constructive manner, stressing from both sides the
“we are sitting in the same boat” attitude.
A developer is a software engineer who understands all development
layers: business, domain, user requirements, architecture, API, data
modeling, network communication, and so on. He or she is able to
optimize the code on all layers. A tester is able to control the quality in
all possible aspects: business, domain, user requirements, architecture,
API, data modeling, network communication, and so on. The developer
and tester show certain synergies, like the two sides of a coin (the term
“synergy” comes from the Attic Greek word συνεργία that means “working
together”). First, we discuss the most common fallacy regarding the
“developer–tester relationship.”
Developers need various skills like knowing algorithms, data structures,
programming languages, architectures, design patterns; abilities to
code, refactor, and debug; knowledge of database handling, compiler
technologies, processor technologies, software engineering, and so on. Testers
don’t need as many skills…
Well, testers, especially test automation engineers, should automate
tests that need programming skills that may include any aspect of software
engineering. Consider a UI test with the tool Selenium. Testers should
use the same IDE and programming language used for development
to set up the tests. Testers should test database connections, API,
should be able to measure quality, should know test design techniques
220
Chapter 4 Developers and Testers Should Constitute a Successful Team
(like design patterns), and should work together with the customer’s
representatives. The reality is that almost all developers write tests and
almost all of the testers write code or models and not just at the unit and
integration level. Consider a test-driven development or a behavior-driven
development framework. Both the testers and developers must think
from the perspective of the end user. There are many instances where the
boundaries of testing and development cross over. There is a convergence
in the developer–tester roles. Some skills they both should have include
• Analytical thinking
• Communication skills
221
Chapter 4 Developers and Testers Should Constitute a Successful Team
independent person’s opinion and feedback can help a lot. That is one of
the reasons why unit and integration tests performed by the developer are
not enough for ensuring the software quality. Consider the case where the
developer builds a model and generates code in some way from the model.
The tester must build a different model for test generation; otherwise,
in case of an erroneous common model, both the code and the test may
be faulty.
Test methods and techniques have remained the same during the
last years. Even model-based testing, continuous testing, and exploratory
testing are more than 15 years old. Novice testers learn the same,
sometimes weak and not entirely correct methods and test selection
criteria. We think that software quality hasn’t improved significantly
during this time, and the reason is the lack of using new software testing
techniques. With this book, we intend to throw a stone into the still water.
One of the possible “conflicts” between developers and testers is that
the latter finds the mistakes of the former. Obviously, like the developers,
testers may produce bugs, but there is rarely a thorough process of
testing the testers’ work. A designed test set can also be verified; however,
usually, it would be too expensive. No problem, making mistakes is a
basic attribute of humans, even the best of us makes mistakes. As Donald
E. Knuth, a recipient of the ACM Turing Award, wrote: “I make mistakes.
I always have, and I probably always will.”1 So, it’s not a problem if you
make mistakes; the problem is if the end user will detect it. As testers’ work
is rarely tested, their responsibility is even bigger. And the responsibility
of the whole testing community is to find better and better test design
techniques, working methods, and test selection criteria by which testers
can detect as many bugs as possible. This remains true even if you
test the tests using mutation testing or by other efforts (Szabados and
Kovács 2015).
1
https://yurichev.com/mirrors/knuth1989.pdf
222
Chapter 4 Developers and Testers Should Constitute a Successful Team
In the last couple of years, shift-left testing has arrived at the stage
of quality assurance. It requires testing as early as possible during the
SDLC to reduce the number of defects and costs. Many people think that
shift-left testing simply means creating more automated UI and API tests.
However, admitting the benefits of automation and fuzzing, there are
some important steps before the developer should commit the code to the
repository for further automation. We think that these steps are essential
and independent from the actual testing hypes:
d. Others
223
Chapter 4 Developers and Testers Should Constitute a Successful Team
1. Behave as a team.
224
Chapter 4 Developers and Testers Should Constitute a Successful Team
225
Chapter 4 Developers and Testers Should Constitute a Successful Team
226
Chapter 4 Developers and Testers Should Constitute a Successful Team
Summary
In this chapter, we showed how developers and testers can mutually
support each other. We showed the usefulness of delta debugging. Non-
reproducible bugs are very rare, so we presented a way to resolve them. We
described how testers and developers can work together, building a great
team. We stressed that tests may also include bugs that can be revealed
during testing. Both developers and testers should be aware of this and
handle it as a natural phenomenon.
227
CHAPTER 5
Conclusion
Now, we bring our thoughts to a close. In this book, we have introduced
several key concepts, including two-phase model-based testing, action-
state testing, and optimized domain testing. Additionally, we have
demonstrated that relying on two- and three-point boundary value
analysis (BVA) is not a reliable approach, but instead, we have introduced
the four-point test selection criterion. We have elucidated the process
of state selection for state-based testing and conducted a comparative
analysis of stateless, stateful, and mixed testing methods. Furthermore, we
have redefined fundamental testing principles and exposed the reasons
why certain traditional principles no longer hold true.
If you have reached this conclusion, it can be assumed that you
have gained valuable knowledge and have become a more proficient IT
professional. As the authors, we have also benefitted significantly from the
process of writing this book.
We firmly believe that by implementing these innovative techniques,
you will be able to detect more control flow bugs and a substantial portion
of computation bugs. You might wonder, “Why is this beneficial for me?”
The answer is straightforward. Firstly, your customers and users will
experience higher satisfaction. Secondly, the overall software development
life cycle (SDLC) cost will be reduced. This reduction occurs because the
testing costs only experience a marginal increase, thanks to the extensive
automation of the introduced techniques. Meanwhile, the cost of rectifying
defects decreases significantly, as illustrated in Figure 5-1.
Figure 5-1. Total cost with traditional and with the new test design
techniques
Note that the cost of applying these new testing techniques is low,
while their bug-revealing capabilities are high. These techniques are
automated, and only the most exciting work, the design, and the modeling
remain for you. The authors are using these techniques and become better
testers. It’s important to note that traditional testing methods typically
detect only around 90% of bugs (Jones and Bonsignour 2011). Although
we can’t provide an exact figure for defect detection efficiency (DDE)
using these new techniques based on a few examples, we firmly believe
it’s closer to 100% than 90%. Even a slight improvement in DDE can lead
to substantial savings. For instance, if DDE increases from 90% to 95%, this
means that instead of 100 bugs, only 50 remain in the code, resulting in a
50% reduction in bug-fixing costs. However, it’s essential to remember that
traditional techniques should not be disregarded; they can complement
the new ones effectively.
In this book, our objective was not only to introduce innovative and
valuable software testing techniques and methods but also to equip
you with practical knowledge for creating and automating tests more
efficiently, enabling you to uncover more bugs in less time. Our aim was to
230
Chapter 5 Conclusion
enhance overall code quality and reduce the total software development
life cycle (SDLC) costs. In summary, we offer the following pieces
of advice:
231
Chapter 5 Conclusion
232
A
ppendixes
Appendix I: Java Code for Quicksort
import java.io.*; //not necessary
import java.util.*; // not necessary
// Recursion
if (low < j)
quicksort(numbers, low, j); //low
if (i < high)
quicksort(numbers, i, high);
}
234
APPENDIXES
235
APPENDIXES
T6
add two cars
add bike
add car
add car
T7
add two cars
add bike
add car
delete car
T8
add two cars
add bike
add car
delete third car
add car
T9
add two cars
add third or more car
delete car
add bike
add car
T10
add two cars
add third or more car
delete car
add bike
add bike
T11
add two cars
add third or more car
delete car
236
APPENDIXES
add bike
delete car
T12
add two cars
add third or more car
delete car
add bike
delete bike
add bike
T13
add two cars
add third or more car
delete car
add bike
delete bike
add car
T14
delete bike
add two cars
add third or more car
delete car
add bike
add car
delete bike
T15
add two cars
add third or more car
delete car
add bike
delete bike
delete car
delete car
237
APPENDIXES
T16
add two cars
add third or more car
delete car
add bike
delete bike
delete car
add bike
T17
add two cars
add third or more car
delete car
add car
add bike
delete bike
delete car
delete bike
T18
add two cars
add third or more car
delete car
add bike
delete bike
delete car
add car
T19
add two cars
add third or more car
delete car
add bike
238
APPENDIXES
delete bike
delete car
add car
239
APPENDIXES
T5
add car
add car
add bike
add car
add car
T6
add car
add car
add bike
add car
delete car
T7
add car
add car
add bike
add car
add bike
T8
add car
add car
add bike
add car
delete bike
T9
add car
add car
add car
delete bike
add bike
240
APPENDIXES
T10
add car
add car
add car
delete car
add car back
T11
add car
add car
add car
delete car
delete car
T12
add car
add car
add car
delete car
add car
T13
add car
add car
add car
delete car
add bike
delete bike
T14
add car
add car
add car
add car
delete car
241
APPENDIXES
T15
add car
add car
add car
add bike
delete bike
242
APPENDIXES
• variableName(bool)
• variableName(int)
• variableName(num)
• variableName(num,0.02)
• *
if there is no constraint on this variable
244
APPENDIXES
• true or false
<30 or !=12.62
IF isVIP = true AND price > 100 AND price <= 199.99 AND
discount > 20
245
APPENDIXES
[45,60); <30
>=60; <30
>=60; >=30
246
G
lossary
Many of the following definitions are based on the ISTQB glossary,
available at https://glossary.istqb.org.
Action-state testing: A step-by-step test design technique where the
abstract steps are created one by one, controlled by a simple algorithm.
Black-box testing: Functional or nonfunctional testing without
referencing the internal structure of the component or system.
Boundary value analysis: A black-box test design technique in
which test cases are designed based on boundary values of equivalence
partitions.
Competent programmer hypothesis: Programmers create programs
that are close to being correct; that is, the specification and the appropriate
code are close to each other.
Competent tester hypothesis: Testers create test cases that are close to
the reliable test set for the correct program.
Coupling effect hypothesis: A test set that can detect the presence of
single faults in the implementation is also likely to detect the presence of
multiple faults.
Defect prevention: A process that identifies, analyzes, and collects
defect information to prevent implementing new defects in the code.
Domain-based testing: A black-box test design technique that is
used to identify efficient and effective test cases when more parameters
can or should be tested together. It establishes, builds on, and generalizes
equivalence partitioning and boundary value analysis.
Equivalence partitioning: A black-box test design technique in which
test cases are designed to exercise disjoint partitions by using at least one
representative input value of each partition. If a single input detects (or
does not detect) a failure, then the system behaves the same way for all
other inputs in the same partition.
First-order bug: A single data parameter triggers a failure whenever
a program point has been reached along any program path from the data
entry point. See single fault assumption.
Optimized domain testing (former general predicate testing): It
determines the best reliable test set in case of being given several logically
dependent requirements.
Multiple fault assumption: It assumes that more than one data
component together leads to a failure. See also single fault assumption.
Mutation testing: A method of testing the tests in which slightly
modifying the original code creates several mutants. A reliable test data
set should then differentiate the original code from all the well-selected
mutants.
NP-completeness: Let NP denote the set of all decision problems
whose solutions can be verified in polynomial time. A problem p in NP
is NP-complete if every other problem in NP can be transformed (or
reduced) into p in polynomial time.
Reliable test design technique: For any test set fulfilling the test
design technique, there is at least one error-revealing test case.
Risk analysis: The overall process of risk identification and risk
assessment.
Second or higher-order bugs: If the number of parameters causing
the failure together is two, then the bug is a second-order bug, similarly, to
higher-order bugs. See multiple fault assumption.
Single fault assumption: It relies on the statistic that failures are only
rarely the product of two or more simultaneous faults. Here we assume
that a fault in a program occurs due to a single program statement (or the
absence of it).
State transition testing: A black-box test design technique in which
the behavior of an application under test is analyzed for different input
events in a sequence. The technique models the specification via states
248
GLOSSARY
249
R
eferences
Adzic, G. (2011), “Specification by example: how successful teams
deliver the right software,” Manning Publications, Westampton, ISBN 13
9781617290084.
Ammann, P., Offut, J. (2008), “Introduction to software testing,”
Cambridge Univ. Press, ISBN-13 9780521880381.
Bach, J. (2000), “Session-based test management,” Software Testing
and Quality Engineering Magazine, 32–37, www.satisfice.com/download/
session-based-test-management.
Beizer, B. (1990), “Software testing techniques,” Van Nostrand
Reinhold, ISBN-10 0442245920.
Binder, R.V. (1999), “Testing object-oriented systems: models, patterns,
and tools,” Addison-Wesley, ISBN-13 9780321700674.
Buxton, J.N. and Randell B., eds (1970), Software Engineering
Techniques. Report on a conference sponsored by the NATO Science
Committee, Rome, Italy, 27–31 October 1969, p. 16.
Cheng, K.-T. and Krishnakumar, A.S. (1996), “Automatic generation of
functional vectors using the extended finite state machine model,” ACM
Transactions on Design Automation of Electronic Systems, 1 (1), 57–79.
Chow, T.S. (1978), “Testing software design modelled by finite-state
machines,” IEEE Transactions on Software Engineering, 4 (3), 178–187.
Clarke, LA., Hassell, J., and Richardson, DJ. (1982), “A close look at
domain testing” (1982). IEEE Transactions on Software Engineering, 8/4
380–390.
Cockburn, A. (2000), “Writing effective use cases,” Boston, Addison-
Wesley, ISBN-10 0201702258.
Dalal, S.R., Jain A., Karunanithi, N., Leaton, J.M., and Lott, C. M.,
Model-based testing of a highly programmable system. In F. Belli, editor,
Proceedings of the Ninth International Symposium on Software Reliability
Engineering, pages 174–178, Nov. 1998.
Davis, M., Sigal, R. and Weyuker, E.J. Computability, complexity, and
languages: fundamentals of theoretical computer science, Elsevier, 1994.
DeMillo, R.A., Lipton, R.J., and Sayward F.G. (1978), “Hints on test data
selection: help for the practicing programmer,” Computer, 11 (4), 34–41.
Forgács, I. and Kovács, A. (2019), A. “Practical test design,” BCS, ISBN
10 1780174721.
Gaines, L. (2021), “Quantifying the cost of fixing vs. preventing bugs,”
www.coderskitchen.com/quantifying-the-cost-of-fixing-vs-
preventing-bugs/ (last visit Aug. 2021).
Gong, D. and Yao, X. (2010), “Automatic detection of infeasible paths in
software testing,” IET Software, vol. 4, no. 5, pp. 361–370.
Goodenough, J.B. and Gerhart S.L. Toward a Theory of Test Data
Selection. IEEE Transactions on Software Engineering, 1(2), 156–173,
June 1975.
Grindal, M., Offutt, J., Andler, S. (2005), “Combination Testing
Strategies: A Survey. In: Software Testing, Verification and Reliability,” John
Wiley & Sons Ltd., 15, pp. 167–199.
Grochtmann, M., Grimm, K. (1993), “Classification Trees for Partition
Testing,” In: Software Testing, Verification & Reliability, Wiley, 3(2),
pp. 63–82.
Hamlet, R.G. (1977), “Testing programs with the aid of a compiler,”
IEEE Transactions on Software Engineering, 3 (4), 279–290.
Howden, W.E. (1976), “Reliability of the path analysis testing strategy,”
IEEE Trans. Softw. Eng. SE-2, 3 (Sept.), 208–215.
Huang, R., Sun, W., Xu, Y., Chen, H., Towey, D., & Xia, X. (2021), “A
Survey on Adaptive Random Testing,” IEEE Transactions on Software
Engineering 47(10), pp. 2052–2083.
252
REFERENCES
253
REFERENCES
254
REFERENCES
255
Index
A ATM authentication
“add wrong PIN”, 108
Abstract test cases, 57, 58,
with EFSM, 76
155–157, 164
with FSM, 74, 75
Action-state techniques, 205, 206
program states, 78
Action-state testing, 229
use case testing, 70, 71
abstract test step, 90, 91
Atomic predicates
advantages and
adjacent partitions, 140
disadvantages, 111
age implementations, 134
car rental specification, 91
create action-state model, 93, computer experiments, 136
94, 100–108 data points, 133, 136–138, 140
fork, 96, 97 examined domain, 132, 133
graph and step, 94, 95 factorial, 141
initial state, 98 fault detection, 136–138
join fork, 97 interpretations, 134
labels, 95 logical operator, 139, 140
model hierarchy, 98 mutants, 135
responses, 91–93 potential faults, 134
vs. stateful modeling, 108–111 range testing, 140
step-by-step technique, 90 tester, 136
test selection criteria, 98–100 testing, 143
test states, 92 Automated test design, 43, 48, 66
two-phase modeling, 92
Adaptive combinatorial testing, 129
All-edge criterion, 52, 66, 84, 85, B
115, 116 Black-box testing, 5, 6
All-transition criterion, 52, 79, 82, Boundary-based approach
83, 88, 98, 99, 115 extending predicates, 156, 157
258
INDEX
E
End user, 35, 219, 221, 222 F
Equivalence partitioning (EP), Failure, 3, 45, 212, 213, 234
119, 205 Fault-based testing, 26–30
259
INDEX
L
G Linear predicates, 132, 133
General predicate testing Logical operators, 132, 139,
(GPT), 119 205, 245
Gherkin-like syntax, 210
Guard conditions, 66, 74, 77–79,
87, 88, 109
M
Model-based testing (MBT), 222
bug detection, 44–48
H comparison, MBT
Higher-order bug, 44–48, 71, approaches, 118
72, 77, 127 computer-readable, 49
Holiday booking system detect bug, 112–115
260
INDEX
O
Online bookstore P
abstract tests, 161, 162, 167, 168 Paid vacation days, 132
cards benefit, 158 abstract tests, 182
concrete tests, 162, 163, 169 additional days, 170
decision table, 165 ages, 170
new books, 158 boundary-based approach
ON/OFF/IN/OUT points, 159 abstract tests, 173, 174
price difference, 158 bugs, 174
price reduction, 165 concrete tests, 174, 175
purchasing books, 165 conditions, 171–173
261
INDEX
262
INDEX
263
INDEX
264
INDEX
265
INDEX
W
U White-box testing, 6, 132, 226
UI test, 220
Use case testing, 68, 69, 71
requirements, ATM X, Y, Z
authentication, 70 XPath (XML Path Language), 208
266