Skip to content

Latest commit

 

History

History
413 lines (290 loc) · 9.3 KB

Testing-Guide.md

File metadata and controls

413 lines (290 loc) · 9.3 KB

XpressData Testing Guide

This guide explains how to run different types of tests in the XpressData project.

Test Organization

Tests are organized by feature area and follow standard xUnit conventions.

Test Categories

  • Datalayer Tests - Tests for database connection, query, and parameter handling
  • Expression Tests - Tests for SQL expression building and generation
  • Mapping Tests - Tests for entity mapping, field types, and attributes

Test Project Structure

XpressData.Test/
├── Datalayer/
│   ├── QueryParameterTests.cs       # Query parameter creation and handling
│   ├── QueryTests.cs                # Query building and SQL text generation
│   └── DatabaseInformationTests.cs  # Connection string and DB info parsing
├── Expressions/
│   ├── BuilderTests.cs              # Expression builder API tests
│   └── ExpressionOutputTests.cs     # SQL output generation tests
└── Mapping/
    └── FieldTypeTests.cs            # Field type SQL generation tests

Running Tests

Run All Tests

dotnet test

Run Tests for a Specific Namespace

dotnet test --filter "FullyQualifiedName~XpressData.Test.Datalayer"

Run Tests for a Specific Class

dotnet test --filter "FullyQualifiedName~BuilderTests"

Run a Specific Test

dotnet test --filter "FullyQualifiedName~Equal_WithNumericExpressions_ReturnsExpressionEqual"

Run Tests with Verbose Output

dotnet test --verbosity normal

Run Tests with Code Coverage

dotnet test --collect:"XPlat Code Coverage"

Test Categories by Feature

Datalayer Tests

Tests for core database components:

dotnet test --filter "FullyQualifiedName~XpressData.Test.Datalayer"

Key scenarios tested:

  • QueryParameter creation with various types
  • Query building with parameters
  • Connection string parsing
  • Database information validation

Expression Tests

Tests for SQL expression building:

dotnet test --filter "FullyQualifiedName~XpressData.Test.Expressions"

Key scenarios tested:

  • Field expressions with/without aliases
  • Value expressions for all data types
  • Comparison operators (Equal, NotEqual, MoreThan, LessThan, etc.)
  • Logical operators (And, Or, Not)
  • Null checks (IsNull, IsNotNull)
  • Collection expressions (In, NotIn)
  • Pattern matching (Like)
  • Arithmetic expressions (Add, Minus)
  • Assignment expressions for UPDATE statements
  • OrderBy chain building

Mapping Tests

Tests for entity and field mapping:

dotnet test --filter "FullyQualifiedName~XpressData.Test.Mapping"

Key scenarios tested:

  • Field type SQL generation
  • Value conversion to/from database
  • Null handling

Expression Interface Casting

When using the Builder API, explicit interface casting may be required for type safety:

// Numeric comparison - cast to IExpressionNumeric
IExpressionNumeric idField = Builder.Field(userMap.Id);
IExpressionNumeric value = Builder.Value(42);
var condition = Builder.Equal(idField, value);

// String comparison - cast to IExpressionString
IExpressionString nameField = Builder.Field(userMap.Name);
IExpressionString pattern = Builder.Value("%test%");
var likeCondition = Builder.Like(nameField, pattern);

// GUID comparison - cast to IExpressionGuid
IExpressionGuid guidField = Builder.Field(userMap.UniqueId);
IExpressionGuid guidValue = Builder.Value(Guid.NewGuid());
var guidCondition = Builder.Equal(guidField, guidValue);

Available Expression Interfaces

Interface Use Case
IExpressionNumeric int, long, decimal comparisons
IExpressionString String comparisons, LIKE
IExpressionGuid GUID comparisons
IExpressionDate Date-only comparisons
IExpressionDateTime DateTime comparisons
IExpressionLogical Boolean/logical operations
IExpressionComperable IN/NOT IN operations
IExpressionAny Null checks, assignments

Writing New Tests

Test Naming Convention

Follow the pattern: MethodName_Scenario_ExpectedResult

[Fact]
public void Equal_WithNumericExpressions_ReturnsExpressionEqual()
{
    // Arrange - Cast to appropriate interface
    IExpressionNumeric left = Builder.Field(_testIntField);
    IExpressionNumeric right = Builder.Value(42);

    // Act
    var result = Builder.Equal(left, right);

    // Assert
    Assert.NotNull(result);
    Assert.IsType<ExpressionEqual>(result);
}

Test Documentation

All test methods should have XML documentation:

/// <summary>
/// Verifies that Equal() with numeric expressions returns the correct type.
/// Note: Explicit cast to IExpressionNumeric is required.
/// </summary>
[Fact]
public void Equal_WithNumericExpressions_ReturnsExpressionEqual()

Continuous Integration

Tests are automatically run on every pull request. Ensure all tests pass before merging.

dotnet test --filter "FullyQualifiedName~HandlerOrderingTests"

Key scenarios tested:

  • Handlers execute in Order property sequence
  • Deterministic tie-breaking for same Order values
  • Multiple handlers all execute

Concurrency Tests

dotnet test --filter "FullyQualifiedName~ConcurrencyTests"

Key scenarios tested:

  • Multiple producers enqueueing concurrently
  • Context isolation under concurrent load
  • Thread-safe operations

Writing New Tests

Test Structure

Follow this pattern for new tests:

[Fact]
public async Task MethodName_Condition_ExpectedBehavior()
{
    // Arrange
    using var provider = TestServiceProviderBuilder.CreateWithXpressWork();
    await TestServiceProviderBuilder.StartQueueAsync(provider);

    try
    {
        // Act
        using var scope = TestServiceProviderBuilder.CreateScopeWithContext(
            provider, 
            TestContext.Create());
        var submitter = scope.ServiceProvider
            .GetRequiredService<IBackgroundWorkSubmitter<TestContext>>();

        // ... perform action ...

        // Assert
        Assert.NotNull(result);
    }
    finally
    {
        await TestServiceProviderBuilder.StopQueueAsync(provider);
    }
}

Creating Custom Test Providers

For tests with custom configuration:

var provider = TestServiceProviderBuilder.CreateWithXpressWork(services =>
{
    // Add additional test services
    services.AddSingleton<IMyTestService, MyTestService>();
});

Testing Scheduler Behavior

For scheduler tests, also start the scheduler:

var scheduler = provider.GetRequiredService<IBackgroundWorkScheduler<TestContext>>();
await ((IHostedService)scheduler).StartAsync(CancellationToken.None);

CI/CD Recommendations

For Pull Requests (Fast Feedback)

# Run all tests
dotnet test --verbosity minimal

Typical Duration: 5-10 seconds

For Main Branch (Comprehensive)

# Run all tests with coverage
dotnet test --collect:"XPlat Code Coverage" --results-directory ./coverage

IDE Integration

Visual Studio

  1. Open Test Explorer (Test > Test Explorer)
  2. Build solution to discover tests
  3. Run all or select specific tests
  4. Use filter to find specific tests

Visual Studio Code

  1. Install C# Dev Kit extension
  2. Open Testing view (beaker icon)
  3. Run tests from the tree view

JetBrains Rider

  1. Open Unit Tests window (View > Tool Windows > Unit Tests)
  2. Build to discover tests
  3. Right-click to run specific tests

Troubleshooting

Tests Timing Out

Symptom: Tests fail with timeout errors

Cause: Async operations not completing in time

Solution:

// Use explicit timeouts
await completion.Task.WaitAsync(TimeSpan.FromSeconds(10));

Context Not Found

Symptom: _contextAccessor.Context is null

Cause: Context not properly captured or rehydrated

Solution:

  1. Ensure scope is created with CreateScopeWithContext
  2. Verify scope factory is correctly implementing rehydration
  3. Check that context provider is registered

Handler Not Executing

Symptom: Work handlers don't run

Cause: Handler not registered or queue not started

Solution:

  1. Verify handler is registered with AddWorkHandler<TArgs, THandler>()
  2. Ensure queue is started before enqueueing
  3. Check completion task for exceptions

Scheduler Tests Flaky

Symptom: Scheduler tests occasionally fail

Cause: Timing-dependent tests sensitive to system load

Solution:

  1. Use longer intervals in tests (e.g., 100-200ms instead of 50ms)
  2. Add adequate delays after scheduling
  3. Use task completion signals instead of fixed delays where possible

Test Coverage Goals

Current Coverage Areas

  • ✅ ...
  • ✅ ...
  • ✅ ...

Future Test Enhancements

  • ...
  • ...
  • ...

Best Practices

DO

  • Always dispose service providers
  • Use try/finally to ensure queue is stopped
  • Test both success and failure paths
  • Test edge cases (null, empty, maximum values)
  • Use meaningful test names describing behavior

DON'T

  • Don't share state between tests
  • Don't use hard-coded delays for async operations
  • Don't test implementation details, test behavior
  • Don't ignore failed tests

See Also