Notes for Quality Assurance (Qualitätssicherung) — Final Exam
Course study interface
Quality Assurance (Qualitätssicherung) — Final Exam
Software quality assurance and testing: motivation and quality factors, the cost model, error/fault/failure, static QA (metrics, reviews, cyclomatic complexity), testing basics and ISTQB principles, test levels and integration strategies, coverage (C0–C4) and black/gray/white-box testing, test design (equivalence classes, boundary values), test doubles and readable/data-driven tests, TDD, fuzzing, asserts, the test process and defect management, non-functional and automated tests, organizational QA, quality-management standards, and formal verification.
Quality Assurance — Foundations and Motivation
Why QA matters, quality factors, the cost model, the QA method taxonomy, verification vs. validation, and the causes of software errors.
Notes for Why quality assurance matters and where errors come from
Why quality assurance matters and where errors come from
Software is never error-free (Murphy's Law: "anything that can go wrong will go wrong"); modern software has millions of lines and therefore millions of chances to go wrong — studies suggest 15–50 errors hide per 1000 lines of code. Famous failures: Therac-25ExampleTherac-25 is commonly cited as a safety-critical software failure where inadequate safeguards and testing contributed to radiation overdoses.addition, not part of the lecture (the classic "test failure" example), a 2004 Austrian software fault that paralysed phones, and a 2007 Skype update causing a worldwide outage.
Automatic tests are widely used in practice: among the 100 most popular Java GitHub projects there are 150,000+ unit testsunit tests later
Test levels, test types, and testing roles
Systems are built by "divide and conquer": split the task into manageable parts with verifiable hand-over points, each tested independently. The test levels (bottom-up) are Component (Unit) test, Integration test, System test, Acceptance test; each level contains several test types and each type needs its own methods and tests one or more quality criteria.
Test types include: functional tests; performance tests; stress test (behavior under overload); mass/volume test (large data); reviews; and more.
Component test (Unit test): checks separately testable components (modules, objects, classes) in isolation using test doubles and test drivers; found errors are clearly attributable; test cases derived from development documents; needs debugging tools or a component-test environment (e.g. JUnit).
System test: focuses on the whole system's specified behavior in an environment equivalent to production (even minimal config differences can cause production problems despite passing tests); broad coverage of functional and non-functional requirements; usually the last full check before delivery. Smoke test: minimal, partly random tests ensuring no totally unusable version is released (does it start? does it crash?).
Test manager: interface between test team and project management; responsible for a working test process; plans testing with project management and assigns work packages; controls progress and writes the test report.
Test engineer (Testingenieur): turns the test manager's strategy (which/how many tests, what focus) into test cases.
Test developer: realizes the test cases designed by the test engineer.
Tester: actually runs the tests using the test cases and the manager's specifications; test logs feed corrections, reporting and progress control.
Test strategy: an approach for efficient, economical tests, set in the test plan — defines test types per quality attribute, their order, and test intensity (focus on areas with high error probability).
Go to block; SQLite ships 644× as much test code as production code; Apache Commons, OkHttp, JodaTime, Apache HttpCore average over 82% coveragecoverage criteria later
Control-flow coverage measures (C0–C4)
Structural (white-box) methods follow the control-flow graph; full 100% coverage is often impossible or unnecessary (e.g. for stable libraries) — identify the elements critical to functionality.
C0 — Statement coverage: every statement (node) is executed at least once. Weak (too easy: one execution counts as 100% tested) and misses interactions, but fast to compute and good for finding completely untested code.
C1 — Branch/edge coverage: every branch (edge) of the control flow is traversed at least once — all true/false outcomes (an if without else still has 2 edges), all case/switch variants, all exceptions/returns. C1 guarantees C0 but not vice versa; weakness: one loop pass counts as a full loop test, and complex conditions are evaluated only as a whole.
C2 — Simple condition coverage: each atomic sub-condition must take both truth values (considered individually). 100% C2 does not guarantee C0/C1, and short-circuit evaluation (||/&&) can leave a sub-condition's evaluation unchecked despite 100% C2 — so a fault in it may be missed.
C3 — Multiple condition coverage (branch condition combination): also checks all composite sub-decisions (truth tables); extremely costly — 2^n test cases for n sub-decisions. Minimal multiple-condition coverage (MC/DC): only combinations where changing one atomic condition changes the overall condition.
C4 — Path coverage: executes all distinct paths through the test object (a path = a unique sequence from entry to exit), considering dependencies like loops. The number of paths explodes (10 ifs → 1024 cases; loops → unbounded), so decide per system part where it is really needed.
Go to block. If old functionality keeps breaking after changes, (automatic) tests are a key step to improve the situation.
Faulty requirements documentation: wrongly defined, missing, incomplete or unneeded requirements.
Misunderstandings from broken customer–developer communication (e.g. misinterpreting requirements).
Logical design errors: architects/developers formulate faulty or insufficient requirements/architectures.
Coding errors: misinterpreting the design document, language errors, faulty test-data selection.
Non-compliance with standardized coding/documentation guidelines, leading to misunderstandings and wrong assumptions about code.
Notes for Software quality factors
Software quality factors
Quality factors let us make statements about a software's quality:
Functionality (Funktionalität): presence of functions with defined properties.
Reliability (Zuverlässigkeit): maintaining a performance level under given conditions over time.
Usability (Benutzbarkeit): the effort needed for users to use the software.
Efficiency (Effizienz): ratio of performance level to resources used.
Changeability (Änderbarkeit): effort needed to make given changes.
Portability (Übertragbarkeit): how easily the software migrates to another environment.
Notes for Costs of quality assurance and cost optimization
Costs of quality assurance and cost optimization
Total costs combine production costs and quality costs. The cost of fixing an error — and thus the quality cost — rises exponentially with the error's latency (the gap between when an error is made and when it is fixed). A key goal is to avoid errors early and prevent their follow-up costs.
Cost optimization is the point where the ratio between error costs, error-avoidance costs and quality is optimal — where the cost of avoiding/checking errors equals the cost of fixing them, and total costs are lowest. (Quality-cost question 2.1: testing effort itself costs — creating/maintaining manual or automatic tests — so even good quality has a price.)
Notes for The QA method taxonomy
The QA method taxonomy
Static analytical QA
Checks a test objectstatic QA later
Static QA and static analysis
Static QA covers all analytical activities that check a test object without executing it (e.g. code reviews); its advantage is that test objects can be checked very early, before they are runnable. Static analysis ensures the "internal quality" (software structure and design): it checks individual parts long before they are assembled into a complex running system.
Structure analysis: determines and guarantees internal quality across the whole process, based on dependency graphs and quality metrics.
Error-pattern analysis: checks source against typical error patterns.
Static analysis tools: check compliance with development guidelines.
Go to block without executing it — static analysis / code metrics, and reviews / inspections / walkthroughs.
Dynamic analytical QA
Checks behavior by executing the code — testing and dynamic analysis.
Organizational QA
Templates / checklists, knowledge management, and quality-management standards that provide the framework for the analytical measures.
Appropriate software quality arises mainly from systematic work using proven construction techniques (including the Block 3 and 4 contentimplementation basics
Component-Oriented Development
Goals and parts of component-oriented development, Dependency Injection, Design by Contract, composition vs. inheritance, complex object creation (builder), and aspect-oriented programming.
Go to block); organizational measures create the conditions so analytical (static and dynamic) measures can find errors.
Notes for Verification vs. validation
Verification vs. validation
Verification (Verifikation)
Checks the product against its specification — does the result meet the specified/technical requirements? 'Was the product built right?'
Validation (Validierung)
Checks the specification/solution against the customer's requirements — does the solution meet the customer's needs? 'Was the right product built?'
Often used synonymously but with opposite aims; technical measures (e.g. tests) mainly achieve verification, while validation needs the customer to confirm the right product was built.
Error, Fault, and Failure
The distinction between error (Irrtum), fault/defect, and failure (Fehlverhalten), the residual-defect table, and defect prioritization under schedule pressure.
Notes for Error vs. fault vs. failure
Error vs. fault vs. failure
Software Error (Irrtum) — process quality
A human activity that produces incorrect results (e.g. incorrect code from a typo). The cause/origin of the problem.
When a system is made unable to perform a required function correctly — the defect embedded in the product.
Software Failure (Fehlverhalten) — quality in use
The actual result deviates from the expected result — the observable misbehavior.
Not every fault leads to a failure: a fault must be triggered to cause a failure, and many faults are rarely or never triggered. The three are distinguished because they occur at different points and are addressed differently. (Y2K example: the error is the assumption/typo of two-digit years, the fault is the date-handling code, the failure is the wrong date computation when the year 2000 is reached.)
Notes for Residual-defect table (errors at delivery)
Residual-defect table (errors at delivery)
Defect counts by application size; bug-finding is never 100% effective, so residual (Restfehler) and severe residual defects remain after delivery (lecture slide 6 / worksheet 2.3).
Application
[object Object]
Number of errors
Errors at delivery (residual)
Severe residual errors
Autopilot
30,000
1,500
60
6
Navigation system
500,000
25,000
1,000
100
Flight-control software
1,000,000
50,000
2,000
200
Nuclear-plant control
1,500,000
75,000
3,000
300
Even critical software ships with thousands of residual defects, yet planes rarely crash and plants rarely explode because most faults are never triggered, redundancy/processes catch the rest, and the severe residual defects are a small fraction. The numbers scale roughly linearly with size (~5% of total errors remain at delivery, ~0.4% severe).
Notes for Defect prioritization under schedule pressure
Defect prioritization under schedule pressure
Scenario: As project leader you receive test/review results: several minor defects, three severe defects, and one missing feature — while you are already weeks behind and want to push ahead. Options: (1) fix all problems first, then plan/build the missing parts; (2) start the missing parts immediately, fix problems only when you must use the broken functions; (3) prioritize and fix only the worst problems before continuing; (4) mainly fix found problems while doing preparatory work on areas with only minor issues.
Interpretation: The reasoned choice is option 4 (or a prioritized variant of 3): fix the severe defects and the missing feature first because defect latency costcost model explained above
Costs of quality assurance and cost optimization
Total costs combine production costs and quality costs. The cost of fixing an error — and thus the quality cost — rises exponentially with the error's latency (the gap between when an error is made and when it is fixed). A key goal is to avoid errors early and prevent their follow-up costs.
Cost optimization is the point where the ratio between error costs, error-avoidance costs and quality is optimal — where the cost of avoiding/checking errors equals the cost of fixing them, and total costs are lowest. (Quality-cost question 2.1: testing effort itself costs — creating/maintaining manual or automatic tests — so even good quality has a price.)
Go to block grows exponentially and building on broken foundations multiplies rework, but parallelize low-risk preparatory work on areas with only minor issues to recover schedule. Pure option 1 ignores the deadline; option 2 maximizes latency cost.
Static QA covers all analytical activities that check a test objectDefinitionThe artifact being checked, such as requirements, design, code, or another development result.addition, not part of the lecture without executing it (e.g. code reviews); its advantage is that test objects can be checked very early, before they are runnable. Static analysis ensures the "internal quality" (software structure and design): it checks individual parts long before they are assembled into a complex running system.
Structure analysis: determines and guarantees internal quality across the whole process, based on dependency graphsExplanationGraphs that show which code units or modules depend on which others, making coupling and structural violations visible.addition, not part of the lecture and quality metrics.
Error-pattern analysis: checks source against typical error patterns.
Static analysis tools: check compliance with development guidelines.
Notes for Software metrics
Software metrics
Software metrics measure software quality. Process metrics measure process properties (quantitative process data); product metrics measure the software (or parts) regardless of how it came to be.
Size metrics: measure module size and help decide when to split oversized modules — LOC (Lines of Code) or the more meaningful NOS (Number of Statements).
Halstead metric: assumes a program consists of operators and operands, counted via formulas.
Logical structure metrics: measure module structure such as number of paths or loop-nesting depth — best-known is the McCabe metric (cyclomatic complexity, the most used).
OO metrics measure object relationships/structure (how good is the OO design?), driven by the Block-3 design principles: CBO (coupling between objects), DIT (depth of inheritance tree), RFC (response for a class), WMC (weighted methods per class), LCOM (lack of cohesion in methods, ideally 1).
Notes for Cyclomatic complexity (McCabe)
Cyclomatic complexity (McCabe)
\[M = E - N + 2\]
M
The cyclomatic complexity (number of independent paths).
E
Number of edges in the control-flow graph.
N
Number of nodes in the control-flow graph.
Cyclomatic complexity measures the complexity of a code piece (e.g. a method) from its control-flow graphDefinitionA graph representation of possible execution flow through code, with nodes for program points and edges for control transfers.addition, not part of the lecture (as in C1/edge coveragecoverage criteria later
Control-flow coverage measures (C0–C4)
Structural (white-box) methods follow the control-flow graph; full 100% coverage is often impossible or unnecessary (e.g. for stable libraries) — identify the elements critical to functionality.
C0 — Statement coverage: every statement (node) is executed at least once. Weak (too easy: one execution counts as 100% tested) and misses interactions, but fast to compute and good for finding completely untested code.
C1 — Branch/edge coverage: every branch (edge) of the control flow is traversed at least once — all true/false outcomes (an if without else still has 2 edges), all case/switch variants, all exceptions/returns. C1 guarantees C0 but not vice versa; weakness: one loop pass counts as a full loop test, and complex conditions are evaluated only as a whole.
C2 — Simple condition coverage: each atomic sub-condition must take both truth values (considered individually). 100% C2 does not guarantee C0/C1, and short-circuit evaluation (||/&&) can leave a sub-condition's evaluation unchecked despite 100% C2 — so a fault in it may be missed.
C3 — Multiple condition coverage (branch condition combination): also checks all composite sub-decisions (truth tables); extremely costly — 2^n test cases for n sub-decisions. Minimal multiple-condition coverage (MC/DC): only combinations where changing one atomic condition changes the overall condition.
C4 — Path coverage: executes all distinct paths through the test object (a path = a unique sequence from entry to exit), considering dependencies like loops. The number of paths explodes (10 ifs → 1024 cases; loops → unbounded), so decide per system part where it is really needed.
Go to block). Lower is better; companies may auto-reject code exceeding a maximum. Reduce it by extracting methods and removing/simplifying branches (e.g. splitting the sqrt method's if/while logic into smaller methods). It indirectly measures quality because the Block 3/4 design principlesimplementation principles earlier
Component-Oriented Development
Goals and parts of component-oriented development, Dependency Injection, Design by Contract, composition vs. inheritance, complex object creation (builder), and aspect-oriented programming.
A review is a formally organized meeting of people to check a product part's content or form against given criteria/checklists — a qualitative assessment of products and processes, not of the author. Its effectiveness depends on correct conduct.
Advantages: applicable to all development results (requirements, designs, source code); can run very early before executable programs exist; shortens error latency, reducing fix costs (offsetting the review effort); relativizes developer self-assessment and fosters knowledge transfer.
Disadvantage: the author can become 'the accused', making reviews unpleasant or team-damaging (e.g. dwelling on single errors). Solution: build an open, critical but fair review culture.
Roles: Manager (commissioned the object, responsible for release); Moderator/Review leader (keeper of process — plans, organizes, leads, ensures an open atmosphere); Recorder/Scribe (preserver of knowledge — writes the review report); Author (creator, attends to clarify but not justify); Reviewer/Inspector (examines and reports findings; cannot also be the author); Reader (keeper of focus and pace).
Process phases: Planning, Initialization, Preparation (the core: reviewers search for defects), Session (defects communicated and documented, not corrected), Rework (author corrects), Third Hour (optional discussion), Analysis (optional systematic evaluation/process improvement). Preparation/Session and Rework are necessary; Third Hour and Analysis are optional.
Notes for Review types and code reviews
Review types and code reviews
With customer: Software Requirements Review (SRR, after requirements before design), Preliminary Design Review (PDR, in design phase), Critical Design Review (CDR, for especially critical components before implementation), In-Process Review (IPR, shows progress/prototypes/test cases — important for large projects to avoid developing past customer wishes).
Without customer: Management Review (MR, formal project-status assessment); Inspection (formal review) and Code Walkthrough (weakened review) — strongly formalized types aimed at finding defects, the most common/important/effective review method; Technical Review (checks a concrete part against specs/standards).
Round-Robin Review: an alternative where reviewers search for positive arguments and try to convince colleagues of the object's quality.
Code reviews (a classic you will give/receive): analyse code in advance, identify wrong decisions and their type (syntax, runtime, logic), explain/justify findings face-to-face, give improvement suggestions and how to avoid the issue in future — treat them as reasoned tips, not criticism. Reviews should be combined with technical QA (e.g. unit tests).
Testing Basics
The definition and importance of testing, its goals, the ISTQB principles, and what makes a good vs. a bad test (including testing the tests).
Per IEEE 610.12: "Software testing is a formal process carried out by a specialized testing team in which a software unit, several integrated software units or an entire software package are examined by running the programs on a computer. All the associated tests are performed according to approved test proceduresDefinitionA test procedure is the ordered instruction for executing a test case, including setup, actions, observations, and recording steps.addition, not part of the lecture on approved test cases."
Testing is a dynamic, product-oriented QA technique. As a formal process it is planned more or less strictly depending on risk (a combination of formal and informal is usually best; well-planned formal tests find up to 6× more errors). Who tests matters (a separate test team is often less biased than developers), and approved/traceable test casestest-case documentation later
Test-case documentation and best practices
Documentation enables a systematic, transparent, traceable test process and helps debugging; it spans all levels from component to system tests.
A test case contains: a running ID; type (normal NF, special SF, error FF); preconditions; input values; description (which component, which property); expected result; actual result; decision (do expected and actual match?).
Best practices (worksheet 3.10): keep tests small and well-organized; ensure reproducibility and side-effect-freeness; abstraction & isolation; avoid Thread.sleep (flaky, slow — it is 'evil'); use frameworks and keep tests green; handle unexpected exceptions explicitly. Unprepared/undocumented tests are largely useless because they are not repeatable or traceable.
Go to block matter when responsibility shifts (e.g. development → operations).
Notes for Goals of testing and what makes a good test
Goals of testing and what makes a good test
Of three common definitions — (1) testing demonstrates a program does what it should, (2) testing runs a program intending to find errors, (3) the IEEE 'operate under specified conditions, observe/record results, evaluate' — the error-finding view (2) and the neutral evaluation view (3) are preferred; view (1) only seeks confirmation.
A good test has a high probability of finding errors; a bad test rarely does. You can "test the tests": e.g. mutation testing deliberately injects faults to check whether the tests catch them, or measure how many real defects the tests detect (better than coveragecoverage limits later
Coverage traps — coverage is necessary but not sufficient
The primary use of coverage is to answer "have I tested enough?", but high coverage does not mean good testing.
The 'tester walks into a bar' joke: a tester orders 2 / 0 / 99999999 / a lizard / -1 / 'qwertyuiop' beers — covering many edge inputs — but a real customer asks where the toilet is and the bar burns down: tests can achieve high coverage yet miss whole classes of real-world inputs/scenarios.
Triangle-test lesson: it is hard to enumerate all relevant cases (valid: equilateral, isosceles, right-angled, scalene; invalid: triangle inequality violated, degenerate, negative side, wrong parameter count, zeros) even though counting coverage afterwards is easy.
Combine coverage with a 'does it find errors?' view: prefer measuring detected (real or mutated) defects, not coverage alone, to judge sufficiency. Note: this course's coverage definitions differ from Wikipedia — use the LV definitions.
Goal: establish the correctness/completeness of functions and find errors as early as possible.
A good test identifies new, unknown errors; the priority is to find and document errors reproducibly so they can be fixed (reproducibility is secondary).
The number of errors is unknown, so you can never prove all errors are found — therefore cover as much functionality/behavior as possible.
A test is successful (a good test) if it found an error; bad tests find few/no errors.
Notes for The seven ISTQB testing principles
The seven ISTQB testing principles
1. Testing shows the presence of errors — it cannot prove their absence (no proof of error-freeness).
2. Exhaustive testing is impossible — tests are samples; set effort by risk/priority (higher risk → more tests).
3. Start testing early — early detection of errors.
4. Defect clustering — errors concentrate in few parts; where one is found, more hide nearby.
5. Repetition is ineffective — repeating the same test cases yields no new findings (pesticide paradox).
6. Testing is context-dependent — adapt to the test objectDefinitionThe test object is the artifact being checked, such as a component, integrated subsystem, whole system, document, or model.addition, not part of the lecture and its environment.
7. Absence-of-errors fallacy — removing all errors does not mean the system meets users' needs.
Test Levels, Types, and Integration Strategies
The test levels (component, integration, system, acceptance) and types, the integration strategies (big-bang, top-down, bottom-up, vertical), regression testing, and test roles.
Notes for Test levels, test types, and testing roles
Test levels, test types, and testing roles
Systems are built by "divide and conquer": split the task into manageable parts with verifiable hand-over points, each tested independently. The test levels (bottom-up) are Component (Unit) test, Integration test, System test, Acceptance test; each level contains several test types and each type needs its own methods and tests one or more quality criteria.
Test types include: functional tests; performance tests; stress test (behavior under overload); mass/volume test (large data); reviews; and more.
Component test (Unit test): checks separately testable components (modules, objects, classes) in isolation using test doublesexplained later
Test drivers and test doubles
Test driver (Testtreiber)
A special test interface/layer that calls the component under test and monitors/controls its execution (drives it with test data).
Dummy
Provides only an interface with no implementation (e.g. ignores method calls); used when the caller expects no return value.
Fake
Has an implementation but only simulates functionality (often strongly abstracted), e.g. storing data in a HashMap/ArrayList instead of a real database → faster tests.
Stub
Like a fake but even more simplified — e.g. ignores modifying calls and always returns the same test data on queries.
Mock
Usually builds on a stub/fake and records details of each call, so a unit test can verify which methods were called, in what order/number, and with which parameters.
Test doubles replace/simulate parts of the software to reduce external dependencies. They rely on the Block 3/4 'good implementation' basics: Dependency Injection swaps real implementations and doubles as needed, and interfaces make it easy to create objects that simulate others.
A special test interface/layer that calls the component under test and monitors/controls its execution (drives it with test data).
Dummy
Provides only an interface with no implementation (e.g. ignores method calls); used when the caller expects no return value.
Fake
Has an implementation but only simulates functionality (often strongly abstracted), e.g. storing data in a HashMap/ArrayList instead of a real database → faster tests.
Stub
Like a fake but even more simplified — e.g. ignores modifying calls and always returns the same test data on queries.
Mock
Usually builds on a stub/fake and records details of each call, so a unit test can verify which methods were called, in what order/number, and with which parameters.
Test doubles replace/simulate parts of the software to reduce external dependencies. They rely on the Block 3/4 'good implementation' basics: Dependency Injection swaps real implementations and doubles as needed, and interfaces make it easy to create objects that simulate others.
Go to block; found errors are clearly attributable; test cases derived from development documents; needs debugging tools or a component-test environment (e.g. JUnit).
System test: focuses on the whole system's specified behavior in an environment equivalent to production (even minimal config differences can cause production problems despite passing tests); broad coverage of functional and non-functional requirements; usually the last full check before delivery. Smoke test: minimal, partly random tests ensuring no totally unusable version is released (does it start? does it crash?).
Test manager: interface between test team and project management; responsible for a working test process; plans testing with project management and assigns work packages; controls progress and writes the test report.
Test engineer (Testingenieur): turns the test manager's strategy (which/how many tests, what focus) into test cases.
Test developer: realizes the test cases designed by the test engineer.
Tester: actually runs the tests using the test cases and the manager's specifications; test logs feed corrections, reporting and progress control.
Test strategy: an approach for efficient, economical tests, set in the test plan — defines test types per quality attribute, their order, and test intensity (focus on areas with high error probability).
Notes for Integration strategies
Integration strategies
Big-Bang
Combine all parts at once; no component needs simulating, but error localization is hard (systems influence each other). Use for small, manageable products.
Top-Down
Layer-oriented iterative: integrate the top layer first (e.g. UI) while simulating lower layers, replacing them step by step. External interfaces available early; high simulation effort (many test doublesexplained later
Test drivers and test doubles
Test driver (Testtreiber)
A special test interface/layer that calls the component under test and monitors/controls its execution (drives it with test data).
Dummy
Provides only an interface with no implementation (e.g. ignores method calls); used when the caller expects no return value.
Fake
Has an implementation but only simulates functionality (often strongly abstracted), e.g. storing data in a HashMap/ArrayList instead of a real database → faster tests.
Stub
Like a fake but even more simplified — e.g. ignores modifying calls and always returns the same test data on queries.
Mock
Usually builds on a stub/fake and records details of each call, so a unit test can verify which methods were called, in what order/number, and with which parameters.
Test doubles replace/simulate parts of the software to reduce external dependencies. They rely on the Block 3/4 'good implementation' basics: Dependency Injection swaps real implementations and doubles as needed, and interfaces make it easy to create objects that simulate others.
Layer-oriented iterative (common in practice): test the lowest layer first (e.g. persistence) via test driversexplained later
Test drivers and test doubles
Test driver (Testtreiber)
A special test interface/layer that calls the component under test and monitors/controls its execution (drives it with test data).
Dummy
Provides only an interface with no implementation (e.g. ignores method calls); used when the caller expects no return value.
Fake
Has an implementation but only simulates functionality (often strongly abstracted), e.g. storing data in a HashMap/ArrayList instead of a real database → faster tests.
Stub
Like a fake but even more simplified — e.g. ignores modifying calls and always returns the same test data on queries.
Mock
Usually builds on a stub/fake and records details of each call, so a unit test can verify which methods were called, in what order/number, and with which parameters.
Test doubles replace/simulate parts of the software to reduce external dependencies. They rely on the Block 3/4 'good implementation' basics: Dependency Injection swaps real implementations and doubles as needed, and interfaces make it easy to create objects that simulate others.
Go to block, replacing them step by step. No stubsexplained later
Test drivers and test doubles
Test driver (Testtreiber)
A special test interface/layer that calls the component under test and monitors/controls its execution (drives it with test data).
Dummy
Provides only an interface with no implementation (e.g. ignores method calls); used when the caller expects no return value.
Fake
Has an implementation but only simulates functionality (often strongly abstracted), e.g. storing data in a HashMap/ArrayList instead of a real database → faster tests.
Stub
Like a fake but even more simplified — e.g. ignores modifying calls and always returns the same test data on queries.
Mock
Usually builds on a stub/fake and records details of each call, so a unit test can verify which methods were called, in what order/number, and with which parameters.
Test doubles replace/simulate parts of the software to reduce external dependencies. They rely on the Block 3/4 'good implementation' basics: Dependency Injection swaps real implementations and doubles as needed, and interfaces make it easy to create objects that simulate others.
Go to block needed (only simpler test drivers); upper layers and system test come late.
Vertical
Function-oriented iterative: integrate a functional subset across all layers (internally iterative or big-bang). No component simulation needed; supports iterative process models; preferred from a testing view since subsets can be tested very early.
The integration test checks interfaces between components, to the system environment, and across systems. To reduce the risk of late error discovery, prefer an incremental strategy over big-bang.
Notes for Acceptance test
Acceptance test
The acceptance test proves the contractor delivered what was agreed; finding errors is not its goal. It is usually done by the customer/ users and answers: does the customer think their requirements are met? It need not be the last level (a system-integration test may follow). Requirements-elicitation mistakes are usually found here, but since the software is far advanced, fixing them is expensive — mitigate with mini-acceptance tests of partially finished components.
Aspects: user acceptance test (usability by users); operational acceptance test (admin: backup/restore, recoverability, user management, maintenance, periodic security checks); regulatory & contractual acceptance test (contractual criteria set at contract close; regulatory checks legal/standard compliance).
Alpha test: at the manufacturer's site; Beta test / field test: at the customer's site (beta versions given to a broad customer base to find errors before the final release). Both are done by customers, not developers.
Notes for Regression testing
Regression testing
In a regression test the previous version of the system defines the expected (presumably correct) behavior, rather than a classic specification. Used when the system changes (component swap, code change) to check whether previously-given functionality still holds — "does my software behave identically before and after changes?". The more complex the system, the greater the danger that a small intended change causes unforeseen side effects.
Especially suited to automation: regression cases need no change as long as only the implementation changes, not the interfaces; cases can even be auto-generated (random data, record returnsregression baseline later
Fuzzing
Fuzzing automatically generates test inputs to find problems with low effort. A fuzzer feeds (mutated/generated) inputs to the program and watches for crashes/abnormal behavior; good seed selection matters (cf. "Optimizing Seed Selection for Fuzzing", Rebert et al.).
Easily finds crash-type defects (e.g. inputs that cause exceptions/crashes) regardless of refactoring; cheap and good when a project lacks a sufficient test base (e.g. legacy projects).
For regression support, the recorded behavior/outputs must serve as the expected baseline (record returns of the old version, compare after changes).
Limits: mainly finds crashes/robustness issues, not functional-correctness bugs without an oracle; cannot replace targeted manual unit tests.
Back-to-back test: using regression as a comparison tool (version n vs n-1). Re-test: re-running all tests after a fix to confirm the error is really gone.
Coverage and Box Testing
Black/gray/white-box testing and the control-flow coverage measures C0–C4, with their hierarchy and traps.
Notes for Black-box, gray-box, and white-box testing
Black-box, gray-box, and white-box testing
Black-box test
Based on requirements/specifications; the internal structure need not be known; data-driven, aiming for high requirements coverage. Techniques: boundary-valuetechnique explained later
Boundary-value analysis
Boundary-value analysis is a special case of equivalence-class analysis: misbehavior often occurs at system boundaries (the upper/ lower ends of a class), e.g. an off-by-one error writing ≥ instead of >. Identify the class boundaries and choose test data from just around them — one value on each side of a boundary, and the boundary value itself (which must belong to one class).
Example (age >18 and <65): sensible representatives are 18 (invalid, A1), 19 (valid, A2), 64 (valid, A2), 65 (invalid, A3).
Theory: at most two boundary values per class (one per upper/lower edge); practice: possibly more per boundary (e.g. also 17 and 66), trading off finding errors vs test effort — needs experience with the system.
Go to block and equivalence-class analysistechnique explained later
Functional and informal test-design methods
Equivalence-class analysis partitions input/output values by system behavior into classes whose values are assumed equivalent: if one value triggers (or doesn't trigger) an error, all values in its class do too. Each class is tested at least once with a representative, reducing test effort.
Distinguish valid and invalid equivalence classes — invalid classes are not arbitrary corrupted data but values outside the expected range. Test cases must cover both valid and invalid inputs.
Example (calendar months 1–12): valid class 1–12; two invalid classes <1 and >12. Example (age >18 and <65): three classes A1 ≤18 (invalid), A2 >18 and <65 (valid), A3 ≥65 (invalid). Example (age >40 AND BMI >25): two classes each → four combinations.
The most common error source is the boundaries of the classes — leading to boundary-value analysis.
State-based testing: based on state machines (UML state diagrams); error cases must be specified separately; completeness criteria are state coverage, transition coverage, and event coverage. Used for technical applications, GUIs, and state-machine-defined systems.
Classification-tree method: partition inputs/states into classifications, split into disjoint classes, combine classes into test cases (each class once per classification). Combinations: minimal (each class once), maximal (every class with every other), pairwise, tripleweise.
Informal methods: wholly/partly skip systematic case derivation, rely on tester intuition/experience — not exactly repeatable or deterministic, but often effective and cheap for spotting fault-prone areas (then tested formally). Exploratory testing: simultaneous learning and testing. Ad-hoc test: run once, positive results not logged. Not suited for repeatable/automated/documented or regression tests.
Go to block. Tests an interface specification and thus every class implementing it.
White-box test
Considers the internal structure and concrete implementation; logic-driven from the source code; aims to test all code sequences; can localize errors, not just detect them; can access private elements.
Gray-box test
Combination: the exact internal build is not known, but the documentation and expected effects on external systems (e.g. database entries) are known/checkable.
Black-box advantage: the tester acts like a real user, avoiding assumptions from code knowledge (so cases aren't skipped as 'making no sense'). White-box advantage: reduces the chance that code-intended behavior is overlooked (e.g. an if that picks a different algorithm for large data would never run if black-box always picks small data). Black-box is often seen as insufficient — e.g. QuickSort vs InsertionSort behave the same externally, so a black-box test cannot target their differing internal edge cases (links to the coverage limitation).
Notes for Control-flow coverage measures (C0–C4)
Control-flow coverage measures (C0–C4)
Structural (white-box) methods follow the control-flow graphgraph symbols earlier
Cyclomatic complexity (McCabe)
\[M = E - N + 2\]
M
The cyclomatic complexity (number of independent paths).
E
Number of edges in the control-flow graph.
N
Number of nodes in the control-flow graph.
Cyclomatic complexity measures the complexity of a code piece (e.g. a method) from its control-flow graph (as in C1/edge coverage). Lower is better; companies may auto-reject code exceeding a maximum. Reduce it by extracting methods and removing/simplifying branches (e.g. splitting the sqrt method's if/while logic into smaller methods). It indirectly measures quality because the Block 3/4 design principles tend to produce less complex code.
Go to block; full 100% coverage is often impossible or unnecessary (e.g. for stable libraries) — identify the elements critical to functionality.
C0 — Statement coverage: every statement (node) is executed at least once. Weak (too easy: one execution counts as 100% tested) and misses interactions, but fast to compute and good for finding completely untested code.
C1 — Branch/edge coverage: every branch (edge) of the control flow is traversed at least once — all true/false outcomes (an if without else still has 2 edges), all case/switch variants, all exceptions/returns. C1 guarantees C0 but not vice versa; weakness: one loop pass counts as a full loop test, and complex conditions are evaluated only as a whole.
C2 — Simple condition coverage: each atomic sub-conditionDefinitionAn atomic sub-condition is a single boolean expression evaluated before it is combined with other conditions by operators such as || or &&.addition, not part of the lecture must take both truth values (considered individually). 100% C2 does not guarantee C0/C1, and short-circuit evaluation (||/&&) can leave a sub-condition's evaluation unchecked despite 100% C2 — so a fault in it may be missed.
C3 — Multiple condition coverage (branch condition combination): also checks all composite sub-decisions (truth tables); extremely costly — 2^n test cases for n sub-decisions. Minimal multiple-condition coverage (MC/DC): only combinations where changing one atomic condition changes the overall condition.
C4 — Path coverage: executes all distinct paths through the test object (a path = a unique sequence from entry to exit), considering dependencies like loops. The number of paths explodes (10 ifs → 1024 cases; loops → unbounded), so decide per system part where it is really needed.
Notes for Coverage traps — coverage is necessary but not sufficient
Coverage traps — coverage is necessary but not sufficient
The primary use of coverage is to answer "have I tested enough?", but high coverage does not mean good testing.
The 'tester walks into a bar' joke: a tester orders 2 / 0 / 99999999 / a lizard / -1 / 'qwertyuiop' beers — covering many edge inputs — but a real customer asks where the toilet is and the bar burns down: tests can achieve high coverage yet miss whole classes of real-world inputs/scenarios.
Triangle-test lesson: it is hard to enumerate all relevant cases (valid: equilateral, isosceles, right-angled, scalene; invalid: triangle inequality violated, degenerate, negative side, wrong parameter count, zeros) even though counting coverage afterwards is easy.
Combine coverage with a 'does it find errors?' view: prefer measuring detected (real or mutatedmutation testing earlier
Goals of testing and what makes a good test
Of three common definitions — (1) testing demonstrates a program does what it should, (2) testing runs a program intending to find errors, (3) the IEEE 'operate under specified conditions, observe/record results, evaluate' — the error-finding view (2) and the neutral evaluation view (3) are preferred; view (1) only seeks confirmation.
A good test has a high probability of finding errors; a bad test rarely does. You can "test the tests": e.g. mutation testing deliberately injects faults to check whether the tests catch them, or measure how many real defects the tests detect (better than coverage alone).
Goal: establish the correctness/completeness of functions and find errors as early as possible.
A good test identifies new, unknown errors; the priority is to find and document errors reproducibly so they can be fixed (reproducibility is secondary).
The number of errors is unknown, so you can never prove all errors are found — therefore cover as much functionality/behavior as possible.
A test is successful (a good test) if it found an error; bad tests find few/no errors.
Go to block) defects, not coverage alone, to judge sufficiency. Note: this course's coverage definitions differ from Wikipedia — use the LV definitions.
Test Design Techniques
Equivalence-class analysis, boundary-value analysis, state-based testing, the classification-tree method, and informal/exploratory testing.
Notes for Functional and informal test-design methods
Functional and informal test-design methods
Equivalence-class analysis partitions input/output values by system behavior into classes whose values are assumed equivalent: if one value triggers (or doesn't trigger) an error, all values in its class do too. Each class is tested at least once with a representative, reducing test effort.
Distinguish valid and invalid equivalence classes — invalid classes are not arbitrary corrupted data but values outside the expected range. Test cases must cover both valid and invalid inputs.
Example (calendar months 1–12): valid class 1–12; two invalid classes <1 and >12. Example (age >18 and <65): three classes A1 ≤18 (invalid), A2 >18 and <65 (valid), A3 ≥65 (invalid). Example (age >40 AND BMI >25): two classes each → four combinations.
The most common error source is the boundaries of the classes — leading to boundary-value analysistechnique next
Boundary-value analysis
Boundary-value analysis is a special case of equivalence-class analysis: misbehavior often occurs at system boundaries (the upper/ lower ends of a class), e.g. an off-by-one error writing ≥ instead of >. Identify the class boundaries and choose test data from just around them — one value on each side of a boundary, and the boundary value itself (which must belong to one class).
Example (age >18 and <65): sensible representatives are 18 (invalid, A1), 19 (valid, A2), 64 (valid, A2), 65 (invalid, A3).
Theory: at most two boundary values per class (one per upper/lower edge); practice: possibly more per boundary (e.g. also 17 and 66), trading off finding errors vs test effort — needs experience with the system.
State-based testing: based on state machines (UML state diagrams); error cases must be specified separately; completeness criteria are state coverage, transition coverage, and event coverage. Used for technical applications, GUIs, and state-machine-defined systems.
Classification-tree method: partition inputs/states into classifications, split into disjoint classes, combine classes into test cases (each class once per classification). Combinations: minimal (each class once), maximal (every class with every other), pairwiseDefinitionPairwise combination means selecting test cases so that every possible pair of classes from two classifications appears at least once, without requiring every full Cartesian-product combination.addition, not part of the lecture, tripleweiseDefinitionTriple-wise combination extends pairwise coverage: every three-way combination of classes across three classifications must appear at least once.addition, not part of the lecture.
Informal methods: wholly/partly skip systematic case derivation, rely on tester intuition/experience — not exactly repeatable or deterministic, but often effective and cheap for spotting fault-prone areas (then tested formally). Exploratory testing: simultaneous learning and testing. Ad-hoc test: run once, positive results not logged. Not suited for repeatable/automated/documented or regression tests.
Notes for Boundary-value analysis
Boundary-value analysis
Boundary-value analysis is a special case of equivalence-class analysistechnique above
Functional and informal test-design methods
Equivalence-class analysis partitions input/output values by system behavior into classes whose values are assumed equivalent: if one value triggers (or doesn't trigger) an error, all values in its class do too. Each class is tested at least once with a representative, reducing test effort.
Distinguish valid and invalid equivalence classes — invalid classes are not arbitrary corrupted data but values outside the expected range. Test cases must cover both valid and invalid inputs.
Example (calendar months 1–12): valid class 1–12; two invalid classes <1 and >12. Example (age >18 and <65): three classes A1 ≤18 (invalid), A2 >18 and <65 (valid), A3 ≥65 (invalid). Example (age >40 AND BMI >25): two classes each → four combinations.
The most common error source is the boundaries of the classes — leading to boundary-value analysis.
State-based testing: based on state machines (UML state diagrams); error cases must be specified separately; completeness criteria are state coverage, transition coverage, and event coverage. Used for technical applications, GUIs, and state-machine-defined systems.
Classification-tree method: partition inputs/states into classifications, split into disjoint classes, combine classes into test cases (each class once per classification). Combinations: minimal (each class once), maximal (every class with every other), pairwise, tripleweise.
Informal methods: wholly/partly skip systematic case derivation, rely on tester intuition/experience — not exactly repeatable or deterministic, but often effective and cheap for spotting fault-prone areas (then tested formally). Exploratory testing: simultaneous learning and testing. Ad-hoc test: run once, positive results not logged. Not suited for repeatable/automated/documented or regression tests.
Go to block: misbehavior often occurs at system boundaries (the upper/ lower ends of a class), e.g. an off-by-one error writing ≥ instead of >. Identify the class boundaries and choose test data from just around them — one value on each side of a boundary, and the boundary value itself (which must belong to one class).
Example (age >18 and <65): sensible representatives are 18 (invalid, A1), 19 (valid, A2), 64 (valid, A2), 65 (invalid, A3).
Theory: at most two boundary values per class (one per upper/lower edge); practice: possibly more per boundary (e.g. also 17 and 66), trading off finding errors vs test effort — needs experience with the system.
Test Doubles and Readable Tests
Test drivers and the test doubles (dummy, fake, stub, mock), test data and data-driven tests, positive vs. negative tests, readable tests with Gherkin/Cucumber, and test-case documentation.
Notes for Test drivers and test doubles
Test drivers and test doubles
Test driver (Testtreiber)
A special test interface/layer that calls the component under test and monitors/controls its execution (drives it with test data).
Dummy
Provides only an interface with no implementation (e.g. ignores method calls); used when the caller expects no return value.
Fake
Has an implementation but only simulates functionality (often strongly abstracted), e.g. storing data in a HashMap/ArrayList instead of a real database → faster tests.
Stub
Like a fake but even more simplified — e.g. ignores modifying calls and always returns the same test data on queries.
Mock
Usually builds on a stub/fake and records details of each call, so a unit test can verify which methods were called, in what order/number, and with which parameters.
Test doubles replace/simulate parts of the software to reduce external dependencies. They rely on the Block 3/4 'good implementation' basics: Dependency InjectionDI explained
Dependency Injection (DI)
DI resolves dependencies on concrete implementations, e.g. by working with interfaces — "injecting dependencies". Classes/components are decoupled from what they depend on: dependencies are not defined inside the class but passed in via constructor or setter, so the calling component supplies the dependencies at runtime. A dependency is an object a system needs to function (e.g. a service that reads data from a database).
Advantages: decouples constructing a class from constructing its dependencies; classes become as independent as possible (Inversion of Control — configure dependencies from outside); increases reusability; classes can be tested independently.
Refactor pattern (worksheet): replace internal 'new' (Service service = new Service(...) / Connection conn = new Connection()) with a constructor parameter (Customer(Service service) / Server(Connection conn)) so different services/connections can be injected and mocked in tests.
Go to block swaps real implementations and doubles as needed, and interfacesinterfaces explained
Interfaces vs. abstract classes
Interface
A pure contract of method signatures (a type can implement many interfaces). Use to define capabilities decoupled from implementation; the Interface Segregation Principle (ISP) favors fine-grained, use-case-specific interfaces over one large universal interface.
Abstract class
May provide shared state and partial implementation plus abstract methods (single inheritance). Abstract classes let you quickly build simple frameworks: the abstract class prescribes the structure and you implement the abstract method hooks (Inversion of Control).
Prefer interfaces for capability contracts and multiple implementations; use abstract classes to share implementation and build whitebox-style frameworks. Apply ISP to keep interfaces small.
Go to block make it easy to create objects that simulate others.
Notes for Test data, data-driven tests, and positive vs. negative tests
Test data, data-driven tests, and positive vs. negative tests
Test data configure the target system, test doubles and test drivers, and drive the driver during execution (a data-driven test).
Data-driven test: separates test cases from test data — cases are defined with placeholders and bound to concrete data only at execution. Benefits: cases and data reused independently; better maintainability/readability; m:n use (one test, many data sets); cases definable before concrete data exist; easier, more maintainable automation.
Positive test (most common): checks the system behaves correctly for valid input (e.g. div(2,2) returns 1). Negative test: explicitly checks that problems are (a) detected and (b) handled as expected, e.g. div(2,0) throws the expected ArithmeticException.
Notes for Readable tests — naming, Gherkin/Cucumber
Readable tests — naming, Gherkin/Cucumber
Readable test names follow a scheme like WhenWeAreInStateX_AndSomethingHappens_ThenTodoIsDone(), so a test result immediately shows what works or not. Cucumber (test framework) and Gherkin (Given/When/Then syntax) refine this idea.
A Gherkin Scenario Outline declares Given/When/Then steps plus an Examples table (e.g. day → answer: Friday→TGIF, Sunday→Nope); Cucumber step definitions (@Given/@When/@Then) bind each step to code and assert the expected vs actual answer.
This is a data-driven test: one scenario runs for every Examples row. Advantage over classic unit tests: the Given/When/Then specification is readable by non-technical stakeholders (business/customer), bridging requirements and tests, while reusing one scenario across many data rows.
Notes for Test-case documentation and best practices
Test-case documentation and best practices
Documentation enables a systematic, transparent, traceable test process and helps debugging; it spans all levels from component to system teststest levels
Test levels, test types, and testing roles
Systems are built by "divide and conquer": split the task into manageable parts with verifiable hand-over points, each tested independently. The test levels (bottom-up) are Component (Unit) test, Integration test, System test, Acceptance test; each level contains several test types and each type needs its own methods and tests one or more quality criteria.
Test types include: functional tests; performance tests; stress test (behavior under overload); mass/volume test (large data); reviews; and more.
Component test (Unit test): checks separately testable components (modules, objects, classes) in isolation using test doubles and test drivers; found errors are clearly attributable; test cases derived from development documents; needs debugging tools or a component-test environment (e.g. JUnit).
System test: focuses on the whole system's specified behavior in an environment equivalent to production (even minimal config differences can cause production problems despite passing tests); broad coverage of functional and non-functional requirements; usually the last full check before delivery. Smoke test: minimal, partly random tests ensuring no totally unusable version is released (does it start? does it crash?).
Test manager: interface between test team and project management; responsible for a working test process; plans testing with project management and assigns work packages; controls progress and writes the test report.
Test engineer (Testingenieur): turns the test manager's strategy (which/how many tests, what focus) into test cases.
Test developer: realizes the test cases designed by the test engineer.
Tester: actually runs the tests using the test cases and the manager's specifications; test logs feed corrections, reporting and progress control.
Test strategy: an approach for efficient, economical tests, set in the test plan — defines test types per quality attribute, their order, and test intensity (focus on areas with high error probability).
A test case contains: a running ID; type (normal NF, special SF, error FF); preconditions; input values; description (which component, which property); expected result; actual result; decision (do expected and actual match?).
Best practices (worksheet 3.10): keep tests small and well-organized; ensure reproducibility and side-effect-freenessside effects defined
Programming paradigms
Top-level paradigms and their sub-paradigms (one possible classification; some literature places OO outside imperative).
Non-structured: the first paradigm; allows Turing-complete algorithms (e.g. early BASIC).
Structured (late 1950s): avoids spaghetti code from goto via subroutines, loops, block structures.
Imperative: statements the computer follows; describes HOW a goal is reached. Sub-paradigms: procedural, object-oriented.
Declarative: describes the desired result; no explicit control flow. Sub-paradigms: functional, logic-based, database query languages.
Procedural (imperative): statements via function calls; no class concept, so no inheritance (e.g. Fortran, COBOL, C, Go).
Object-oriented (imperative): based on objects with attributes (values) and methods (code); class-based (objects are instances of classes; C++, Java) or prototype-based (objects are primary entities; JavaScript, Lua).
Functional (declarative): computations as math functions of their parameters only; f(x,y,z) always returns the same result for the same input → no side effects (Haskell, Erlang, Clojure).
Logic-based (declarative): based on formal logic; each statement expresses a fact or rule about the domain (Prolog, ALF).
Multi-paradigm languages mix several concepts so the programmer picks the best for the problem (C#; Java with imperative+OO+functional lambdas). Runtimes are especially flexible (C# + F#, Java + Scala).
Go to block; abstraction & isolation; avoid Thread.sleep (flaky, slow — it is 'evil'); use frameworks and keep tests green; handle unexpected exceptions explicitly. Unprepared/undocumented tests are largely useless because they are not repeatable or traceable.
TDD, Fuzzing, and Asserts
Test-Driven Development (Think/Red/Green/Refactor), fuzzing for regression support, and asserts (pre/post-conditions, invariants, weak vs. strong).
Notes for Test-Driven Development (Think → Red → Green → Refactor)
Test-Driven Development (Think → Red → Green → Refactor)
Think: choose the requirement to implement, devise test cases, sketch the architecture as a skeleton (e.g. empty methods).
Red: implement the tests; they fail because no real logic exists yet.
Green: implement the requirement step by step until the tests pass (status 'Green').
Refactor: optimize/clean the code; re-run the tests and keep them green; then pick the next requirement back at Think.
Notes for Traditional testing vs. TDD
Traditional testing vs. TDD
Traditional testing finds errors in an already existing solution, testing late (toward/after the end of development) — drawbacks: errors are found late and are harder/costlier to fix, sometimes skipped under time pressure ('banana softwareExplanationSoftware delivered immature or unfinished, with defects expected to surface and be fixed only after customers start using it.' that ripens at the customer), and it needs complete requirements/specifications.
In TDD, test cases are specified before or in parallel with implementation — the system is effectively designed via tests; it is the best practice in agile development. Benefits: immediate feedback, early detection of problems/side effectsside effects defined
Programming paradigms
Top-level paradigms and their sub-paradigms (one possible classification; some literature places OO outside imperative).
Non-structured: the first paradigm; allows Turing-complete algorithms (e.g. early BASIC).
Structured (late 1950s): avoids spaghetti code from goto via subroutines, loops, block structures.
Imperative: statements the computer follows; describes HOW a goal is reached. Sub-paradigms: procedural, object-oriented.
Declarative: describes the desired result; no explicit control flow. Sub-paradigms: functional, logic-based, database query languages.
Procedural (imperative): statements via function calls; no class concept, so no inheritance (e.g. Fortran, COBOL, C, Go).
Object-oriented (imperative): based on objects with attributes (values) and methods (code); class-based (objects are instances of classes; C++, Java) or prototype-based (objects are primary entities; JavaScript, Lua).
Functional (declarative): computations as math functions of their parameters only; f(x,y,z) always returns the same result for the same input → no side effects (Haskell, Erlang, Clojure).
Logic-based (declarative): based on formal logic; each statement expresses a fact or rule about the domain (Prolog, ALF).
Multi-paradigm languages mix several concepts so the programmer picks the best for the problem (C#; Java with imperative+OO+functional lambdas). Runtimes are especially flexible (C# + F#, Java + Scala).
Go to block, and a plausibility check of requirements/specifications. In practice: higher customer satisfaction, fewer customer-reported errors, and a shorter test phase that offsets the initial extra effort. (Worksheet account example: implement Account with getAmount/deposit/withdraw/transfer test-first, a minimal set of tests per method.)
Notes for Fuzzing
Fuzzing
Fuzzing automatically generates test inputs to find problems with low effort. A fuzzer feeds (mutated/generated) inputs to the program and watches for crashes/abnormal behavior; good seed selection matters (cf. "Optimizing Seed Selection for Fuzzing", Rebert et al.).
Easily finds crash-type defects (e.g. inputs that cause exceptions/crashes) regardless of refactoring; cheap and good when a project lacks a sufficient test base (e.g. legacy projects).
For regression supportregression testing
Regression testing
In a regression test the previous version of the system defines the expected (presumably correct) behavior, rather than a classic specification. Used when the system changes (component swap, code change) to check whether previously-given functionality still holds — "does my software behave identically before and after changes?". The more complex the system, the greater the danger that a small intended change causes unforeseen side effects.
Especially suited to automation: regression cases need no change as long as only the implementation changes, not the interfaces; cases can even be auto-generated (random data, record returns).
Back-to-back test: using regression as a comparison tool (version n vs n-1). Re-test: re-running all tests after a fix to confirm the error is really gone.
Go to block, the recorded behavior/outputs must serve as the expected baseline (record returns of the old version, compare after changes).
Limits: mainly finds crashes/robustness issues, not functional-correctness bugs without an oracle; cannot replace targeted manual unit tests.
Notes for Asserts — pre/post-conditions, invariants, weak vs. strong
Asserts — pre/post-conditions, invariants, weak vs. strong
Beyond "big" unit tests, assert statements add small in-code mini- tests (e.g. assert(var != null) throws automatically if violated) and are used broadly (Microsoft Office has over a million). They check pre-conditions, post-conditions and invariants (the same Design by ContractImplementation block
Design by Contract (pre/post-conditions, invariants)
Design by Contract (Bertrand Meyer; integrated in .NET/Eiffel, implementable in Java/C++ via asserts or frameworks) determines the correctness of runtime behavior and component interfaces via invariants, preconditions and postconditions. It enables runtime analysis (does code behave as expected on each run?), static compile-time analysis (e.g. a variable that should never be null), early automatic error detection, and easier component integration.
Preconditions: define the required state at/before a method call (e.g. valid input parameters): assert(input != null).
Postconditions: define the state right before/after method end (e.g. valid return values, expected object state): assert(result > 27).
Invariants: define stable object state while no code runs — between method calls and right after construction (e.g. 0 <= top <= list.size()).
Conditions can be weak (ε < 0.001) or strong (ε < 0.00001), with weak vs strong consequences (log an error vs throw IllegalArgumentException); they implicitly document how an interface may be called and what the caller can expect.
Practice: analyzing the 100 largest GitHub projects (>40M lines), ~5% of methods contain asserts; methods with asserts have fewer bugfix commits; more experienced developers add asserts more; asserts cluster at frequently-executed important points; project type does not affect assert count. Enable Java asserts via the -ea VM argument.
Go to block concepts as in the Implementation block).
Pre-condition: required state at/before a call; Post-condition: state at method end; Invariant: stable object state between calls and after construction (Stack example: pre top>0 before pop, post top decreased, invariant 0 <= top <= capacity).
Weak vs strong negotiation: as the method implementer you prefer strong pre-conditions (caller must guarantee more, less work for you) and weak post-conditions (you promise less); the caller prefers the opposite (weak pre, strong post).
They need not be asserts (also possible via comments or frameworks). Asserts vs unit tests: asserts are always-on internal checks across many runs and document contracts, but are not standalone test cases; unit tests are external, targeted, repeatable cases — they complement each other.
Test Process, Defect Management, and Automated/Non-Functional Tests
The test process phases, the defect-management process, test automation (unit and GUI), and non-functional tests (performance, usability, security).
Notes for The test process
The test process
Planning & control: runs throughout; defines/monitors tasks and goals within resources, sets the test strategy, framework and infrastructure, prioritizes by requirements/risk, and defines project-specific completion criteria (when is enough tested?).
Analysis & design: check that enough templates exist to generate concrete test cases; derive concrete cases from the strategy and methods using reproducible states/data (defined start state).
Realization & execution: define, prioritize and group cases into scenarios; review/inspect them for completeness and correctness; run manually or automatically; document; decide whether a deviation is a real defect or a faulty test.
Evaluation & report: were the test criteria sufficiently met? If yes, testing of that component/system can end; if no, return to planning for more tests.
Closure: draw conclusions about product and test-process quality (feeding future planning) and archive tests, data, environments and infrastructure for traceability/reproducibility.
Notes for Defect-management process
Defect-management process
Report the defect: hand the defect report to the test/project leader.
Assess the defect: classify it; close duplicates, handle change requestsmaintenance artefact
Maintenance foundations, terms, and artefacts
Motivation: investment protection (software has strategic-economic value, keep its quality/functionality alive long, like renovating buildings) and cost minimization (not maintaining can cost more than maintaining).
Maintenance = all software changes for fault correction, runtime-behaviour improvement, or adaptation to a changed environment (e.g. framework/library updates); triggers: found faults, runtime weaknesses, performance problems (more users), security holes, changed environment.
Details: don't underestimate the effort (big OS/framework updates); relevant across the whole life cycle (ERP systems run 20+ years); maintenance cost often far exceeds initial development; happens alongside further development; preventive restructuring minimizes future effort; large maintenance should be its own project; record its scope in SLAs; specify maintenance artefacts (not just code — network, servers, frameworks, OS); plan maintenance already at initial analysis; high effort if maintainers ≠ developers (DevOps combines both); architecture-affecting maintenance is very costly; automation is often the only viable way.
Software reengineering: re-developing a working component with the same functionality to improve quality (prepare for new functionality or migration); a higher abstraction level than refactoring (which can be a step of it). Refactoring: changing code without affecting functionality, to keep an app maintainable during maintenance (IDE-supported, e.g. splitting oversized methods).
Reverse engineering: recovering code/models from artefacts like compiled binaries when old source is missing; includes re-documentation; aids understanding, usually a prep step before reengineering. Anti-patterns: common bad solutions, recognized early so improvement strategies can fix/avoid them.
Artefacts: change requests (change wishes/fault reports — track controllably, transparently, via issue trackers, involve users); documentation of implemented changes/corrections (keep product docs consistent and traceable: who/what/why/how).
Maintenance vs further development: maintenance does not change the functional state (only brings the technical realization up to date / further-develops existing functionality), whereas further development changes the functional state (add/remove functions); they can run in parallel, possibly by different teams.
Assign the defect: give defects to be fixed to a programmer.
Fix the defect: a responsible programmer fixes it and classifies it by cause.
Deliver the correction: usually several fixes are collected before a new version is shipped to the customer or internal test environment.
Re-test the defect: a tester reproduces it from the description and reports whether it is really fixed.
Close the defect: the test/project leader marks it as fixed.
Notes for Automated and non-functional testing
Automated and non-functional testing
Test automation is the systematic, repeatable, traceable, tool-supported running of predefined tests. About 30–50% of development costs go to testing, so it is a professional, ongoing process integrated into development.
Non-functional tests are specialized for the most important non-functional properties (performance, usability, security); they need more expert and domain knowledge than functional tests and are usually run by a separate, specialized team.
Pros: once created, tests run arbitrarily often with little extra effort; running effort is far lower than manual. Cons: building automation concepts is expensive (preparation ~2× the manual effort) and needs a suitable framework (e.g. JUnitDefinitionA Java unit-testing framework used to write and run automated component tests.addition, not part of the lecture). Applied to regression testsregression testing
Regression testing
In a regression test the previous version of the system defines the expected (presumably correct) behavior, rather than a classic specification. Used when the system changes (component swap, code change) to check whether previously-given functionality still holds — "does my software behave identically before and after changes?". The more complex the system, the greater the danger that a small intended change causes unforeseen side effects.
Especially suited to automation: regression cases need no change as long as only the implementation changes, not the interfaces; cases can even be auto-generated (random data, record returns).
Back-to-back test: using regression as a comparison tool (version n vs n-1). Re-test: re-running all tests after a fix to confirm the error is really gone.
Continuous Integration, Delivery, and Deployment in practice
Continuous Integration (CI)
Commit changes to the master branch as often as possible; each change is validated automatically by building the application and running automatic tests (build + test automated).
Continuous Delivery
Extends CI with an automated release process: build, test and the deploy-to-staging + acceptance tests are automated, but the deploy to production stays a manual (button) step.
Continuous Deployment
Goes one step further than Continuous Delivery: if all stages pass successfully, every update goes to the customer automatically (deploy to production and smoke tests automated too).
Self-managed deployment with Gradle
Gradle suffices for most simple continuous-deployment needs: gradle build (resolve dependencies + build), gradle test (unit tests, plus smoke/acceptance tests via e.g. Gatling), and SSH plugins to transfer built jars / restart servers on a new release.
GitLab CI/CD
GitLab integrates CI & CD: a .gitlab-ci.yml script runs on every commit (compile, test, linters) with stages like build and test; the faculty GitLab has a shared runner so student group repos can use CI/CD directly, and success/failure is documented in GitLab.
Software passes development stages of rising quality (local development → test system → QA system → production). The pipeline is build → test → deploy to staging → acceptance tests → deploy to production → smoke tests; CI/Delivery/Deployment differ only in how far down that pipeline automation reaches.
Automated component tests use unit-test frameworks that define and continuously run tests in the IDE (dynamic result evaluation), provide verification methods, and are often coupled with code-coverage frameworks (instrumentationDefinitionExtra measurement hooks added around or into code so a tool can observe which code parts executed during tests.addition, not part of the lecture) — frequently targeting ~80% coverage.
Automated GUI tests simulate the user through the UI (a black-box approach where both the Point of Control and Point of Observation are the UI; higher complexity). Capture/Replay: record user actions into a script — fast to create but poorly maintainable and only usable once the (near-final) GUI exists. Scripting: write tests from scratch (UI elements by ID) — slower to create but more maintainable. Practice: combine both (e.g. manual rework of recordings).
Performance test: a risk-minimizing test of performance/scalability against SLAsSLAs defined
Operation requirements and SLAs
Requirements: high stability/fault tolerance (minimal downtime); data security (protection from unauthorized access/loss, emergency plans, backups); documentation for all relevant areas; recoverability (restart/recovery any time); easy analysability/adaptability/configurability; frugality (low CPU/memory use); independence (apps don't interfere); trained staff in place before commissioning.
Service Level Agreements (SLAs): fix criteria for non-functional requirements, set by stakeholders before development and met during operation or penalties apply; define per the SMART principle — Specific, Measurable, Achievable, Reasonable, Time-bound. Examples: service availability, response time under load, time to answer a maintenance request.
Go to block (measurable required performance); run regularly to localize which change degraded performance. Key metrics: latency (response time), throughput (data/work per time), transaction rate (a special throughput). Load test: a special case checking correct function under higher load. Performance tests record behavior (monitoring) instead of functional assertions.
Usability testing: improves product usability; representative users perform realistic tasks while usability experts observe and record, then analyse and propose improvements; phases: preparation/planning, execution, evaluation, communication of results (written report, e.g. NIST Common Industry FormatDefinitionA standardized usability-test report format for documenting test setup, participants, tasks, results, and findings.).
Security test: checks security via methods like penetration testing (attacking a running system with various techniques to find weaknesses); identify unspecified functionality as it can be a vulnerability — detailed in the Security chapter.
Organizational QA, Standards, and Formal Verification
Organizational QA measures, quality-management standards, and the formal-verification appendix (SAT/SMT, Z notation).
Notes for Organizational quality assurance
Organizational quality assurance
Organizational QA provides infrastructure to avoid software errors or at least lower the error rate.
Knowledge management: a development of organizational learning, improving the organization at all levels through deliberate handling of the resource knowledge.
Configuration management: manages all objects (specifications, documentation, source) across the whole lifecycle; must control version lines, change states and releases of the 'configuration itemsDefinitionThe managed lifecycle objects whose versions, change states, and releases must stay controlled, such as specifications, documentation, and source code.'.
Templates: secure the format, structure and content of documents (test plans, test-case descriptions, test/review reports).
Checklists: guidelines and hypotheses about suspected weaknesses, phrased as yes/no questions.
Notes for Quality standards and formal verification
Quality standards and formal verification
Quality management is the set of coordinated activities to direct an organization regarding quality; standards raise the quality level and continuity, improve mutual understanding/coordination, and increase customer confidence. A distinction is made between certification and assessment standardsExplanationCertification standards support an external proof that an organization meets defined requirements; assessment standards rate or evaluate process capability/maturity. (certification can be required for some state projects). Testing standards include ISO/IEC/IEEE 29119 (concepts, processes, documentation, techniques, keyword-driven testingExplanationA test style where reusable action keywords describe test steps, separating test intent from the lower-level execution details.); test certifications (ISTQB, ISAB) are increasingly popular.
Source code can be translated into formulas so that SAT (Boolean satisfiability / propositional logic) or SMT (satisfiability modulo theories / predicate logic) can check whether the program shows the expected behavior.
ISO 9001 / ISO 9000-3: international quality-assurance norms; process-centred, oriented to customer needs, certification-focused.
CMM / CMMI (Capability Maturity Model[/Integration]): maturity models rating the quality of an organization's development processes, developed by the SEI for US-DoD supplier comparison; CMMI is the unified, modular successor.
SPICE (ISO/IEC 15504): structured very similarly to CMMI, a joint ISO/IEC initiative and competitor to the US-dominated CMMI.
Approach: translate the first n lines into a formula N, translate the required properties into a formula A, and check whether some variable assignment makes N → A true; if ¬(N → A) is satisfiableDefinitionA formula is satisfiable when at least one assignment of variables makes the formula true; for ¬(N → A), such an assignment is the counterexample., an error exists and the assignment is a clue. Solvers evaluate SAT/SMT formulas automatically.
Advantage over tests: formal verification can give a final verdict on whether a system is error-free, so it is applied to limited critical areas (e.g. parts of rocket-control software). Disadvantage: very costly, so not broadly usable.
The Z notation is a formal ISO-standard language (since 2002) to define systems via declarations (above the line) and conditions (below), e.g. modelling a debit from an account.