Skip to main content

A pytest plugin that enforces test quality standards through automatic marker detection and AAA structure validation

Project description

๐ŸŽ–๏ธ Pytest Drill Sergeant

CI Status codecov Quality Gate

PyPI version Python versions Downloads

Platforms License: MIT Code style: black

Tested Type Checked Drill Sergeant Approved

LISTEN UP, MAGGOTS! ๐Ÿ—ฃ๏ธ

Your test suite is a DISASTER! Tests scattered everywhere like a tornado hit your codebase, no markers, no structure, and don't even get me started on your "AAA" pattern that looks more like "Aaahhh-what-am-I-even-testing" pattern.

The Drill Sergeant is here to whip your tests into shape! ๐Ÿ’ช

This pytest plugin will turn your chaotic test mess into a disciplined, well-organized military formation. No mercy. No exceptions. Only QUALITY.

๐Ÿ… Live Battle Intelligence (Auto-Updating Intel)

Metric Live Status Military Assessment
๐Ÿ“ฆ Codebase Size Files "Lean and mean - no bloat allowed!"
โญ Bug Reports GitHub issues "Zero tolerance for battlefield failures!"
๐Ÿ“ˆ Activity Level Commits "Active military operations in progress!"
โšก Response Time GitHub last commit "Always ready for action!"
๐ŸŽฏ Stars Earned GitHub stars "Recognition from fellow soldiers!"

The Drill Sergeant's Live Record: "All systems operational, zero compromises accepted!" ๐ŸŽ–๏ธ

๐ŸŽฏ What This Bad Boy Does

  • ๐Ÿท๏ธ Automatic Marker Detection - Because apparently you can't be trusted to add @pytest.mark.unit yourself
  • ๐Ÿ“ AAA Structure Enforcement - "Arrange-Act-Assert" not "Arrange-Act-And-Hope-It-Works"
  • ๐Ÿ’ฅ Comprehensive Error Messages - So detailed even your manager could understand what you did wrong
  • ๐Ÿšจ Zero Tolerance Policy - One violation = one failed test. NO EXCEPTIONS!

๐Ÿš€ Installation (AKA Basic Training)

For Smart Developers

# Development dependency (where it belongs, recruit!)
uv add --group dev pytest-drill-sergeant

# Or if you're still using that ancient pip thing...
pip install pytest-drill-sergeant

For "Special" Developers

# Runtime dependency (really? You need test quality enforcement in production?)
uv add pytest-drill-sergeant

๐ŸŽ–๏ธ Advanced Arsenal (Secret Weapons Unlocked)

AAA Structure Auto Detection Synonyms Zero Config

๐Ÿ“ Before vs After (Prepare to be AMAZED)

Before: Your Disaster Zone ๐Ÿ”ฅ

# tests/whatever/test_something.py (what kind of name is that?!)
def test_thing():
    x = Calculator()
    result = x.add(1, 2)
    assert result == 3  # Wow, such insight. Much test. Very quality.

Drill Sergeant says: WHAT IS THIS GARBAGE? No marker, no structure, and it's in a random directory! This test FAILS until you fix it!

After: PROPER MILITARY FORMATION ๐ŸŽ–๏ธ

# tests/unit/test_calculator.py (NOW we're talking!)
import pytest

@pytest.mark.unit  # AUTOMATIC! The Sergeant detects from directory and adds this! ๐ŸŽฏ
def test_addition_with_positive_numbers():
    """Test addition functionality with positive integers."""
    # Arrange - Set up your battlefield, soldier!
    calculator = Calculator()
    first_operand = 5
    second_operand = 3
    expected_sum = 8

    # Act - Execute the mission!
    actual_sum = calculator.add(first_operand, second_operand)

    # Assert - Verify victory conditions!
    assert actual_sum == expected_sum

Drill Sergeant says: OUTSTANDING! This is what DISCIPLINE looks like!

For Simple Tests (One-Liner AAA)

Sometimes your test is so simple that combining AAA sections makes sense:

@pytest.mark.unit
def test_simple_calculation():
    """Test basic arithmetic operation."""
    # Arrange and Act - Set up calculator and perform addition
    result = Calculator().add(2, 3)

    # Assert - Verify the calculation is correct
    assert result == 5

@pytest.mark.unit
def test_ultra_simple():
    """Test with all AAA in one comment (for the truly lazy)."""
    # Arrange, Act, and Assert - Create, call, and verify in one swift motion
    assert Calculator().multiply(4, 2) == 8

Drill Sergeant says: Fine, soldier. Sometimes efficiency trumps ceremony. But don't get TOO comfortable!

๐ŸŽ–๏ธ Automatic Marker Detection (Because You're Lazy)

The Drill Sergeant isn't just here to yell at you - he's here to HELP. Place your tests in the right directories and watch the magic happen:

Directory Auto-Applied Marker What It Means
tests/unit/ @pytest.mark.unit Fast, isolated tests
tests/integration/ @pytest.mark.integration Tests multiple components
tests/e2e/ @pytest.mark.e2e End-to-end user scenarios
tests/api/ @pytest.mark.integration API endpoint tests
tests/performance/ @pytest.mark.performance Speed/load tests
tests/smoke/ @pytest.mark.integration Quick sanity checks

16 built-in mappings so you don't have to think too hard! ๐Ÿง  (I've seen you think, and it ain't pretty!)

How It Works

  1. You write a test (hopefully)
  2. Forget to add a marker (as usual)
  3. Drill Sergeant detects directory (tests/unit/test_foo.py)
  4. AUTOMATICALLY MODIFIES your test function to add @pytest.mark.unit
  5. Test passes as if you had added the marker yourself ๐ŸŽญ
  6. You look competent (even though the Sergeant did the work)

๐Ÿ”ง Configuration (For Control Freaks)

The Nuclear Option: Turn Everything Off

# pytest.ini
[tool:pytest]
drill_sergeant_enabled = false

Drill Sergeant says: You're on your own, soldier. Don't come crying when your tests are garbage.

Selective Enforcement (Baby Steps)

When you can't handle the full military experience and need training wheels ๐Ÿšฒ

# pytest.ini
[tool:pytest]
drill_sergeant_enforce_markers = true      # YES! ENFORCE THE MARKERS!
drill_sergeant_enforce_aaa = false         # Fine, be sloppy with your structure
drill_sergeant_auto_detect_markers = true  # Let me do your job for you
drill_sergeant_min_description_length = 5  # At least TRY to be descriptive

Custom Mappings (For Special Snowflakes)

# pytest.ini
[tool:pytest]
# Format: directory_name=marker_name (maps test directories to pytest markers)
drill_sergeant_marker_mappings = contract=api,smoke=integration,load=performance
# Translation for civilians:
# tests/contract/ โ†’ @pytest.mark.api
# tests/smoke/ โ†’ @pytest.mark.integration
# tests/load/ โ†’ @pytest.mark.performance

Or via environment (because you love complexity):

# Same format: directory=marker pairs
export DRILL_SERGEANT_MARKER_MAPPINGS="widget=unit,gizmo=integration"
# For those who need it spelled out:
# tests/widget/ โ†’ @pytest.mark.unit
# tests/gizmo/ โ†’ @pytest.mark.integration

๐ŸŽญ Error Messages That Don't Suck

When you inevitably mess up, the Drill Sergeant doesn't just say "test failed" like some amateur plugin. Oh no. You get the FULL TREATMENT:

โŒ CODE QUALITY: Test 'test_disaster' violates project standards by missing test annotations and missing AAA structure
๐Ÿ“‹ 3 requirement(s) must be fixed before this test can run:

๐Ÿท๏ธ  MISSING TEST CLASSIFICATION:
   โ€ข Add @pytest.mark.unit, @pytest.mark.integration, or move test to appropriate directory structure

๐Ÿ“ MISSING AAA STRUCTURE (Arrange-Act-Assert):
   โ€ข Add '# Arrange - description of what is being set up' comment before test setup
   โ€ข Add '# Act - description of what action is being performed' comment before test action

โ„น๏ธ  This is a PROJECT REQUIREMENT for all tests to ensure:
   โ€ข Consistent test structure and readability
   โ€ข Proper test categorization for CI/CD pipelines
   โ€ข Maintainable test suite following industry standards

๐Ÿ“š For examples and detailed requirements:
   โ€ข https://github.com/jeffrichley/pytest-drill-sergeant
   โ€ข pytest.ini (for valid markers)

Translation: Your test is bad and you should feel bad. Here's exactly how to fix it.

๐ŸŽช Configuration Examples (Real World Scenarios)

The "I'm New Here" Setup

# pytest.ini - Training wheels ON
[tool:pytest]
drill_sergeant_enabled = true
drill_sergeant_enforce_markers = false     # Baby steps
drill_sergeant_enforce_aaa = true          # Learn structure first
drill_sergeant_auto_detect_markers = true  # Let the magic happen

The "CI/CD Enforcer" Setup

# pytest.ini - NO MERCY in production
[tool:pytest]
drill_sergeant_enabled = true
drill_sergeant_enforce_markers = true      # ZERO TOLERANCE
drill_sergeant_enforce_aaa = true          # PERFECT STRUCTURE
drill_sergeant_auto_detect_markers = false # Do it yourself, lazy!

The "Legacy Codebase Survival" Setup

# pytest.ini - Gradual improvement without mass suicide
[tool:pytest]
drill_sergeant_enabled = true
drill_sergeant_enforce_markers = false     # Skip marker enforcement for now
drill_sergeant_enforce_aaa = true          # Fix structure first
drill_sergeant_auto_detect_markers = true  # Auto-add markers where possible

๐ŸŽจ Environment Variables (For the DevOps Heroes)

Control the Drill Sergeant from your environment like a puppet master:

# Turn the drill sergeant into a teddy bear
export DRILL_SERGEANT_ENABLED=false

# Make him extra mean about markers
export DRILL_SERGEANT_ENFORCE_MARKERS=true

# Demand War and Peace level descriptions
export DRILL_SERGEANT_MIN_DESCRIPTION_LENGTH=50

# Custom directory mappings for your special setup
export DRILL_SERGEANT_MARKER_MAPPINGS="widgets=unit,chaos=stress"

๐ŸŽญ AAA Synonym Recognition (Because Apparently You're All Special Snowflakes! โ„๏ธ)

The Problem: "You're Too Good for Military Vocabulary" ๐Ÿ˜ค

Oh, so the Drill Sergeant's perfectly good vocabulary isn't fancy enough for you? You can't be bothered to learn basic military terminology that's been battle-tested across thousands of codebases?

Let me guess:

  • "Arrange" is too corporate for your hip startup? ๐Ÿ™„
  • "Act" doesn't capture your artistic vision of test methodology? ๐ŸŽจ
  • "Assert" sounds too aggressive for your safe space codebase? ๐Ÿณ๏ธ

Fine. FINE! The Drill Sergeant will swallow his pride and learn your precious little words. But don't think for a second that lowering his standards to accommodate your delicate sensibilities makes him happy about it.

The Solution: Synonym Recognition (AKA "Participation Trophy Mode") ๐Ÿ†

Against his better judgment, the Sergeant can be taught new vocabulary. He'll grumble about it, but he'll do it. Because apparently "professional military standards" aren't good enough for you people.

Enable This Madness:

# pytest.ini - Enabling the coddling of your fragile vocabulary preferences
[tool:pytest]
drill_sergeant_aaa_synonyms_enabled = true  # *Heavy military sighing* ๐Ÿ˜ฎโ€๐Ÿ’จ

Built-in Synonyms (Because I Apparently Have to Do Everything for You):

Arrange Synonyms: Setup, Given, Prepare, Initialize, Configure, Create, Build "Setup? SETUP?! It's called ARRANGE! But sure, let's use baby words..."

Act Synonyms: Call, Execute, Run, Invoke, Perform, Trigger, When "I suppose 'When' is more gentle than 'Act'. Wouldn't want to trigger anyone..."

Assert Synonyms: Verify, Check, Expect, Validate, Confirm, Ensure, Then "Oh, we can't 'Assert' things anymore? Too confrontational? My mistake, let's 'gently verify'..."

Now These ALL Work! (God Help Us All) ๐ŸŽ‰

def test_user_authentication():
    # Setup user credentials and mock database
    user = User(username="test_user")

    # Call the authentication service
    result = auth_service.authenticate(user.username, "password123")

    # Verify successful authentication
    assert result.success is True

Drill Sergeant internal monologue: "'Setup'... 'Call'... 'Verify'... What's next, 'Pretty please test my code'? In my day, we had STANDARDS! But nooooo, everyone's a special butterfly with their own vocabulary..." ๐Ÿฆ‹

def test_bdd_style():
    # Given a valid shopping cart with items
    cart = ShoppingCart()
    cart.add_item("widget", price=10.00)

    # When calculating the total price
    total = cart.calculate_total()

    # Then the result should include tax
    assert total == 10.80  # 8% tax included

Drill Sergeant: "Oh look, BDD! 'Given/When/Then' - how PRECIOUS! Let me guess, you also use 'user stories' instead of requirements and call bugs 'opportunities for improvement'? Next you'll want me to validate your feelings instead of your code!" ๐Ÿ’…

Custom Synonyms (For Extra Special Snowflakes):

Oh, the built-in synonyms aren't unique enough for you? You need your OWN PERSONAL vocabulary? Of course you do. ๐Ÿ™„

# pytest.ini - Because you're just THAT special
drill_sergeant_aaa_arrange_synonyms = Background,Precondition,Setup
drill_sergeant_aaa_act_synonyms = Execute,Trigger,Action
drill_sergeant_aaa_assert_synonyms = Expect,Outcome,Result

"Let me guess - your team is 'different' and 'innovative' and needs custom words to express your unique testing philosophy? Can't just use the same words as literally everyone else in the industry?"

def test_with_custom_vocabulary():
    # Background - Configure the test environment
    api_client = APIClient(base_url="https://test.api.com")

    # Execute - Trigger the user creation endpoint
    response = api_client.create_user({"name": "John", "email": "john@test.com"})

    # Expect - Result should be successful user creation
    assert response.status_code == 201
    assert response.json()["user"]["name"] == "John"

Drill Sergeant: "'Background'? 'Execute'? 'Expect'? What are you, writing poetry? It's a TEST, not a haiku! But fine, I'll learn your artisanal vocabulary. Just don't expect me to like it." ๐Ÿ“

Environment Variable Control (For the Control Freaks):

Because apparently config files are too mainstream for you? You need ENVIRONMENT VARIABLES? ๐ŸŒ

# Enable this circus of accommodation
export DRILL_SERGEANT_AAA_SYNONYMS_ENABLED=true

# Reject all my hard work and use only your precious custom words
export DRILL_SERGEANT_AAA_BUILTIN_SYNONYMS=false

# Define your team's "unique" vocabulary (eye roll intensifies)
export DRILL_SERGEANT_AAA_ARRANGE_SYNONYMS="Setup,Given,Background"
export DRILL_SERGEANT_AAA_ACT_SYNONYMS="When,Call,Execute"
export DRILL_SERGEANT_AAA_ASSERT_SYNONYMS="Then,Verify,Check"

"Oh sure, let's make it even MORE complicated! Why have one place to configure things when you can have seventeen different ways? This is why we can't have nice things." ๐Ÿคฆโ€โ™‚๏ธ

Backward Compatibility (Because I'm Not a Complete Monster): โœ…

Look, I may be bitter about this whole "synonym accommodation" situation, but I'm not going to break your existing code. I have SOME integrity left.

  • Default: DISABLED - Because I refuse to enable this madness by default
  • Original keywords always work - "Arrange/Act/Assert" will NEVER be deprecated (unlike my dignity)
  • Explicit opt-in - You have to ASK for this nonsense explicitly
  • Layered configuration - Environment variables override pytest.ini because apparently you need 47 ways to configure everything

The Drill Sergeant's Reluctant Promise: "Fine, I'll learn your fancy words. BUT - and this is a big BUT - whether you say 'Arrange', 'Setup', 'Given', or 'Pretty-please-configure-my-test', you WILL STILL write descriptive comments! I may have bent on vocabulary, but I will NEVER compromise on quality! Got it, recruit?!" ๐ŸŽ–๏ธ

Mutters under breath: "Setup... Given... what's next, 'Lovingly prepare the test environment'? Kids these days..." ๐Ÿ˜ค

๐ŸŽฏ Advanced Usage (Graduate Level)

Custom Test Structure

# tests/widgets/test_widget_factory.py
# Using the custom mapping from above: DRILL_SERGEANT_MARKER_MAPPINGS="widgets=unit"

def test_widget_creation_with_custom_colors():  # No marker needed! Auto-detected as @pytest.mark.unit
    """Test widget factory creates widgets with specified colors."""
    # Arrange - Prepare the widget factory and color specifications
    factory = WidgetFactory()
    desired_color = Color.NEON_PINK
    expected_widget_count = 1

    # Act - Request widget creation with custom color
    created_widgets = factory.create_widgets(
        count=expected_widget_count,
        color=desired_color
    )

    # Assert - Verify widget meets specifications
    assert len(created_widgets) == expected_widget_count
    assert created_widgets[0].color == desired_color
    assert created_widgets[0].is_properly_initialized()

Complex AAA with Sub-sections

@pytest.mark.integration
def test_user_authentication_flow():
    """Test complete user authentication including edge cases."""
    # Arrange - Set up test environment and dependencies
    # Database setup
    test_db = create_test_database()
    user_service = UserService(test_db)

    # Test user data
    valid_email = "test@example.com"
    valid_password = "SecurePassword123!"

    # Mock external services
    email_service = Mock(spec=EmailService)

    # Act - Execute the authentication flow
    registration_result = user_service.register_user(
        email=valid_email,
        password=valid_password,
        email_service=email_service
    )

    # Assert - Verify all expectations are met
    # Registration success
    assert registration_result.success is True
    assert registration_result.user_id is not None

    # Database state
    stored_user = test_db.get_user_by_email(valid_email)
    assert stored_user is not None
    assert stored_user.email == valid_email

    # External service interactions
    email_service.send_welcome_email.assert_called_once()

๐ŸŽช Troubleshooting (When Things Go Wrong)

"The Drill Sergeant is Too Mean!"

Problem: Every test fails with quality violations. Solution: Your tests actually ARE garbage. Fix them or lower the standards:

drill_sergeant_enforce_aaa = false  # Give up on structure
drill_sergeant_min_description_length = 1  # Accept "a" as description

"Auto-detection Isn't Working!"

Problem: Tests in tests/unit/ aren't getting @pytest.mark.unit. Solution: Check your directory structure, genius:

# Wrong (no auto-detection)
tests/
  random_stuff/
    test_unit_something.py

# Right (auto-detects @pytest.mark.unit)
tests/
  unit/
    test_something.py

"I Don't Want Markers!"

Problem: You hate organization and progress. Solution: Turn off marker enforcement:

drill_sergeant_enforce_markers = false

"The Error Messages Are Too Verbose!"

Problem: You don't like helpful feedback. Solution: There is no solution. Embrace the verbosity. Learn from it. Grow as a developer.

๐ŸŽ–๏ธ Contributing (Join the Army)

Want to make the Drill Sergeant even more ruthless? We accept contributions!

Development Setup

# Clone the repo (obviously)
git clone https://github.com/jeffrichley/pytest-drill-sergeant.git
cd pytest-drill-sergeant

# Install with development dependencies
uv sync

# Run tests (they better all pass!)
just test

# Check quality (no excuses for sloppy code)
just quality

# See all available commands
just --list

Development Commands

just test          # Run all tests
just test-unit     # Run only unit tests
just test-integration  # Run integration tests
just lint          # Check code style
just type-check    # Verify type annotations
just quality       # Run all quality checks
just clean         # Clean up generated files

๐Ÿ“Š Why This Plugin Exists

Real talk: I got tired of reviewing pull requests where tests looked like someone threw code at a wall and hoped it stuck. Tests without markers, no structure, comments like # test stuff - it was chaos.

The Drill Sergeant fixes this by:

  1. Making quality automatic - Can't forget markers if they're added automatically
  2. Teaching good habits - Clear error messages explain what quality looks like
  3. Enforcing standards - No more "we'll fix it later" (spoiler: later never comes)
  4. Being helpful - Auto-detection means less work for developers who do things right

๐ŸŽฏ Philosophy

  • Quality is not negotiable - Your tests represent your code quality
  • Structure creates clarity - AAA pattern makes tests readable and maintainable
  • Consistency enables scale - Markers and structure let teams work together
  • Automation prevents regression - What can be automated should be automated

๐Ÿ”ฎ Future Features (Coming Soonโ„ข)

  • ๐ŸŽจ Custom AAA patterns - Define your own test structure requirements
  • ๐Ÿ“Š Quality metrics - Dashboard showing test quality across your codebase
  • ๐Ÿค– AI-powered suggestions - Smart recommendations for test improvements
  • ๐Ÿ”— IDE integration - Real-time quality feedback while you type
  • ๐Ÿ“ˆ Historical tracking - See how your test quality improves over time

๐Ÿ“œ License

MIT License - Because sharing is caring, and good test quality should be available to everyone.

๐ŸŽฏ Final Words

The Drill Sergeant doesn't exist to make your life harder. He exists to make your tests BETTER.

Better tests = Better code = Better software = Better world.

Now drop and give me 20 properly structured test cases! ๐Ÿ’ช


Made with โค๏ธ (and a healthy dose of sarcasm) by developers who care about test quality.

P.S. - If you think this plugin is too strict, wait until you meet Production. The Drill Sergeant is just trying to prepare you for that harsh reality. ๐Ÿ˜ˆ

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pytest_drill_sergeant-0.2.0.tar.gz (35.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pytest_drill_sergeant-0.2.0-py3-none-any.whl (28.6 kB view details)

Uploaded Python 3

File details

Details for the file pytest_drill_sergeant-0.2.0.tar.gz.

File metadata

  • Download URL: pytest_drill_sergeant-0.2.0.tar.gz
  • Upload date:
  • Size: 35.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for pytest_drill_sergeant-0.2.0.tar.gz
Algorithm Hash digest
SHA256 4d2dfaede7e838e89959126aca870a41b4cd12aa4c0be2dc80439100a969c6c4
MD5 9b28bda77548cffb0ba4628f72b5d1e7
BLAKE2b-256 1adb6932efbbfb150d0480dc254ce3d9e0eba8b795ad8ae7d72dd4bc2a4034db

See more details on using hashes here.

File details

Details for the file pytest_drill_sergeant-0.2.0-py3-none-any.whl.

File metadata

File hashes

Hashes for pytest_drill_sergeant-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 708398065dfbafb8a7eac94f643ca06f386918c0fb96d43aec20ba4080d43497
MD5 ca6da74c9b08520ff396ae480599cf91
BLAKE2b-256 4967899006a7625ed532b06c83f38a7af6d2f7277acb0e25a161cf601f2df116

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page