Python Project Structure: venv, pip, Requirements, and Testing

Python By TryzTech Team
PythonProject StructurevenvTestingBeginner

A single-file script is great for learning. But once your code has several functions, modules, data files, dependencies, and tests, you need a cleaner project structure.

Project structure is not only about looking professional. A clear structure makes a project easier to rerun, easier for others to read, and safer as dependencies grow.

Think of structure as a work map. Source code has its place, tests have their place, dependencies are recorded, and the README explains the commands to run. With these boundaries, another person does not have to guess which file to open first.

A simple folder structure

For a small project, use a structure like this:

expense-tracker/
  src/
    expense_tracker/
      __init__.py
      app.py
      storage.py
  tests/
    test_app.py
  requirements.txt
  README.md

src/expense_tracker/ contains the main source code. tests/ contains tests. requirements.txt records dependencies. README.md explains how to run the project.

You do not need this structure for every tiny exercise, but it is a solid pattern once a project becomes more serious.

Creating a virtual environment

A virtual environment separates project dependencies from your global Python installation. This keeps one project from breaking another.

A virtual environment is not a new Python version. It is an isolated workspace with its own package location and executables, so pytest and other packages for this project do not mix with a different project.

python -m venv .venv

Activate it on macOS or Linux:

source .venv/bin/activate

Activate it in Windows PowerShell:

.\.venv\Scripts\Activate.ps1

After activation, the terminal usually shows (.venv) at the start of the prompt.

Installing packages with pip

Use pip to install packages.

pip install pytest

To see installed packages:

pip list

Avoid installing project dependencies into global Python. Activate .venv first.

requirements.txt

After installing dependencies, save the list:

pip freeze > requirements.txt

Someone else can install the same dependencies with:

pip install -r requirements.txt

For beginner projects, requirements.txt is enough. Later, you can learn modern packaging with pyproject.toml.

.env and configuration

A .env file is often used for configuration that changes between environments, such as API keys, database URLs, or app mode.

Example .env:

APP_ENV=development
DATA_FILE=expenses.json

Do not commit real secrets to a repository. For practice projects, create a .env.example file so others know which variables are needed.

Separating source code and tests

In src/expense_tracker/app.py:

# File: app-total.py
def calculate_total(expenses):
    return sum(expense["amount"] for expense in expenses)

In tests/test_app.py:

# File: test_app.py
from expense_tracker.app import calculate_total

def test_calculate_total():
    expenses = [
        {"title": "Coffee", "amount": 25_000},
        {"title": "Lunch", "amount": 50_000},
    ]

    assert calculate_total(expenses) == 75_000

Tests help keep important logic correct as the code changes.

Start with tests for the most important behavior. A normal case protects the main flow, while empty or invalid cases clarify the program’s boundaries. Test names should describe behavior rather than implementation details, so they remain useful when the internals change.

Running pytest

Install pytest:

pip install pytest

Run tests:

pytest

When using a src/ structure, you may need to install the project in editable mode or configure PYTHONPATH. For a beginner project, the simplest path is to run commands from the project root and make sure the package can be imported.

A simple entry point

Use a main() function to keep the main file tidy.

# File: main.py
def main():
    print("Expense tracker")

if __name__ == "__main__":
    main()

This pattern lets the file run directly while still being safe to import from tests or other modules.

Mini structure example

src/expense_tracker/app.py:

# File: app-helpers.py
def calculate_total(expenses):
    return sum(expense["amount"] for expense in expenses)

def format_currency(amount):
    return f"IDR {amount:,}".replace(",", ".")

src/expense_tracker/storage.py:

# File: storage.py
import json
from pathlib import Path

def load_expenses(path):
    file_path = Path(path)

    if not file_path.exists():
        return []

    with file_path.open("r", encoding="utf-8") as file:
        return json.load(file)

src/expense_tracker/main.py:

# File: main-report.py
from expense_tracker.app import calculate_total, format_currency
from expense_tracker.storage import load_expenses

def main():
    expenses = load_expenses("expenses.json")
    total = calculate_total(expenses)
    print(format_currency(total))

if __name__ == "__main__":
    main()

This structure is small, but the boundaries are clear: app.py for logic, storage.py for files, and main.py for program flow.

Common mistakes

The first mistake is committing the .venv folder. Add .venv/ to .gitignore.

The second mistake is forgetting to record dependencies. If a project needs pytest, requests, or another package, make sure it is listed in requirements.txt.

The third mistake is placing all logic in main.py. That is fine at the start, but split it once the file grows.

The fourth mistake is only writing tests after the project breaks. Start with small tests for the most important functions.

Small Python project checklist

Before sharing a project, check:

  • The project has a README.md.
  • Dependencies are recorded in requirements.txt.
  • .venv/ is not committed.
  • There is a clear src/ folder or package.
  • There is a tests/ folder.
  • The command to run the project is documented in the README.
  • Important logic has at least one test.

Mini exercise

Create an expense-tracker project with:

  • A .venv virtual environment.
  • An expense_tracker package.
  • A calculate_total(expenses) function.
  • A format_currency(amount) function.
  • Tests for both functions.
  • A README.md with install and test commands.

Checklist before moving on

Try copying the project into another folder and follow the README from the beginning. If it only works because of a personal terminal setting, the documentation is incomplete. The activation command, dependency installation, and test command should all be clear.

Before moving to the mini project, remove one unused dependency and run the tests again. This shows that requirements.txt describes project needs rather than every package that has ever been installed on your computer.

Next: CLI mini project

Now the pieces are ready: basic syntax, functions, files, error handling, modules, OOP, project structure, and tests. In the final article of this series, we will combine them into a CLI expense tracker. Continue with Python CLI Mini Project: Build an Expense Tracker from Scratch.

If you want to review OOP, open Object-Oriented Python for Beginners.

Keep reading within the same topic.

Don't Miss Out

Get the latest tech articles, tips, and insights delivered to your inbox.