Programming Fundamentals

Chapter 12
Testing, Code Quality, and Version Control

Certificate in Java-Python-Programming Languages
Programming Fundamentals   •   Certificate

Testing, Code Quality, and Version Control

Introduction

A program is not complete when it merely runs once. Professional software must produce correct results, remain understandable to other developers, tolerate expected errors, and be changed without unnecessary risk. Testing, code quality, and version control provide the practical foundation for achieving these goals.

Testing supplies evidence that software behaves as intended. Code-quality practices reduce defects and make programs easier to read, review, and maintain. Version control records how software changes over time and enables developers to collaborate safely. These activities are closely connected: well-structured code is easier to test, tests make refactoring safer, and version control preserves both the code and the reasoning behind changes.

This chapter applies these ideas to Java and Python programming at certificate level. It emphasizes practical techniques rather than advanced testing theory, while introducing the professional vocabulary and workflows used in software teams.

Learning Objectives

After studying this chapter, you should be able to:

  • design useful test cases and distinguish expected from unexpected behavior;
  • explain unit testing, integration testing, regression testing, and acceptance testing;
  • apply equivalence partitioning and boundary-value analysis;
  • conduct constructive code reviews;
  • apply naming conventions, documentation practices, and basic maintainability principles;
  • refactor code without changing its observable behavior;
  • use essential Git workflows for local and collaborative development;
  • recognize limitations, ethical concerns, and professional responsibilities in software quality assurance.

1. What Software Quality Means

Software quality is multidimensional. Correctness is important, but it is not the only quality attribute. A useful program should also be understandable, maintainable, reasonably efficient, secure, and appropriate for its users.

Quality attributePractical meaning
CorrectnessThe program produces the required results.
ReliabilityThe program behaves consistently under expected conditions.
MaintainabilityDevelopers can understand and modify it efficiently.
UsabilityUsers can learn and operate it effectively.
EfficiencyIt uses acceptable time, memory, and other resources.
SecurityIt protects data and resists misuse.
PortabilityIt can operate in intended environments.

These attributes may conflict. For example, extensive input validation may improve security but increase code complexity. A professional developer therefore makes quality decisions in relation to requirements, risks, users, and available resources.

A defect is a flaw in a work product, such as an incorrect condition or missing validation. An error is a human action or misunderstanding that introduces a defect. A failure occurs when executing software produces behavior that violates a requirement. Testing primarily observes failures; investigation then seeks the underlying defects and errors.

Testing does not prove that a program contains no defects. It provides evidence about selected behavior. The quality of that evidence depends on the choice of inputs, expected results, environment, and test design.

2. Test Cases and Test Design

A test case specifies an input, the conditions under which the program is executed, and the expected result. A practical test case normally includes:

  1. an identifier;
  2. a concise purpose;
  3. preconditions;
  4. input data;
  5. execution steps;
  6. expected result;
  7. actual result;
  8. pass or fail status.

For example, suppose a function calculates a shipping charge. The requirement states that orders weighing from 1 to 5 kilograms cost $10, while weights outside the accepted range must be rejected. A test case might be:

FieldValue
PurposeAccept the smallest valid weight
Input1 kg
Expected resultCharge is $10
StatusRecorded after execution

A good test case is specific and independently verifiable. “The program works” is not a test case because it does not identify an input or an expected result.

Positive, negative, and exceptional tests

A positive test uses valid input and checks expected successful behavior. A negative test uses invalid or inappropriate input and checks that the program responds safely. An exceptional test examines events such as a missing file, a network interruption, division by zero, or insufficient permissions.

For a Python function that accepts an integer age, useful tests might include:

def classify_age(age):
    if not isinstance(age, int):
        raise TypeError("age must be an integer")
    if age < 0:
        raise ValueError("age cannot be negative")
    return "minor" if age < 18 else "adult"

Potential tests include 17, 18, 0, −1, and the string "18". The last two tests are valuable because they examine validation rather than only normal operation.

3. Levels and Types of Testing

Testing can be organized by the size of the software component being examined.

Unit testing

A unit test examines a small component, usually a function, method, or class in isolation. Unit tests should be fast, repeatable, and focused. In Python, the unittest framework is built into the standard library, while pytest is a widely used external framework. Java commonly uses JUnit.

Python example:

import unittest

class TestClassifyAge(unittest.TestCase):
    def test_minor(self):
        self.assertEqual(classify_age(17), "minor")

    def test_adult_at_boundary(self):
        self.assertEqual(classify_age(18), "adult")

    def test_negative_age_rejected(self):
        with self.assertRaises(ValueError):
            classify_age(-1)

The test method names describe behavior. A failing test should provide useful information about what requirement was violated.

Integration testing

Integration testing examines interactions among components. Examples include a service communicating with a database, a Java class using a file repository, or a Python program calling an external API. A function may pass unit tests while integration fails because of incompatible data formats, incorrect configuration, or transaction problems.

System testing

System testing examines the complete application in an environment resembling actual use. It may involve user interfaces, databases, authentication, files, and external services.

Acceptance testing

Acceptance testing determines whether the software satisfies user or client requirements. It may be performed by customers, product owners, testers, or representatives of the business. Acceptance tests are often expressed in user-oriented language, such as: “When a registered user enters valid credentials, the dashboard is displayed.”

Regression testing

Regression testing repeats tests after a change to detect unintended effects. An automated test suite is particularly valuable for regression testing. A test that once passed and later fails may indicate a regression, although changes in requirements or test data must also be considered.

4. Boundary-Value Analysis and Equivalence Partitioning

Testing every possible input is usually impossible. Test design techniques reduce the number of tests while targeting high-risk areas.

Equivalence partitioning

Equivalence partitioning divides inputs into groups expected to behave similarly. One representative from each group can often provide useful coverage.

If an examination score must be an integer from 0 through 100, possible partitions are:

  • less than 0: invalid;
  • 0 through 100: valid;
  • greater than 100: invalid;
  • non-integer values: potentially invalid, depending on the specification.

A test set could include −1, 50, 101, and 75.5 if decimals are not allowed.

Boundary-value analysis

Defects frequently occur at transitions between valid and invalid regions. For an inclusive range from 0 through 100, important boundary values are −1, 0, 1, 99, 100, and 101. The values immediately outside and inside each boundary are especially informative.

The terms inclusive and exclusive must be read carefully. A requirement of “age greater than 17” differs from “age at least 17.” Translating requirements into explicit conditions reduces ambiguity.

Worked example

Suppose a function accepts a password length from 8 through 20 characters, inclusive. Boundary analysis suggests testing:

Input lengthReasonExpected result
7Just below lower boundaryReject
8Lower boundaryAccept
9Just above lower boundaryAccept
19Just below upper boundaryAccept
20Upper boundaryAccept
21Just above upper boundaryReject

These tests do not guarantee correctness, but they efficiently target common off-by-one errors such as using length > 8 instead of length >= 8.

5. Test Automation and Test Doubles

Automated tests can be executed repeatedly and included in a continuous integration process. A strong automated test generally has three stages:

  • Arrange: prepare data and conditions;
  • Act: execute the behavior under test;
  • Assert: compare the result with the expected outcome.

For example:

def test_total_with_tax():
    # Arrange
    items = [10.00, 5.00]
    # Act
    result = calculate_total(items, tax_rate=0.10)
    # Assert
    assert result == 16.50

The example assumes that currency rounding rules have been defined. Financial software should generally use decimal arithmetic and explicit rounding rather than relying blindly on binary floating-point values.

A test double replaces a real dependency. A stub supplies predetermined responses, a mock verifies interactions, and a fake provides a simpler working implementation. Test doubles are useful when a real database or network service would make tests slow, costly, unreliable, or unsafe. They should not hide important integration defects; integration tests are still required for real interfaces.

A useful test suite should be deterministic. Tests should not depend on execution order, a developer’s local clock, random uncontrolled data, or an unreliable external service. If randomness is necessary, record the seed so a failure can be reproduced.

6. Code Quality and Readability

Code quality is supported by conventions that reduce unnecessary mental effort. Consistency is often more valuable than personal preference.

Naming conventions

Names should communicate purpose and follow the conventions of the language.

  • Python variables and functions commonly use snake_case; classes use PascalCase.
  • Java variables and methods commonly use camelCase; classes use PascalCase; constants often use UPPER_SNAKE_CASE.
  • Boolean names should suggest a true-or-false question, such as is_valid, has_permission, or isEmpty.
  • Avoid names such as x, data, and temp unless their scope and meaning are genuinely obvious.

A name should be accurate, not merely short. calculate_discounted_total is more informative than calc when the function performs a meaningful business operation.

Structure and simplicity

Functions should normally have one clear responsibility. Deep nesting, long parameter lists, duplicated logic, and unexplained global state increase maintenance difficulty. A conditional should express the business rule directly where possible.

Poor code:

if user != None and user.active == True:
    do_work()

Clearer Python:

if user is not None and user.active:
    do_work()

The improvement is not merely stylistic: it uses idiomatic language constructs and makes the condition easier to scan.

Documentation and comments

Documentation should explain purpose, assumptions, inputs, outputs, exceptions, and important side effects. Comments should explain why a non-obvious decision exists, not repeat what the code visibly does.

A docstring might be written as follows:

def withdraw(balance, amount):
    """Return the new balance after a valid withdrawal.

    Raises ValueError if amount is non-positive or exceeds balance.
    """

Documentation must be maintained with the code. Incorrect documentation can be more harmful than missing documentation because it creates false confidence.

7. Code Reviews

A code review is a systematic examination of a change by one or more developers before it is integrated. Reviews identify defects, clarify design, spread knowledge, and improve consistency. They are not a substitute for testing.

An effective review considers:

  • Does the change satisfy the requirement?
  • Are normal, boundary, and invalid inputs handled?
  • Are tests present and meaningful?
  • Are names and structure understandable?
  • Could the change introduce security, privacy, performance, or compatibility problems?
  • Is error handling appropriate?
  • Is documentation updated?

Review comments should be specific and respectful. “This is bad” is not useful. “Could this condition handle an empty list? The requirement permits zero items” identifies both the concern and the reason.

The author should keep commits focused so reviewers can understand the change. Reviewers should distinguish blocking defects from optional suggestions. Teams may use pull requests, protected branches, approval rules, and automated checks to formalize the process.

8. Refactoring and Maintainability

Refactoring is the disciplined restructuring of existing code without changing its externally observable behavior. Examples include extracting a function, renaming a misleading variable, removing duplication, simplifying a conditional, or replacing a magic number with a named constant.

Refactoring should be incremental:

  1. establish a reliable test baseline;
  2. make one small structural change;
  3. run the tests;
  4. inspect the change;
  5. repeat.

Without tests, refactoring may accidentally alter behavior. However, tests themselves can be incomplete, so developers should also inspect requirements and perform appropriate manual or integration checks.

Maintainability can be improved through cohesion, low coupling, clear interfaces, limited duplication, and appropriately sized modules. A useful design principle is to separate policy from mechanism. For example, a function deciding whether a customer qualifies for free delivery should not also contain low-level file-writing logic.

Technical debt describes the future cost created by expedient design or deferred work. Technical debt is not automatically irresponsible; a deliberate short-term compromise may be reasonable. It becomes dangerous when it is undocumented, accumulates interest through repeated workarounds, or prevents safe change.

9. Git and Version Control

Version control records changes to files and enables recovery, comparison, branching, and collaboration. Git is a distributed version-control system: each clone contains repository history rather than depending entirely on a central server.

Important terms include:

  • repository: the project and its recorded history;
  • working tree: the files currently checked out;
  • commit: a permanent local record of selected changes;
  • branch: a movable line of development;
  • remote: another repository location, often hosted online;
  • merge: combining histories;
  • pull request: a proposed change submitted for review.

A basic workflow is:

edit → inspect → test → stage → commit → push → review → merge

Typical commands are:

git status
git diff
git add src/ tests/
git commit -m "Validate password length boundaries"
git push origin feature/password-validation
git pull --rebase origin main

git status and git diff should be used before staging. Developers should confirm that generated files, passwords, API keys, and personal data are not being committed. A .gitignore file can exclude build outputs, virtual environments, caches, and local configuration files, but it cannot remove secrets already committed to history.

Commits and branches

A good commit is focused, builds successfully where practical, and describes one logical change. Messages such as “Fix validation” are less informative than “Reject passwords shorter than eight characters.” Small commits are easier to review and revert.

A feature-branch workflow commonly uses a stable main branch and short-lived branches for individual changes:

git switch main
git pull origin main
git switch -c feature/age-validation
# edit and test files
git add .
git commit -m "Add age validation tests"
git push -u origin feature/age-validation

The branch is then reviewed through a pull request. After approval and successful automated checks, it is merged. Teams may use merge commits, rebasing, or squash merges; the choice should be documented and applied consistently.

Merge conflicts

A conflict occurs when Git cannot automatically reconcile changes. The developer must inspect the marked sections, choose or combine the correct content, remove conflict markers, test the result, and then stage and commit the resolution. Conflict resolution is a reasoning task, not simply a mechanical choice between “ours” and “theirs.”

10. A Practical Quality Workflow

Quality should be built into the development process rather than added only at the end. A small feature workflow may be:

  1. clarify the requirement and acceptance criteria;
  2. identify normal, invalid, and boundary inputs;
  3. write or update unit tests;
  4. implement the simplest understandable solution;
  5. run automated tests and inspect failures;
  6. refactor carefully if duplication or complexity is evident;
  7. run formatting, linting, and security checks where available;
  8. review the diff and documentation;
  9. commit a focused change;
  10. open a pull request and respond to review;
  11. run integration or acceptance checks before release.

Continuous integration systems automatically build projects and run tests when changes are pushed. They improve feedback speed but do not replace thoughtful test selection, code review, or user acceptance. A green build means that configured checks passed; it does not mean that all possible behavior is correct.

11. Illustrative Case: Improving a Course-Registration Module

Background

A college develops a registration service. Students may register for a course only when they are enrolled, the course has available capacity, and they have not already registered.

Problem

The first implementation performs all checks in one long method. It has few tests, uses ambiguous names, and occasionally allows registration when capacity is zero. Developers are reluctant to change it because failures are difficult to diagnose.

Analysis

The team translates the requirements into testable rules:

  • an enrolled student may register when capacity is greater than zero;
  • a non-enrolled student is rejected;
  • a duplicate registration is rejected;
  • capacity zero is rejected;
  • successful registration reduces available capacity by one.

Boundary analysis identifies capacity values 0 and 1 as especially important. Equivalence partitions include enrolled and non-enrolled students, duplicate and new registrations, and positive versus non-positive capacity.

Approach and solution

The team first adds unit tests for each rule and an integration test for the registration repository. It extracts separate functions for eligibility and capacity checks, replaces names such as s and c with student and course, and documents the exception behavior. A feature branch is used, and each logical improvement is committed separately. A pull request requires passing tests and one reviewer’s approval.

A concurrency concern is also identified: two requests might observe the same available seat. The final integration design performs the capacity check and decrement as one protected database operation. This issue would not be detected by isolated unit tests alone.

Outcome

The boundary defect is fixed, the test suite documents the requirements, and future changes produce faster feedback. The team also records that unit tests cannot establish correctness of database transactions or concurrent requests; those concerns require integration and system-level testing.

Learning points

The case demonstrates that quality is a process, not a final inspection. Test design exposes ambiguous requirements, refactoring improves structure, code review spreads responsibility, and version control makes changes traceable and reversible.

12. Limitations, Ethics, and Professional Responsibility

Testing resources are limited. Teams must prioritize high-risk behavior, critical data, security-sensitive operations, and frequently changed components. Code coverage measures which code was executed, not whether the executed behavior was tested correctly. High coverage can coexist with weak assertions.

Developers must protect confidential test data. Real customer information should not be copied into development environments without authorization and appropriate safeguards. Tests should not expose passwords, personal identifiers, or security tokens in logs and repositories.

Automation and AI-assisted coding tools can suggest tests, explain errors, or generate boilerplate. Their output must be reviewed because generated code may contain defects, insecure practices, licensing concerns, or invented assumptions. Developers remain responsible for verifying behavior, respecting organizational policies, and documenting important decisions.

Quality practices should also support accessibility and fairness. A test suite that checks only the developer’s environment may overlook users with different devices, languages, abilities, or network conditions. Requirements and acceptance criteria should identify these concerns when relevant.

Chapter Summary

Testing provides evidence that software meets defined expectations. Test cases should include clear inputs, conditions, expected results, and outcomes. Unit, integration, system, acceptance, and regression testing examine software at different levels. Equivalence partitioning reduces test inputs by grouping similar behavior, while boundary-value analysis targets transitions where defects commonly occur.

Code quality depends on meaningful names, simple structure, focused functions, accurate documentation, and appropriate error handling. Code reviews provide a second perspective and should be specific, respectful, and evidence-based. Refactoring improves internal structure without changing observable behavior and should be protected by tests.

Git records development history and supports branching, focused commits, review, merging, and recovery. A professional workflow combines requirements analysis, testing, implementation, review, automation, and controlled integration. No single metric or tool proves quality; responsible developers combine technical evidence with judgment, security awareness, and concern for users.

Key Terms

Defect: A flaw in a software work product.

Failure: Observable software behavior that violates a requirement.

Test case: Defined inputs, conditions, actions, and expected results used to examine behavior.

Unit testing: Testing a small software component in isolation.

Integration testing: Testing interactions among components or external systems.

System testing: Testing the complete application in a realistic environment.

Acceptance testing: Testing whether software satisfies user or business requirements.

Regression testing: Repeating tests after changes to detect unintended effects.

Equivalence partitioning: Dividing inputs into groups expected to behave similarly.

Boundary-value analysis: Testing values at and near the edges of valid regions.

Test double: A substitute for a real dependency during testing.

Code review: Systematic examination of a proposed code change.

Refactoring: Restructuring code without changing its observable behavior.

Maintainability: The ease with which software can be understood, corrected, or changed.

Technical debt: Future cost created by expedient or deferred technical decisions.

Repository: A project’s files together with recorded version history.

Commit: A recorded set of changes in a version-control repository.

Branch: An independent line of development in version control.

Merge conflict: A situation in which changes cannot be combined automatically.

Continuous integration: Automated building and checking of changes as they are integrated.

Review Questions

Short Answer Questions

  1. What information should a well-defined test case contain?
  2. Distinguish unit testing from integration testing.
  3. What is the purpose of boundary-value analysis?
  4. Why should code comments explain “why” rather than merely repeat “what”?
  5. What is the difference between a commit and a branch in Git?

Long Answer Questions

  1. Explain how positive, negative, exceptional, and regression tests contribute to software quality. Give an example of each.
  2. Describe equivalence partitioning and boundary-value analysis using a password-length requirement of 8 through 20 characters.
  3. Explain the role of code reviews and identify characteristics of constructive review feedback.
  4. Describe a safe refactoring process and explain why automated tests are valuable during refactoring.
  5. Explain a feature-branch Git workflow from creating a branch through review and merge.

Analytical / Application Questions

  1. A function accepts an integer from 1 through 12 representing a month. Design a compact test set using equivalence partitions and boundary values. State the expected result for each test.
  2. A registration program passes all unit tests but occasionally assigns the same final seat to two students. Identify the testing level most directly needed, explain the likely risk, and propose a quality improvement.
  3. Review a proposed change that adds a database password to a configuration file and commits it to Git. Analyze the security and governance problems, and recommend corrective actions for both the code and repository history.

Multiple Choice Questions with Answers

Question 1

Which testing level primarily examines a single function or method in isolation?

A. Unit testing

B. System testing

C. Acceptance testing

D. Deployment testing

Correct Answer: A. Unit testing

Question 2

For an inclusive valid range of 0 through 100, which value is just outside the upper boundary?

A. 0

B. 1

C. 100

D. 101

Correct Answer: D. 101

Question 3

What is the main purpose of equivalence partitioning?

A. To eliminate the need for requirements

B. To group inputs expected to behave similarly

C. To measure execution speed

D. To merge Git branches

Correct Answer: B. To group inputs expected to behave similarly

Question 4

Which activity is an example of refactoring?

A. Changing a function’s required output

B. Deleting all automated tests

C. Extracting duplicated logic into a function without changing behavior

D. Adding an unrelated feature during a bug fix

Correct Answer: C. Extracting duplicated logic into a function without changing behavior

Question 5

Which Git command shows the current branch and unstaged or staged changes?

A. git status

B. git merge

C. git clone

D. git tag

Correct Answer: A. git status

Question 6

A test that verifies rejection of a negative age is best classified as which kind of test?

A. Positive test

B. Negative test

C. Performance benchmark

D. Deployment test

Correct Answer: B. Negative test

Question 7

What is a primary benefit of a focused commit?

A. It makes every change irreversible

B. It prevents all merge conflicts

C. It makes review and reverting easier

D. It removes the need for testing

Correct Answer: C. It makes review and reverting easier

Question 8

Which statement about code coverage is correct?

A. High coverage proves that the program has no defects

B. Coverage indicates executed code but not necessarily adequate assertions

C. Coverage replaces acceptance testing

D. Coverage measures only Git branch count

Correct Answer: B. Coverage indicates executed code but not necessarily adequate assertions

Question 9

Which item should generally not be committed to a shared repository?

A. A documented test

B. A source-code change

C. A focused commit message

D. An unprotected database password

Correct Answer: D. An unprotected database password

Question 10

What is the principal role of a test double?

A. To replace a dependency during a test

B. To permanently replace production code

C. To compress repository history

D. To generate user requirements

Correct Answer: A. To replace a dependency during a test

Programming Fundamentals
Chapter 12 of 14