Git Basics for Testers

Git is a version control system: it records a project's history as a chain of snapshots called commits, each with a unique id, an author, a date, a message and a link to the commit before it. When a developer fixes the VAT rounding in a checkout and commits the change as 3f9c2ab, that commit becomes a permanent point in the history: anyone can see which lines changed, who changed them and when, and test exactly that version.

For a tester, Git is less about writing code than about knowing exactly what you are testing: which version, which change, and what else the change touched.

Why it matters

"Which build did you test?" is a question Git answers precisely. Without a commit id a failure is hard to reproduce, and nobody can prove that a retest ran on the version with the fix. "Fails on main at 3f9c2ab" can be reproduced by anyone on the team.

Git also shows what changed: the diff since the last tested version is direct input for choosing regression tests. And automated tests, test data and pipeline files live in the same repository as the product code, so maintaining them means committing, branching and merging like a developer.

Repository, commit and the three places a change lives

A repository is a project folder plus its full history, kept in a hidden .git directory. The team's shared copy, the remote, usually named origin, lives on a server; git clone copies it to your machine with the whole history. Later, git fetch downloads new commits and records where the remote's branches now point under names such as origin/main, without touching your files; git pull also brings your current branch up to date, and git push sends your own commits to origin.

A change passes through three places: the working tree (the files you edit), the staging area or index (the changes you select with git add) and the history (git commit records a snapshot of what is staged). Git names each commit by a hash, a fixed-length fingerprint calculated from the commit's content and history: 40 hexadecimal characters (SHA-1 in most repositories), usually shown shortened to the first seven or more, such as 3f9c2ab. Change one character and the id changes, so a commit id names exactly one version. The SHA-1 Generator shows this on any text; Git hashes a commit in its own internal format, so you will not reproduce a commit id with it.

$ git clone https://git.example.com/shop/web.git  # the repository and its history
$ cd web
$ git fetch                     # later: get new commits from origin; files untouched
$ git pull                      # fetch, then bring your current branch up to date
$ git status                    # what is modified, staged or untracked
$ git add tests/checkout/vat.spec.ts
$ git commit -m "Add VAT test for a total ending in .005"
$ git push                      # send your new commits to origin

Try it: SHA-1 Generator

Branches, HEAD and merges

A branch is a movable name that points to one commit; each new commit on the branch moves the name forward. main (in older repositories often master) usually holds the integrated product, and developers create a short-lived branch per change, such as fix/vat-rounding, so unfinished work disturbs nobody. HEAD is what you have checked out, normally a branch.

A merge brings one branch's commits into another. If the target has not moved since the branch started, Git simply moves its name forward (a fast-forward); if both sides have new commits, it creates a merge commit with two parents. If both sides changed the same lines, Git stops with a merge conflict and a person writes the result. Even a clean merge combines changes that were never run together, so a merged build deserves its own test. Branching strategies and pull requests are the next lesson.

In git log --graph, each * is a commit and the lines lead to its parents; the names in brackets show where branches, tags and HEAD point.

Branch fix/vat-rounding starts at release v2.3.0 and gets one commit while main gets two; merging it back creates merge commit 9e41d07 with two parents.
  • main the integrated product
  • fix/vat-rounding one change, kept apart
  1. 1 main internally: Release v2.3.0 tagged at 1d2e3f4 The version the tester last tested
  2. 2 From main To fix/vat-rounding: Branch created at the same commit git switch -c fix/vat-rounding
  3. 3 main internally: Commit 5b0a9f4: minimum order 50.00 SAR Colleagues keep committing to main
  4. 4 main internally: Commit c7d2e18: Arabic invoice labels
  5. 5 fix/vat-rounding internally: Commit 3f9c2ab: VAT rounding fix So far only this branch has it
  6. 6 From fix/vat-rounding To main: Merged: merge commit 9e41d07 Two parents: c7d2e18 and 3f9c2ab git merge fix/vat-rounding
  7. 7 main internally: The tester lists what is new Four commits since v2.3.0: the regression scope git log --oneline v2.3.0..main

Time runs downwards. Each lane is a branch name that moves forward with every new commit.

$ git switch -c fix/vat-rounding   # new branch at the current commit, switch to it
$ git commit -am "Fix VAT rounding for totals ending in .005"  # -a stages edited files
$ git switch main
$ git pull                         # fast-forward: two new commits from colleagues
$ git merge fix/vat-rounding       # both sides moved on: Git makes a merge commit
$ git log --oneline --graph -5
*   9e41d07 (HEAD -> main) Merge branch 'fix/vat-rounding' into main
|\
| * 3f9c2ab (fix/vat-rounding) Fix VAT rounding for totals ending in .005
* | c7d2e18 (origin/main) Add Arabic labels to the invoice PDF
* | 5b0a9f4 Update minimum order total to 50.00 SAR
|/
* 1d2e3f4 (tag: v2.3.0) Release 2.3.0

Reading history with log, show and diff

git log lists commits, newest first; git show displays one commit and its changes; git diff compares any two versions. A tag is a fixed name for one commit, typically a release such as v2.3.0.

In the diff below, --- and +++ name the old and the new file, lines starting with - were removed, lines starting with + were added, and @@ -9,5 +9,5 @@ means the excerpt starts at line 9 and spans five lines in both versions; the text after the second @@ is only an earlier line of the file, shown for orientation.

  • git log --oneline v2.3.0..main: everything in main that is not in release v2.3.0.
  • git log --oneline -- src/checkout/: only the commits that touched the checkout folder.
  • git diff --stat v2.3.0 main: the changed files and line counts between two versions.
  • git tag --contains 3f9c2ab: the releases that already include a fix.
$ git diff v2.3.0 main -- src/checkout/vat.ts
diff --git a/src/checkout/vat.ts b/src/checkout/vat.ts
index 4b1e2c0..9d7a3f1 100644
--- a/src/checkout/vat.ts
+++ b/src/checkout/vat.ts
@@ -9,5 +9,5 @@ import { roundHalfUp } from './money';
 // VAT rate in this example: 15 %
 export function vatFor(net: number): number {
   const rate = 0.15;
-  return Math.round(net * rate * 100) / 100;
+  return roundHalfUp(net * rate, 2);
 }

Try it: Text Diff

Worked example: what changed since the last release

You tested release v2.3.0 last week; today you are asked for a regression pass on the latest main. Before opening the application, you ask Git what changed; the commands and their output are at the end of this section. Read them file by file:

  • vat.ts: one line of VAT rounding changed. Test amounts whose VAT ends in half a halala, such as a net price of 1.10 SAR (VAT 0.165). git grep shows that totals and refunds call vatFor too, so refunds join the scope.
  • min-order.ts: the minimum order is now 50.00 SAR. Test 49.99 and 50.00, as in boundary value analysis; if no ticket or release note mentions the change, ask before testing.
  • labels.ar.json: new Arabic invoice labels. Check the Arabic PDF invoice and its right-to-left layout.
  • vat.spec.ts: new unit tests. Values they cover need less manual effort; values they miss go on your list.
  • Write "Regression on main at 9e41d07, changes since v2.3.0" in the test report and in every bug report.
$ git fetch origin
$ git log --oneline v2.3.0..origin/main
9e41d07 (origin/main) Merge branch 'fix/vat-rounding' into main
3f9c2ab Fix VAT rounding for totals ending in .005
c7d2e18 Add Arabic labels to the invoice PDF
5b0a9f4 Update minimum order total to 50.00 SAR
$ git diff --stat v2.3.0 origin/main
 src/checkout/min-order.ts  |  4 ++--
 src/checkout/vat.ts        |  2 +-
 src/invoice/labels.ar.json | 18 ++++++++++++++++++
 tests/checkout/vat.spec.ts | 21 +++++++++++++++++++++
 4 files changed, 42 insertions(+), 3 deletions(-)
$ git grep -n "vatFor(" origin/main -- src
origin/main:src/checkout/total.ts:27:  const vat = vatFor(net);
origin/main:src/checkout/vat.ts:10:export function vatFor(net: number): number {
origin/main:src/refunds/refund.ts:41:  return amount + vatFor(amount);

Try it: Regression Scenario Generator

Common mistakes

  • Reporting against "the latest build" instead of a commit id or tag: nobody can reproduce the failure or prove that the retest ran on the fix.
  • Testing a stale copy: git status knows about origin only as of your last git fetch.
  • Trusting commit messages without reading the diff: a "refactor" can still change a business rule.
  • Committing secrets, .env files, test reports or screenshots. Use .gitignore; a secret deleted later stays in the history, so report it at once so it can be revoked.
  • Making large commits with messages such as "fix": the history no longer tells anyone what changed or why, and one change cannot be found or undone on its own.

The QA perspective

Testers use a small part of Git every day: they record the exact version under test, read the log and diff since the last tested version to choose regression tests, check out a commit to reproduce a failure, and keep automated tests in the repository, where CI starts on Git events such as a push or a merge. The ISTQB Foundation syllabus calls this configuration management: test items and testware are uniquely identified and version controlled.

Ask for the commit or pull request that fixes each defect and link it in the defect record; before confirmation testing, check that the fix is in your build. You do not need every line of a diff: file names, function names and the size of a change are enough to ask "what else uses this?", the question that finds regressions.

Practice exercise

Read a release history and plan the retest

Your task

You last tested release v1.8.0 of a shop application. Using the output below, find the new commits, the branch and the merge commit, list the areas to test, and decide how you will identify the tested version.

Material
$ git log --oneline --graph v1.8.0..main
*   a41f9c0 (HEAD -> main) Merge branch 'feature/saved-cards' into main
|\
| * 7be2d15 Mask card number except the last 4 digits
| * 0c93e6a Add saved cards page
* | e5d8b72 Fix account lockout after 5 failed logins
|/
* 52aa1f3 Change password reset link expiry to 30 minutes
Steps
  1. Read the graph from the bottom up: where did the branch start, and where was it merged?
  2. Group the new commits by product area.
  3. Write two or three test ideas per area, using techniques from earlier lessons.
  4. Write the version line for your report and one question for the team.
Show the model answer
Model answer

Five commits are new. feature/saved-cards started at 52aa1f3 and holds 0c93e6a and 7be2d15; meanwhile e5d8b72 was committed on main.

a41f9c0 is the merge commit: its first parent is e5d8b72, the previous tip of main, and its second parent is 7be2d15, the tip of the branch. The two lines may never have been tested together, so the merged build needs its own pass.

Authentication: reset links just before and just after 30 minutes, and a link used twice; 4, 5 and 6 failed logins. Payments: adding and listing saved cards, with only the last 4 digits visible on the page, in API responses, emails and logs.

Version line: "Regression on main at a41f9c0, changes since v1.8.0". Question: does the 30-minute expiry come from a ticket, and does it apply to links sent before the release?

Frequently asked questions

Do I need Git if I only test manually?

You need the reading part: commit ids, tags, log and diff, so that you know which version you tested and what changed in it. Hosting services show the same history in the browser, so you can start there. Writing commits and resolving conflicts become necessary once you maintain automated tests.

What is the difference between Git and GitHub?

Git is the version control tool; it runs on your machine, and most of its commands work without a network. GitHub, GitLab and Bitbucket are services that host remote repositories and add pull requests, issue tracking and CI pipelines on top of Git.

What is the difference between git fetch and git pull?

git fetch downloads new commits and updates remote-tracking branches such as origin/main, but leaves your files and your branch alone. git pull runs a fetch and then updates your current branch: with no new commits of your own it fast-forwards; if both sides have new commits, it must either merge them or rebase (replay your commits on top of the new ones), and recent Git versions stop and ask you to choose with --no-rebase, --rebase or the pull.rebase setting.

Is it safe to check out an old commit to reproduce a bug?

Yes. git checkout 3f9c2ab (or git switch --detach 3f9c2ab) puts that version in your working tree in a "detached HEAD" state: you can build and test it, and git switch main takes you back. Commit your own changes first, or set them aside with git stash, because Git refuses to switch when they would be overwritten.

References

Practise with Mutqan tools

Compare two texts line by line or word by word and highlight differences.

Formatting

Generate SHA-1 hashes of text using the browser WebCrypto API.

Security

Go deeper

Your progress on this topic

Part of the QA Roadmap