Jenkins CI/CD (2/11): Create the Minimal Repo and Prove Local Tests Run
Summary: Build a minimal Python package with a
greet()function, a CLI entry point, and eight unit tests. By the end you will have a workingsrc-layout project, a green pytest run, and a git repo ready for Jenkins to pick up.
Example Values Used in This Tutorial
| Key | Value |
|---|---|
| Package name | helloci |
| Source root | src/helloci |
| Python version | 3.12 |
| Working directory | ~/projects/helloci |
| Virtual environment | ~/projects/helloci/.venv |
| Test framework | pytest |
| CLI command | helloci greet Alice |
0. Prerequisites
- Python
3.12or later installed and available aspython3 pipandvenvmodules working (runpython3 -m venv --helpto confirm)- Git installed (
git --version) - A terminal open in your home directory
Note: This is Part 2 of an 11-part series. Part 1 covered the mental model and prerequisites checklist. If you already have Python 3.12, pip, and Git you are ready to go.
1. Create the Project Directory
Create the project root and move into it.
mkdir -p ~/projects/helloci
cd ~/projects/hellociCode language: Shell Session (shell)
The src layout keeps your package source separate from tests, config files, and tooling scripts. Here is the target structure you will build in this tutorial:
helloci/
├── src/
│ └── helloci/
│ ├── __init__.py
│ └── cli.py
├── tests/
│ ├── __init__.py
│ └── test_greet.py
├── pyproject.toml
└── README.mdCode language: Shell Session (shell)
Create all the directories and empty files now.
mkdir -p src/helloci tests
touch src/helloci/__init__.py src/helloci/cli.py
touch tests/__init__.py tests/test_greet.py
touch pyproject.toml README.mdCode language: Shell Session (shell)
2. Write the greet Function
Open src/helloci/__init__.py and add the greet function. This is the entire public API of the package.
"""helloci — a tiny package that greets people."""
def greet(name: str) -> str:
"""Return a greeting string for *name*."""
return f"Hello, {name}!"Code language: Python (python)
Three lines of real code. That is deliberate. The goal is a package small enough that nothing distracts from the CI pipeline you will build around it. If greet() works locally, Jenkins has something to test. If it breaks, the failure is obvious.
3. Write the CLI Entry Point
Open src/helloci/cli.py. This file gives you a helloci command that calls greet() from the terminal.
"""Command-line interface for helloci."""
import argparse
from helloci import greet
def main() -> None:
"""Parse arguments and print a greeting."""
parser = argparse.ArgumentParser(
description="Greet someone from the command line.",
)
parser.add_argument("name", help="Name of the person to greet")
args = parser.parse_args()
print(greet(args.name))
if __name__ == "__main__":
main()Code language: Python (python)
The main() function reads one positional argument and prints the greeting. You will wire this up as a console script in the next step.
4. Create pyproject.toml
Open pyproject.toml and paste the full contents below.
[build-system]
requires = ["setuptools>=68.0", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "helloci"
version = "0.1.0"
description = "A tiny greeting package used to learn Jenkins CI/CD."
requires-python = ">=3.12"
[project.optional-dependencies]
test = ["pytest>=7.0"]
[project.scripts]
helloci = "helloci.cli:main"
[tool.setuptools.packages.find]
where = ["src"]Code language: TOML, also INI (ini)
Key points in this file:
[project.scripts]registers thehellociconsole command so you can runhelloci Aliceafter installing.[project.optional-dependencies]declares pytest as a test dependency. Installing withpip install -e ".[test]"pulls it in automatically.[tool.setuptools.packages.find]tells setuptools to look inside thesrc/directory for packages.
5. Add a README
Open README.md and add a minimal description. The file should contain a project title, a one-line summary, and a quick-start snippet showing how to create a venv, install, and run tests.
# helloci
A tiny Python package used to learn Jenkins CI/CD.
## Quick startCode language: Shell Session (shell)
python -m venv .venv source .venv/bin/activate pip install -e “.[test]” pytest
Code language: Shell Session (shell)
The README is not strictly required for CI, but every repo should have one. Keep it short.
6. Write the Tests
Open tests/test_greet.py and add all eight tests.
"""Unit tests for the helloci package."""
import subprocess
import sys
from helloci import greet
def test_greet_basic():
"""greet returns the expected greeting string."""
assert greet("Alice") == "Hello, Alice!"
def test_greet_empty_string():
"""greet handles an empty string without crashing."""
assert greet("") == "Hello, !"
def test_greet_name_with_spaces():
"""greet handles a name that contains spaces."""
assert greet("Bob Smith") == "Hello, Bob Smith!"
def test_greet_special_characters():
"""greet handles special characters in the name."""
assert greet("O'Reilly") == "Hello, O'Reilly!"
def test_greet_long_name():
"""greet handles an unusually long name."""
long_name = "A" * 500
result = greet(long_name)
assert result == f"Hello, {long_name}!"
assert len(result) == 508 # "Hello, " (7) + 500 + "!" (1)
def test_greet_unicode():
"""greet handles Unicode characters."""
assert greet("\u00e9milie") == "Hello, \u00e9milie!"
def test_cli_entry_point():
"""The helloci CLI command runs and prints the greeting."""
result = subprocess.run(
[sys.executable, "-m", "helloci.cli", "World"],
capture_output=True,
text=True,
)
assert result.returncode == 0
assert result.stdout.strip() == "Hello, World!"
def test_greet_multiple_calls():
"""Calling greet multiple times returns independent results."""
first = greet("Alice")
second = greet("Bob")
assert first == "Hello, Alice!"
assert second == "Hello, Bob!"
assert first != secondCode language: Python (python)
These eight tests cover the basics: correct output, edge cases (empty string, spaces, special characters, long input, Unicode), the CLI subprocess, and multiple calls. That is more than enough for Jenkins to exercise.
Tip: Keep your first test suite simple. The goal is to prove that the pipeline runs tests end-to-end. You can add coverage, property-based tests, and integration tests later in the series.
7. Set Up the Virtual Environment and Install
Create a virtual environment, activate it, and install the package in editable mode with test dependencies.
cd ~/projects/helloci
python3 -m venv .venv
source .venv/bin/activateCode language: Shell Session (shell)
pip install -e ".[test]"Code language: Shell Session (shell)
Editable mode (-e) means changes to your source files take effect immediately without reinstalling. The [test] extra pulls in pytest.
Verify the installation.
pip show hellociCode language: Shell Session (shell)
Name: helloci
Version: 0.1.0
Location: /home/user/projects/helloci/srcCode language: Shell Session (shell)
Confirm the CLI command works.
helloci WorldCode language: Shell Session (shell)
Hello, World!Code language: Shell Session (shell)
8. Run pytest Locally
Run the full test suite.
pytest -vCode language: Shell Session (shell)
========================= test session starts ==========================
collected 8 items
tests/test_greet.py::test_greet_basic PASSED [ 12%]
tests/test_greet.py::test_greet_empty_string PASSED [ 25%]
tests/test_greet.py::test_greet_name_with_spaces PASSED [ 37%]
tests/test_greet.py::test_greet_special_characters PASSED [ 50%]
tests/test_greet.py::test_greet_long_name PASSED [ 62%]
tests/test_greet.py::test_greet_unicode PASSED [ 75%]
tests/test_greet.py::test_cli_entry_point PASSED [ 87%]
tests/test_greet.py::test_greet_multiple_calls PASSED [100%]
========================== 8 passed in 0.42s ===========================Code language: Shell Session (shell)
All eight tests pass. This is the exact result Jenkins will need to reproduce.
Warning: If any test fails here, fix it before continuing. CI is just running your commands in a clean-ish environment. If something is awkward locally, it will be worse in Jenkins.
9. Initialize the Git Repo
Initialize a git repository and make the first commit.
cd ~/projects/helloci
git initCode language: Shell Session (shell)
Create a .gitignore to keep build artifacts and the virtual environment out of version control.
cat > .gitignore << 'EOF'
__pycache__/
*.pyc
*.egg-info/
.venv/
dist/
build/
results/
.pytest_cache/
EOFCode language: Shell Session (shell)
Stage everything and commit.
git add .
git commit -m "Initial commit: minimal helloci package with 8 tests"Code language: Shell Session (shell)
[main (root-commit) a1b2c3d] Initial commit: minimal helloci package with 8 tests
8 files changed, 120 insertions(+)
create mode 100644 .gitignore
create mode 100644 README.md
create mode 100644 pyproject.toml
create mode 100644 src/helloci/__init__.py
create mode 100644 src/helloci/cli.py
create mode 100644 tests/__init__.py
create mode 100644 tests/test_greet.pyCode language: Shell Session (shell)
Verify the state.
git log --onelineCode language: Shell Session (shell)
a1b2c3d Initial commit: minimal helloci package with 8 testsCode language: Shell Session (shell)
Your repo is clean, committed, and ready for Jenkins to clone.
Summary
You built a minimal Python package from scratch and proved it works locally.
src/helloci/__init__.pycontains thegreet()functionsrc/helloci/cli.pyprovides ahellociCLI command via argparsepyproject.tomldeclares metadata, the console script, and test dependenciestests/test_greet.pycontains eight unit tests covering basic output, edge cases, the CLI, and multiple calls- pytest passes all eight tests with a single
pytest -vcommand - The git repo has one clean commit with a proper
.gitignore
The key takeaway: CI is just running your commands in a clean-ish environment. If the local workflow is pip install -e ".[test]" followed by pytest, then that is exactly what Jenkins will do. Get it working here first. In Part 3 you will create a Jenkins pipeline that clones this repo and runs these same tests automatically.
Jenkins CI/CD — All Parts
- 1 Jenkins CI/CD (1/11): Prerequisites and Mental Model
- 2 Jenkins CI/CD (2/11): Create the Minimal Repo and Prove Local Tests Run You are here
- 3 Jenkins CI/CD (3/11): First Jenkins Job With a Jenkinsfile
- 4 Jenkins CI/CD (4/11): Test Reporting With JUnit Results in Jenkins
- 5 Jenkins CI/CD (5/11): Fast-Fail Static Checks With a Lint Stage
- 6 Jenkins CI/CD (6/11): Integration Tests With Docker Compose
- 7 Jenkins CI/CD (7/11): Artifacts and Debugging Failed Builds
- 8 Jenkins CI/CD (8/11): Workspace Cleanup, Timeouts, and Retries
- 9 Jenkins CI/CD (9/11): Concurrency and Port Conflicts
- 10 Jenkins CI/CD (10/11): Release Pipeline — Build Artifacts on Tags
- 11 Jenkins CI/CD (11/11): Publish to PyPI Securely
