Skip to content

Instantly share code, notes, and snippets.

@ares
Last active November 20, 2025 14:45
Show Gist options
  • Select an option

  • Save ares/01c2005a3777b415fc0193ee54560cc2 to your computer and use it in GitHub Desktop.

Select an option

Save ares/01c2005a3777b415fc0193ee54560cc2 to your computer and use it in GitHub Desktop.

CRUD Operation Analysis: Unit Tests vs Integration Tests

Executive Summary

Analysis of 2,376 tests reveals that 24.2% (576 tests) are single-operation tests that only create, update, or delete a resource and verify the result. However, 17.3% of these (121 tests) are actually integration tests due to their complexity.

Key Findings

1. Test Distribution by Operation Type

Test Type Count Percentage Classification
Single-Operation Tests 576 24.2% Unit-like
- CREATE only 325 13.7% Unit-like
- UPDATE only 135 5.7% Unit-like
- DELETE only 116 4.9% Unit-like
Multi-Operation Tests 709 29.8% Integration
- CREATE + UPDATE 336 14.1% Integration
- CREATE + DELETE 54 2.3% Integration
- Full CRUD 120 5.0% Integration
- Complex integration 199 8.4% Integration
Other 1,091 45.9% Mixed

2. Breakdown by Category

API Tests (877 total)

  • Single-Operation: 229 tests (26.1%)
    • CREATE only: 130 (14.8%)
    • UPDATE only: 66 (7.5%)
    • DELETE only: 33 (3.8%)
  • Multi-Operation: 285 tests (32.5%)
    • CREATE + UPDATE: 178 (20.3%)
    • CREATE + DELETE: 19 (2.2%)
    • Full CRUD: 36 (4.1%)
    • Complex: 52 (5.9%)

CLI Tests (925 total)

  • Single-Operation: 233 tests (25.2%)
    • CREATE only: 122 (13.2%)
    • UPDATE only: 50 (5.4%)
    • DELETE only: 61 (6.6%)
  • Multi-Operation: 206 tests (22.3%)
    • CREATE + UPDATE: 94 (10.2%)
    • CREATE + DELETE: 9 (1.0%)
    • Full CRUD: 35 (3.8%)
    • Complex: 68 (7.4%)

UI Tests (574 total)

  • Single-Operation: 114 tests (19.9%)
    • CREATE only: 73 (12.7%)
    • UPDATE only: 19 (3.3%)
    • DELETE only: 22 (3.8%)
  • Multi-Operation: 218 tests (38.0%)
    • CREATE + UPDATE: 64 (11.1%)
    • CREATE + DELETE: 26 (4.5%)
    • Full CRUD: 49 (8.5%)
    • Complex: 79 (13.8%)

3. Are Single-Operation Tests Really Unit Tests?

Analysis of 700 single-operation tests:

Operation Total Simple Unit-like Actually Integration % Integration
CREATE only 426 339 (79.6%) 87 (20.4%) 20.4%
UPDATE only 158 124 (78.5%) 34 (21.5%) 21.5%
DELETE only 116 116 (100.0%) 0 (0.0%) 0.0%
TOTAL 700 579 (82.7%) 121 (17.3%) 17.3%

Key Insight: While DELETE tests are always simple, ~20% of CREATE and UPDATE tests are actually integration tests because they involve:

  • Multiple entity types (>2)
  • Complex operations (sync, publish, promote, register)
  • Multiple dependencies/fixtures (>3)
  • Cross-system interactions

Examples

Example 1: Simple CREATE Test (True Unit Test)

def test_positive_create_unlimited_hosts(target_sat):
    """Create a plain vanilla activation key.
    
    :expectedresults: Check that activation key is created and its
        "unlimited_hosts" attribute defaults to true.
    """
    assert target_sat.api.ActivationKey().create().unlimited_hosts is True

Characteristics:

  • Single operation: .create()
  • Single entity type: ActivationKey
  • Single assertion
  • No external dependencies
  • Classification: Unit Test

Example 2: CREATE Test That's Actually Integration

def test_positive_repo_sync_publish_promote_cv(
    self, module_org, module_lce, repo, target_sat
):
    """Synchronize repository with SRPMs, add repository to content view
    and publish, promote content view
    
    :expectedresults: srpms can be listed in organization, content view, 
        Lifecycle env
    """
    repo.sync()  # External operation
    
    cv = target_sat.api.ContentView(
        organization=module_org, 
        repository=[repo]
    ).create()
    cv.publish()  # Complex operation
    cv = cv.read()
    
    cv.version[0].promote(data={'environment_ids': module_lce.id})  # Another operation
    
    # Multiple assertions across different systems...

Characteristics:

  • Multiple operations: sync, create, publish, promote
  • Multiple entity types: Repository, ContentView, LifecycleEnvironment
  • Multiple fixtures: module_org, module_lce, repo
  • Cross-system interactions
  • Classification: Integration Test (despite being labeled "create")

Example 3: Simple UPDATE Test (True Unit Test)

def test_positive_update_name(new_name, target_sat, module_org):
    """Create activation key providing the initial name, then update
    its name to another valid name.
    
    :expectedresults: Activation key is created, and its name can be updated.
    """
    act_key = target_sat.api.ActivationKey(organization=module_org).create()
    updated = target_sat.api.ActivationKey(
        id=act_key.id, 
        organization=module_org, 
        name=new_name
    ).update(['name'])
    assert new_name == updated.name

Characteristics:

  • Primary operation: .update()
  • Setup: minimal create
  • Single entity type
  • Single assertion
  • Classification: Unit Test (tests update functionality)

Example 4: CREATE+UPDATE Test (Integration)

def test_positive_create_and_update_with_name(target_sat):
    """Create and update a host with different names and minimal input parameters
    
    :expectedresults: A host is created and updated with expected name
    """
    name = gen_choice(datafactory.valid_hosts_list())
    host = target_sat.api.Host(name=name).create()
    assert host.name == f'{name}.{host.domain.read().name}'
    
    new_name = gen_choice(datafactory.valid_hosts_list())
    host.name = new_name
    host = host.update(['name'])
    assert host.name == f'{new_name}.{host.domain.read().name}'

Characteristics:

  • Multiple operations: create, read, update
  • Tests full lifecycle
  • Multiple assertions
  • Classification: Integration Test (tests create-update workflow)

Analysis: Should Single-Operation Tests Be Considered Integration Tests?

Arguments FOR Considering Them Integration Tests

  1. They Test Real System Behavior

    • Even "simple" CREATE tests interact with database, API layer, validation logic
    • Tests actual HTTP requests/responses (API) or command execution (CLI)
    • Involves multiple system components (controllers, models, database)
  2. They Have External Dependencies

    • Require running Satellite instance
    • Need database access
    • May require other entities (organization, location)
  3. They Test More Than One Thing

    • CREATE tests verify: validation, persistence, default values, relationships
    • UPDATE tests verify: validation, persistence, change detection
    • DELETE tests verify: cascade behavior, cleanup, constraints

Arguments AGAINST Considering Them Integration Tests

  1. They Test Single Operation

    • Focus on one CRUD operation
    • Don't test workflows or business processes
    • Don't test interactions between multiple operations
  2. They're Isolated

    • Don't depend on complex setup
    • Don't test cross-feature interactions
    • Can run independently
  3. They're Fast

    • Single API call or CLI command
    • Minimal setup/teardown
    • Quick execution

Conclusion: It Depends on Complexity

Simple single-operation tests (579 tests, 82.7%):

  • Classification: Functional/Component Tests
  • Not pure unit tests (involve real system)
  • Not integration tests (single operation, isolated)
  • Best described as: "Component-level functional tests"

Complex single-operation tests (121 tests, 17.3%):

  • Classification: Integration Tests
  • Involve multiple systems (sync, publish, promote)
  • Test cross-component workflows
  • Have complex dependencies

Implications for Test Reduction

1. Single-Operation Tests Are Valuable

Should NOT be considered duplicates even if multiple tests do "create":

  • Each tests different attributes/scenarios
  • Each tests different validation rules
  • Each tests different edge cases

Example: These are NOT duplicates:

test_positive_create_with_name()       # Tests name validation
test_positive_create_with_description() # Tests description validation
test_positive_create_with_lce()        # Tests lifecycle environment assignment

2. But Many Can Be Consolidated

When single-operation tests CAN be consolidated:

  • Testing same attribute with different values → parametrize
  • Testing same operation with different lookup methods → parametrize
  • Testing same validation with different invalid inputs → parametrize

Example: These CAN be consolidated:

# Before: 3 separate tests
test_positive_create_with_name_alpha()
test_positive_create_with_name_numeric()
test_positive_create_with_name_special()

# After: 1 parametrized test
@pytest.mark.parametrize('name', [alpha_name, numeric_name, special_name])
test_positive_create_with_name(name)

3. Multi-Operation Tests Are True Integration Tests

CREATE+UPDATE tests (336 tests):

  • Test full lifecycle workflows
  • Verify state transitions
  • Test operation sequences
  • Should be preserved - they provide unique coverage

Full CRUD tests (120 tests):

  • Test complete resource lifecycle
  • Verify cleanup and cascade
  • Test end-to-end workflows
  • Should be preserved - they're true E2E tests

Recommendations

1. Reclassify Tests

Create clear test categories:

# Component-level functional test (single operation)
@pytest.mark.component
def test_positive_create_with_name(name):
    """Test activation key creation with valid name."""
    ak = api.ActivationKey(name=name).create()
    assert ak.name == name

# Integration test (multiple operations)
@pytest.mark.integration
def test_positive_create_and_update_with_name(name):
    """Test activation key creation and update workflow."""
    ak = api.ActivationKey(name=name).create()
    assert ak.name == name
    
    new_name = gen_string('alpha')
    ak.name = new_name
    ak = ak.update(['name'])
    assert ak.name == new_name

# End-to-end test (full workflow)
@pytest.mark.e2e
def test_positive_full_lifecycle():
    """Test complete activation key lifecycle."""
    ak = api.ActivationKey().create()
    # ... multiple operations ...
    ak.delete()

2. Preserve Single-Operation Tests

Do NOT remove single-operation tests just because they're "simple":

  • They test important validation logic
  • They're fast and reliable
  • They provide clear failure signals
  • They document expected behavior

3. Consolidate Variations, Not Operations

Good consolidation:

# Before: 5 tests
test_create_with_name_alpha()
test_create_with_name_numeric()
test_create_with_name_utf8()
test_create_with_name_latin1()
test_create_with_name_long()

# After: 1 test
@pytest.mark.parametrize('name', [alpha, numeric, utf8, latin1, long_name])
test_create_with_name(name)

Bad consolidation:

# DON'T DO THIS - loses clarity
@pytest.mark.parametrize('operation', ['create', 'update', 'delete'])
def test_activation_key_operations(operation):
    # Too generic, hard to debug failures

4. Focus on Semantic Duplication

From previous analysis, the real duplication is:

  1. Tests with identical logic (150-200 tests)

    • Same operations, same assertions
    • Only difference is the attribute being tested
    • These should be consolidated
  2. Tests with similar patterns (300-400 tests)

    • Follow same workflow
    • Test same scenarios
    • These could be consolidated with parametrization

Summary Statistics

Test Distribution

Category Count % of Total Should Preserve?
Simple CREATE tests 339 14.3% ✓ Yes - test validation
Simple UPDATE tests 124 5.2% ✓ Yes - test updates
Simple DELETE tests 116 4.9% ✓ Yes - test deletion
Complex CREATE/UPDATE (integration) 121 5.1% ✓ Yes - integration tests
CREATE+UPDATE workflows 336 14.1% ✓ Yes - integration tests
Full CRUD workflows 120 5.0% ✓ Yes - E2E tests
Complex integration 199 8.4% ✓ Yes - integration tests
Tests with semantic duplication 150-200 6-8% Can consolidate

Reduction Potential

From single-operation tests:

  • Consolidate variations: ~50-80 tests
  • Parametrize lookup methods: ~30-40 tests
  • Total: 80-120 tests (3-5% reduction)

From multi-operation tests:

  • Consolidate similar workflows: ~40-60 tests
  • Total: 40-60 tests (2% reduction)

Combined with semantic duplication:

  • Total reduction potential: 150-200 tests (6-8%)
  • Tests remaining: 2,176-2,226 (maintaining full coverage)

Conclusion

  1. 576 tests (24.2%) are single-operation tests, but this is NOT a problem:

    • 82.7% are legitimate component-level tests
    • 17.3% are actually integration tests
    • They provide valuable, focused testing
  2. The real duplication is semantic, not operational:

    • 150-200 tests execute identical logic with minor variations
    • These can be consolidated through parametrization
    • This represents 6-8% reduction potential
  3. Multi-operation tests (709 tests, 29.8%) are true integration tests:

    • They test workflows and state transitions
    • They should be preserved
    • They provide unique coverage
  4. Recommendation: Focus on consolidating semantic duplicates, not reducing single-operation tests. The distinction between unit/integration matters less than whether tests provide unique value.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment