Build a Python CI Pipeline
Automate pytest checks with GitHub Actions and fix a failing build.
Introduction
30 Second Summary
Code that worked yesterday can break after one tiny change. Without an automatic check, the problem can slip into every copy of the project.
In this project, you will build a small Python calculator protected by three automated tests. A GitHub Actions workflow will check it on a hosted Linux runner after every push.
What You'll Build
You'll push a change to GitHub to watch a red pipeline reveal the defect that your next green run proves you fixed.
By the end of this project, you'll have:
- A local pytest suite that protects multiplication plus even-number behavior with three passing checks.
- An automatic CI run that starts after every push to test the project on Linux.
- A red-to-green repair guided by the failed multiply assertion in the workflow log.
- Secret Mission: Expand the workflow into a three-version Python test matrix that runs the same tests on Python 3.13, 3.14, plus 3.15.
Are there any prerequisites?
You need a computer running Microsoft Windows with Visual Studio Code plus Git for Windows installed. You also need an existing GitHub account.
Basic Python familiarity is enough because Step 1 verifies the installation.
Before We Start
This step marks your commitment to building an automated safety net for a Python calculator. Your pipeline will run pytest tests after every GitHub push so broken behavior gets immediate feedback.
Set Up Your Windows Python Workspace
Your future CI pipeline needs a predictable local foundation. A known Python version makes local results easier to compare with remote results.
A project-local virtual environment keeps dependencies isolated from other projects. In this step, you will prepare that environment for the pytest test runner.
In this step, get ready to:
- Confirm Python 3.15.2 is available on Windows.
- Configure the global Git identity used for future commits.
- Create an isolated workspace with pytest 9.2.0 installed.
Verify Python and your Git identity
The local project needs the same Python release selected for the future workflow. Start by checking which version the Windows Python launcher can find.
- Press the Windows key to open Windows search.
- Type Command Prompt into the search field.
- Press Enter to open Command Prompt.
- Check the installed Python version by running this command:
py --version
What does this command do?
The py launcher finds Python installations on Windows. The version flag prints the release selected by the launcher.
✔️ I see version 3.15.2 or higher
Great, Python 3.15.2 is ready to support your local test environment.
ⓧ I see an older version
The project uses Python 3.15.2 so that your local environment matches the workflow version.
- Visit the official Windows downloads page.
- Download the Windows installer that matches your computer for Python 3.15.2.
- Run the downloaded installer.
- Follow the installer prompts to complete the installation.
- Close the current Command Prompt window.
- Reopen Command Prompt through Windows search.
- Check the newly installed version by running this command:
py --version
What should I see?
The output should now report Python 3.15.2 or a higher compatible release.
ⓧ Command not found
Windows cannot find the Python launcher yet. Installing Python 3.15.2 supplies the launcher used throughout this project.
- Visit the official Windows downloads page.
- Download the Windows installer that matches your computer for Python 3.15.2.
- Run the downloaded installer.
- Follow the installer prompts to complete the installation.
- Close the current Command Prompt window.
- Reopen Command Prompt through Windows search.
- Verify the Python launcher by running this command:
py --version
What should I see?
The output should report Python 3.15.2 or a higher compatible release.
Git stores an author name with every commit. It also stores an author email that can connect those commits to your GitHub account.
- Check the global Git name and email by running these commands:
git config --global user.name
git config --global user.email
What do these commands check?
- The user.name value identifies the author shown on a commit.
- The user.email value links that author identity to an email address.
Each command should print one configured value. A blank line means that value still needs to be set.
- Prepare a missing identity value by copying the relevant command below:
git config --global user.name "Mona Lisa"
git config --global user.email "YOUR_EMAIL"
Why configure a global identity?
The global settings apply your identity to repositories created with this Windows account. Your future commits can then carry a consistent author.
- Replace Mona Lisa with your own name if the name was blank.
- Replace YOUR_EMAIL with the email associated with your GitHub account if the email was blank.
- Run each edited command that corresponds to a blank value.
- Verify both identity values by running the checks again:
git config --global user.name
git config --global user.email
What should I see?
The first line should show your name. The second line should show the email associated with your GitHub account.
Still seeing a blank Git value?
Check that the setting command includes --global. Keep quotation marks around a name that contains spaces.
Use this guided prompt if the value still does not appear: Help me configure my global Git user name and email on Windows.
Create the isolated workspace
A named project folder keeps your code files beside their dependency configuration. Opening that folder in Visual Studio Code also makes its integrated terminal start in the correct location.
- Create the devops-ci-starter folder on your Desktop by running these commands in Command Prompt:
cd /d %USERPROFILE%\Desktop
mkdir devops-ci-starter
cd devops-ci-starter
code .
What do these commands do?
- The first command moves Command Prompt to your Desktop.
- The next command creates the devops-ci-starter folder.
- The third command enters the new folder.
- The final command opens the current folder in Visual Studio Code.
- Select Yes, I trust the authors if the Workspace Trust dialog appears.
- Confirm the Explorer sidebar shows devops-ci-starter as the open folder.
- Click View in the top menu.
- Select Command Palette.
- Enter Terminal: Select Default Profile into the command field.
- Select Command Prompt from the profile list.
- Click Terminal in the top menu.
- Select New Terminal.
The terminal opens inside devops-ci-starter. Its tab identifies the active shell as Command Prompt.
Did the code command fail?
Use Visual Studio Code's File menu to select Open Folder. Choose the devops-ci-starter folder on your Desktop.
Use this guided prompt if Visual Studio Code still cannot open the folder: Help me open my Desktop project folder in Visual Studio Code on Windows.
The dependency file pins the test runner version used locally. This makes every installation repeatable.
- Select the file icon with a plus sign at the top of the Explorer sidebar.
- Enter requirements.txt as the file name.
- Press Enter to create the file.
- Add the pinned pytest dependency by pasting this content:
pytest==9.2.0
What does this file do?
The double equals sign pins pytest to version 9.2.0. Future installs use that exact release.
- Click File in the top menu.
- Select Save.
- Confirm requirements.txt remains listed under devops-ci-starter in the Explorer sidebar.
Dependency file missing?
Confirm the file is named requirements.txt with no extra file extension. Make sure it sits directly inside devops-ci-starter.
Use this guided prompt if the file is still missing: Help me create requirements.txt in my Visual Studio Code project folder.
Some generated folders belong only to your computer. A .gitignore file prevents those folders from entering version control later.
- Select the file icon with a plus sign at the top of the Explorer sidebar.
- Enter .gitignore as the file name.
- Press Enter to create the file.
- Add the generated folders that Git should ignore by pasting this content:
.venv/
__pycache__/
.pytest_cache/
What does this file protect?
- The .venv/ rule keeps the local dependency environment out of Git.
- The __pycache__/ rule excludes Python's generated bytecode cache.
- The .pytest_cache/ rule excludes pytest's generated cache.
- Click File in the top menu.
- Select Save.
- Confirm .gitignore appears beside requirements.txt in the Explorer sidebar.
Ignore file missing?
Confirm the filename starts with a full stop. Check that Visual Studio Code did not add .txt to the end.
Use this guided prompt if the file still looks wrong: Help me create a valid .gitignore file in Visual Studio Code.
✔️ Awesome, I've got everything!
Great, save both files before you continue.
ⓧ I'd like to double check the full code
Compare each file with the complete versions below.
pytest==9.2.0
.venv/
__pycache__/
.pytest_cache/
The virtual environment gives this project its own Python dependency space. Its files live inside .venv under devops-ci-starter.
- Create the project-local virtual environment by running this command in the Visual Studio Code terminal:
py -m venv .venv
What does this command do?
Python creates an isolated environment inside the .venv folder. Packages installed there stay separate from system-wide packages.
- Confirm a .venv folder appears in the Explorer sidebar.
Virtual environment not created?
Confirm the terminal path points to devops-ci-starter. Recheck py --version if the command reports that Python cannot be found.
Use this guided prompt if the folder does not appear: Help me create a Python virtual environment in my Windows project folder.
- Activate the virtual environment by running this command:
.venv\Scripts\activate
What does activation change?
Activation places the virtual environment's Python executable first for the current terminal session. Future package commands now target this project-local environment.
The environment name now appears at the start of your terminal prompt. That prefix confirms the environment is active.
Activation command not working?
Confirm the terminal tab uses Command Prompt. Check that the .venv folder exists inside devops-ci-starter.
Use this guided prompt if activation still fails: Help me activate my .venv environment from Command Prompt.
Install and verify pytest
The active environment is ready to receive the dependency pinned in requirements.txt. Installing from that file keeps the setup repeatable.
- Install the dependency declared in requirements.txt by running this command:
py -m pip install -r requirements.txt
What does this command do?
The command asks pip to read requirements.txt. Pip installs the pinned pytest release into the active virtual environment.
The installation output should finish successfully with pytest 9.2.0 available in the environment.
Package installation failed?
Confirm requirements.txt contains exactly one dependency line. Check that the virtual environment name still appears in the terminal prompt.
Use this guided prompt if installation still fails: Help me install pytest from requirements.txt in my active Windows virtual environment.
- Check which Python executable the terminal resolves by running this command:
where python
What does this path prove?
The first returned path should include .venv\Scripts\python. That path proves the active terminal resolves Python from your project environment.
Your local commands now use the isolated Python installation inside devops-ci-starter.
Path does not include .venv?
Run the activation command again from the Command Prompt terminal. Make sure the prompt shows the environment name before repeating the path check.
Use this guided prompt if the system Python still appears first: Help me make python resolve to my active .venv on Windows.
Before you run the final check, which pytest version do you expect the activated environment to report?
- Verify the installed pytest version by running this command:
pytest --version
What does this check prove?
The command runs pytest from the active virtual environment. Its output confirms which pinned release is ready to run your future tests.
You should see pytest 9.2.0 in the terminal. Your isolated Windows test environment is ready.
Pytest version not found?
Confirm the environment remains active. Run the dependency installation command again if the terminal says pytest cannot be found.
Use this guided prompt if a different version appears: Help me make my active virtual environment use pytest 9.2.0.
Your Windows workspace now has a verified Python version. It also has isolated dependencies ready for local testing. Next, you will build the calculator module and its first automated tests.
Build and Test the Calculator
Your Python workspace is ready in Visual Studio Code. The activated environment gives this project its own pytest installation.
A continuous integration pipeline needs a green local baseline before automation can reveal where a future failure began. In this step, you will build a small calculator module.
Three tests will protect the calculator's expected behavior. A green local run proves the baseline is trustworthy.
In this step, get ready to:
- Define two calculator behaviors.
- Protect the calculator with three plain assertions.
- Prove the local baseline with one green pytest run.
Create the calculator functions
The calculator keeps the behavior small enough to reason about before automation enters the picture. Each function has one clear result that a test can protect.
- Select Explorer in the Activity Bar.
- Select New File... in the Explorer title bar.
- Enter calculator.py as the filename.
- Press Enter to create the file.
- Add both calculator behaviors by pasting this code into calculator.py:
def multiply(a: int, b: int) -> int:
"""Return the product of two integers."""
return a * b
def is_even(number: int) -> bool:
"""Return True when an integer is even."""
return number % 2 == 0
What does this code do?
- The multiply() function uses a * b to return the product of two integers.
- The is_even() function checks whether division by two leaves a remainder of zero.
- The type hints describe the integer inputs and the expected return type for each function.
- Save calculator.py.
- Confirm the Explorer lists calculator.py inside devops-ci-starter.
- Predict the return value for multiply(3, 4) by tracing return a * b.
- Predict the return value for is_even(8) by tracing return number % 2 == 0.
Calculator file looks incomplete?
- Check that both def lines begin at the left edge of calculator.py.
- Check that each return line is indented inside its function.
Ask for help with the file structure:
The tabs below give you a complete checkpoint for calculator.py.
✔️ Awesome, I've got everything!
Your calculator.py file contains both functions with their expected return expressions.
ⓧ I'd like to double check the full code
def multiply(a: int, b: int) -> int:
"""Return the product of two integers."""
return a * b
def is_even(number: int) -> bool:
"""Return True when an integer is even."""
return number % 2 == 0
Create the automated tests
A test pairs an example input with an expected result. A plain assert makes that expectation executable.
- Return to the Explorer.
- Select New File... in the Explorer title bar.
- Enter test_calculator.py as the filename.
- Press Enter to create the file.
- Protect the calculator behaviors by pasting this code into test_calculator.py:
from calculator import is_even, multiply
def test_multiply_positive_numbers() -> None:
assert multiply(3, 4) == 12
def test_multiply_negative_number() -> None:
assert multiply(-2, 3) == -6
def test_is_even() -> None:
assert is_even(8) is True
What do these tests protect?
- The import gives the test file access to both calculator functions.
- The first assertion protects multiplication with two positive integers.
- The second assertion protects multiplication when one integer is negative.
- The final assertion protects the even-number check.
- Save test_calculator.py.
- Match assert multiply(3, 4) == 12 to the positive multiplication behavior.
- Match assert multiply(-2, 3) == -6 to the negative multiplication behavior.
- Match assert is_even(8) is True to the even-number behavior.
Tests not lining up with the functions?
- Confirm the test filename is exactly test_calculator.py.
- Check that the import names match is_even and multiply.
- Compare every expected value with the behavior you traced in calculator.py.
Ask for help matching each test to its function:
The tabs below give you a complete checkpoint for test_calculator.py.
✔️ Awesome, I've got everything!
Your test_calculator.py file imports both functions and defines three tests.
ⓧ I'd like to double check the full code
from calculator import is_even, multiply
def test_multiply_positive_numbers() -> None:
assert multiply(3, 4) == 12
def test_multiply_negative_number() -> None:
assert multiply(-2, 3) == -6
def test_is_even() -> None:
assert is_even(8) is True
Run the local feedback loop
Pytest discovers files that match test_*.py. It collects functions whose names start with test.
- Switch back to the Command Prompt terminal from earlier.
Before you run the command, do you expect pytest to discover every test and finish successfully?
- Test the project from the Command Prompt terminal by running this command:
pytest
What should I see?
- The command searches the current project for discoverable test files.
- Pytest collects the three functions in test_calculator.py.
- The terminal should finish with 3 passed.
Not seeing three passing tests?
- Confirm the terminal prompt still shows the activated .venv environment.
- Save calculator.py.
- Save test_calculator.py.
- Compare any failing assertion with the matching return expression in calculator.py.
Help me diagnose my local pytest result.
Your calculator now proves its own behavior with three passing tests. Next, you will automate this same check after every push.
Automate Tests and Trigger a Failure
Your calculator now has three passing local tests. That green baseline proves the expected behavior before automation takes over.
Local tests only protect the project when someone remembers to run them. In this step, GitHub Actions will run pytest after every push. You will then introduce a defect to prove the pipeline can stop broken code.
In this step, get ready to:
- Create a continuous integration workflow that runs the tests on a hosted Linux runner.
- Publish the green baseline to a public GitHub repository.
- Trigger a red workflow run with an intentional multiplication defect.
Create the CI workflow
A YAML workflow describes when automation starts. It also defines the tasks performed by the remote runner.
Why GitHub Actions instead of Jenkins?
GitHub Actions runs beside your repository. A separate Jenkins server requires its own setup.
This approach keeps your attention on the continuous integration feedback loop. Every push becomes an automatic test opportunity.
- Select the new-file icon in the Visual Studio Code Explorer sidebar.
- Enter .github/workflows/ci.yml as the file path.
- Press Enter to create the nested folders.
- Fill ci.yml by copying the workflow below:
name: Python CI
on: [push]
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v6
- name: Set up Python 3.15.2
uses: actions/setup-python@v6
with:
python-version: "3.15.2"
- name: Install dependencies
run: python -m pip install -r requirements.txt
- name: Run tests
run: pytest
What does this workflow do?
- The on: [push] trigger starts the workflow whenever a commit reaches the repository.
- The contents: read permission lets the job read the repository.
- The ubuntu-latest value selects a hosted Linux runner.
- The actions/checkout@v6 step places the repository files in the runner workspace.
- The actions/setup-python@v6 step provides Python 3.15.2.
- The installation step reads requirements.txt.
- The final step runs pytest against the three tests.
- Save .github/workflows/ci.yml.
- Confirm the Visual Studio Code Explorer shows ci.yml inside .github/workflows.
✔️ Awesome, I've got everything!
Your workflow file is saved in the directory GitHub Actions checks for workflow definitions.
ⓧ I'd like to double check the full code
name: Python CI
on: [push]
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v6
- name: Set up Python 3.15.2
uses: actions/setup-python@v6
with:
python-version: "3.15.2"
- name: Install dependencies
run: python -m pip install -r requirements.txt
- name: Run tests
run: pytest
What should match?
Compare the trigger, permission, runner, action versions, Python version, installation command, and test command with your saved file.
Workflow file missing or misaligned?
- Confirm the full path is .github/workflows/ci.yml inside devops-ci-starter.
- Check that each nested YAML section uses spaces matching the reference file.
Help me compare my workflow file with the expected YAML structure.
Publish the first green version
The workflow needs a remote repository before a hosted runner can execute it. Your first commit captures the passing local baseline.
A public repository makes these project files visible to anyone. This project contains code only, so no credentials belong in the commit.
- Create the local Git history on the main branch by running these commands:
git init -b main
git add .
git commit -m "First commit"
What do these commands do?
- The first command initializes Git with main as the first branch.
- The second command stages the project files.
- The .gitignore rules keep generated folders out of the commit.
- The final command records the green baseline with the message First commit.
You should see a commit summary containing the calculator, tests, requirements, ignore rules, and workflow file.
Commit blocked?
- Return to the global name configuration from the workspace setup if Git reports a missing identity.
- Return to the global email configuration from the workspace setup if Git reports a missing email.
- Confirm .gitignore contains .venv/ if virtual environment files appear in the commit summary.
Help me resolve the problem with my first Git commit.
- Go to GitHub in your browser.
- Select New repository.
- Enter devops-ci-starter as the repository name.
- Choose public visibility.
- Leave all repository initialization options unselected.
- Click Create repository.
GitHub now shows an empty public repository named devops-ci-starter. It is ready to receive your existing project history.
- Copy the repository remote URL into your GitHub repository remote URL.
- Switch back to the Visual Studio Code terminal from earlier.
- Use your GitHub repository remote URL wherever the first command shows REMOTE-URL.
GitHub authentication
The first push may start a browser authentication flow. Complete it with the GitHub account that owns the new repository.
- Connect the local repository to GitHub by running these commands:
git remote add origin REMOTE-URL
git remote -v
git push -u origin main
What happens during publication?
- The first command stores the GitHub repository as the origin remote.
- The second command prints the configured remote URL.
- The final command publishes main to GitHub.
- The push trigger starts the Python CI workflow.
Before you check the workflow, do you think the same three tests will pass on the hosted runner?
- Switch back to the public repository in your browser.
- Click the Actions tab.
- Open the newest Python CI run.
- Wait for the workflow to finish.
You should see a green check on the first run. Its test output confirms that all three tests pass on the hosted Linux runner.
Your automated safety net is live. Every new push now starts the same test suite without relying on someone to remember it.
No green workflow run?
- Confirm the project files reached the main branch of the public repository.
- Confirm the repository contains .github/workflows/ci.yml.
- Open the stopped workflow step to identify whether checkout, Python setup, dependency installation, or testing failed.
Help me diagnose why my first Python CI run is missing or failing.
Trigger the designed failure
The tests now act as a contract for the calculator. You will alter the multiplication implementation while leaving that contract unchanged.
- Switch back to calculator.py in Visual Studio Code.
- Find this line inside multiply():
return a * b
What is the current behavior?
This line returns the product of a and b. It matches the behavior protected by both multiplication tests.
- Replace the multiplication line with this addition line:
return a + b
What changed?
The function now returns a sum for the same inputs. Its name and tests remain unchanged.
- Save calculator.py.
Before you push this change, do you think the unchanged remote tests will accept the new return value?
- Record the intentional defect by running these commands:
git add .
git commit -m "Introduce a multiply bug"
git push origin main
What does this push test?
The commit records the changed implementation while preserving the original tests. The push starts a new Python CI run against that exact revision.
- Switch back to the repository in your browser.
- Select the Actions tab.
- Open the newest Python CI run.
- Wait for the workflow to finish.
- Expand the Run tests step.
The newest run finishes with a red failure. The positive multiplication test receives 7 where it expects 12.
The negative multiplication test receives 1 where it expects -6. The even-number test still passes.
That red run is useful evidence. Your pipeline caught a real code defect through the same assertions that protected the green baseline.
Newest run still green or missing?
- Confirm the newest commit message is Introduce a multiply bug.
- Confirm calculator.py contains return a + b inside multiply().
- Confirm ci.yml still contains the on: [push] trigger.
Help me work out why the intentional multiply defect did not produce a red Python CI run.
✔️ Awesome, I've got everything!
Your local implementation contains the intentional defect that produced the red remote run.
ⓧ I'd like to double check the full code
def multiply(a: int, b: int) -> int:
"""Return the product of two integers."""
return a + b
def is_even(number: int) -> bool:
"""Return True when an integer is even."""
return number % 2 == 0
What should match?
The multiply() function should contain the intentional addition defect. The is_even() function should remain unchanged.
Your pipeline has proved that it can reject broken behavior automatically. Next, you will use the failure log as evidence to repair the implementation and restore a green run.
Diagnose the Failure and Restore Green
Your last push produced the red GitHub Actions run that this project was designed to create. The failed pytest assertions now hold the evidence you need to repair the defect.
A red CI pipeline becomes useful when you can trace its evidence back to a specific line of code. In this step, you will diagnose the failed multiplication tests before restoring the pipeline to green.
In this step, get ready to:
- Use the workflow log to connect the failed multiplication tests to the sum returned by multiply().
- Repair multiply() through a locally verified change.
- Publish the repair for a green hosted run.
Read the failure as evidence
A failed workflow run is a diagnostic record. Its assertion details connect each protected behavior to the result produced by your code.
- Switch back to the repository's Actions tab from earlier.
- Select the newest failed Python CI run for the Introduce a multiply bug commit.
- Select the test job.
- Expand the Run tests step.
- Find the failure for test_multiply_positive_numbers().
- Compare the expected result of 12 with the actual result of 7.
- Find the failure for test_multiply_negative_number().
- Compare the expected result of -6 with the actual result of 1.
The log shows both failures producing sums. You can see 3 + 4 = 7 plus -2 + 3 = 1.
That repeated use of addition points directly to return a + b inside multiply(). You have turned a red status into a specific diagnosis.
What did the workflow prove?
The workflow completed its job by exposing a result that violated two assertions. The shared failure pattern identifies the implementation as the source of the defect.
The tests still describe the intended behavior. Their expected products give you a stable target for the repair.
Repair the defect and check locally
The smallest safe repair restores the multiplication operator while leaving the tests unchanged. A green local run confirms that the implementation satisfies the same expectations that failed remotely.
- Switch back to Visual Studio Code from earlier.
- Select calculator.py in the Explorer sidebar.
- Find the multiply() function whose final line reads return a + b.
- Replace the multiply() function by copying this corrected version:
def multiply(a: int, b: int) -> int:
"""Return the product of two integers."""
return a * b
What does this repair do?
- The * operator makes multiply() return a product.
- The unchanged tests continue to protect the intended multiplication behavior.
- Save calculator.py.
Before you run the local suite, what result do you expect after restoring multiplication? The next command tests your prediction.
- Run the local test suite from the devops-ci-starter terminal by running this command:
pytest
What does this check prove?
Pytest discovers the three test functions in test_calculator.py. It executes them against the repaired code in calculator.py.
A passing suite proves the multiplication repair preserved the existing even-number behavior.
You should see 3 passed in the terminal. Your repair now has a green local baseline.
Still seeing a local test failure?
- Confirm you saved calculator.py after replacing the function.
- Check that multiply() returns a * b.
- Confirm the terminal is using the virtual environment from earlier.
Get help with my local pytest failure.
Your corrected file should now match the finished version below.
✔️ Awesome, I've got everything!
Your corrected calculator.py is saved. The local suite is ready for the hosted check.
ⓧ I'd like to double check the full code
def multiply(a: int, b: int) -> int:
"""Return the product of two integers."""
return a * b
def is_even(number: int) -> bool:
"""Return True when an integer is even."""
return number % 2 == 0
What should match?
- The multiply() function returns the product with *.
- The is_even() function remains unchanged.
Publish the fix and confirm green
The local run proves the repaired code works on your Windows environment. A fresh hosted runner checks the committed version on Linux.
- Publish the corrected snapshot from the Command Prompt terminal from earlier by running these commands:
git add .
git commit -m "Fix the multiply implementation"
git push origin main
What do these commands publish?
- The first command stages the corrected file.
- The second command records the repair in project history.
- The third command sends main to the configured origin remote.
- The push trigger starts a new workflow run for that commit.
Before you inspect the new run, do you expect the hosted suite to stay red or return to green? The next check tests your prediction.
- Switch back to the repository's Actions tab from earlier.
- Refresh the workflow runs list.
- Select the newest Python CI run for the Fix the multiply implementation commit.
- Wait for the newest run to finish.
- Select the test job.
- Confirm the Set up Python 3.15.2 step has a green check.
- Expand the Run tests step.
The newest run shows a green check. The test log shows 3 passed.
That is the full red-to-green loop complete. Your corrected code now passes on the ubuntu-latest runner with Python 3.15.2.
Is the newest run still red?
- Expand the Run tests step to identify which assertion still fails.
- Confirm the pushed version of calculator.py contains return a * b.
- Check the terminal output to confirm the repair commit reached main.
Help me diagnose my red repair run.
- Return to the Python CI workflow runs list.
- Compare the red Introduce a multiply bug run against the green Fix the multiply implementation run.
You now have visible proof that the pipeline caught a real defect. Your project history also shows the tested correction that restored green.
Secret mission
Test Across Three Python Versions
Your workflow currently proves the calculator works with one Python version. Expand it into a compatibility matrix that runs the same tests across Python 3.13, 3.14, and 3.15.
Clean Up Your Resources
Clean Up Your Resources
Your finished project can remain available for more practice. You can also pause your work or remove both copies completely. The GitHub repository stays on the $0 path while it remains public and its GitHub Actions workflow uses standard hosted runners.
Resources you used:
- Your local devops-ci-starter folder, including the disposable .venv environment.
- Your public GitHub repository named devops-ci-starter, including its workflow and Actions history.
Keep everything running
Keep both copies if you want to continue practising the automated testing loop. Your files and workflow remain ready for the next change.
- Keep the public devops-ci-starter repository on GitHub.
- Leave the local devops-ci-starter folder in its current location.
- Push future changes to main whenever you want to run the three-version workflow again.
Each future push starts three test jobs. Your commit history and workflow logs remain available for comparison.
Pause - I'll come back to this later
Pause the project when you want to free your local session while keeping every file. The workflow remains stored until your next push.
- Close the Command Prompt terminal from earlier to end its active .venv session.
- Close Visual Studio Code.
- Leave the local devops-ci-starter folder in place.
- Leave the public devops-ci-starter repository in place.
No workflow runs while you are not pushing changes. Another push to main restarts the three-version test matrix.
Delete - I don't want to use this again
Deleting both copies is permanent. Choose this option only when you no longer need the code or its history.
The active .venv applies to the current Command Prompt session. End that session before removing the project folder.
- End the active environment by running this command in the Command Prompt terminal from earlier:
deactivate
What does this command do?
This command disconnects the current terminal session from .venv. The environment files remain inside the project folder until you delete it.
Command Not Available?
If the terminal no longer shows an active .venv session, the environment is already inactive. Continue with the folder removal.
Ask for help if the environment still appears active:
The local folder contains the environment, source files, tests, workflow file, and local Git history. Removing the folder clears the entire local copy.
- Move to the folder above devops-ci-starter and remove the project by running these commands:
cd ..
rmdir /s /q devops-ci-starter
What Do These Commands Remove?
- The first line moves Command Prompt out of the project folder.
- The second line permanently removes the devops-ci-starter folder.
- The deletion includes .venv and every project file stored inside the folder.
- Confirm that devops-ci-starter no longer appears in its previous parent folder.
Local Folder Still Present?
Close Visual Studio Code if it still has the project open. Use Windows file management to remove the remaining devops-ci-starter folder from its existing location.
Get help with a folder that Windows will not remove.
The remote repository stores the public copy and its Actions history. Deleting it also stops future push-triggered workflow runs for this project.
- Return to the public devops-ci-starter repository on GitHub.
- Select the Settings tab in the repository navigation.
- Scroll to the Danger Zone section.
- Select Delete this repository.
- Complete GitHub's confirmation prompt.
- Return to the former repository URL.
The repository should no longer be available at its former URL. You have cleared both the local copy and the public copy.
Repository Deletion Unavailable?
Confirm that you are signed in to the account that owns devops-ci-starter. Repository deletion requires owner access.
Get help locating the repository deletion controls.
Nice Work!
Nice Work!
You did it! Your Python calculator now has an automated safety net powered by GitHub Actions.
You've learned how to:
- Build a calculator module with three automated checks. Use pytest to protect multiplication behavior. Protect even-number behavior with the same local test suite.
- Publish the project to a public GitHub repository with Git version control. Configure the workflow to test every push on a hosted Linux runner.
- Complete a red-to-green CI loop by introducing a defect in multiply(). Use the failed assertion as evidence. Restore the implementation until the remote pipeline returns to green.
- Secret Mission: Convert one CI job into a compatibility matrix. Confirm green jobs for Python 3.13. Confirm the same result for Python 3.14. Verify Python 3.15 too.
Ready to quiz yourself?