Skip to content

Refactor: Split Assert.That.cs (1522 lines) into focused partial-class files #8366

@Evangelink

Description

@Evangelink

Overview

The file src/TestFramework/TestFramework/Assertions/Assert.That.cs has grown to 1522 lines, making it difficult to navigate and maintain. It already lives in a partial class, which makes splitting it into logically grouped partial-class files straightforward without any breaking changes.

Current State

  • File: src/TestFramework/TestFramework/Assertions/Assert.That.cs
  • Size: 1522 lines
  • Language: C#
Structural Analysis

The file contains one public method (That) and 35 private static helpers organized around five distinct concerns:

  1. Public API (lines 1–125): The That(Expression<Func<bool>>) extension method and its supporting constants/sentinels.
  2. Expression evaluation (lines 126–481): EvaluateExpression, CreateEvaluationCache, RequiresSinglePassEvaluation, EvaluateAllSubExpressions, TryEvaluateShortCircuitBinary, TryEvaluateCoalesce, TryEvaluateConditional.
  3. Expression replacement/simplification (lines 483–611): ReplaceSubExpressionsWithConstants, ReplaceChildWithConstant.
  4. Detail/variable extraction (lines 580–874): ExtractDetails, ExtractVariablesFromExpression, HandleArrayIndexExpression, AddMemberExpressionToDetails, HandleMethodCallExpression, TryAddExpressionValue, TranslateFailureSentinel.
  5. Text formatting & cleaning utilities (lines 875–1522): GetCleanMemberName, GetIndexArgumentDisplay, IsVariableReference, IsSimpleExpression, FormatValue, CleanExpressionText, RemoveAnonymousTypeWrappers, CleanListInitializers, TryMatchListInitPattern, CleanTypeName, CleanParentheses, RemoveOuterParentheses, CleanExcessiveParentheses, RemoveCompilerGeneratedWrappers, TryRemoveWrapper, IsFuncOrActionType, CompilerGeneratedDisplayClassRegex.

Refactoring Strategy

Because AssertExtensions is already a partial class, no public API changes are needed — simply split the private helpers into new files in the same Assertions/ folder.

Proposed File Splits

  1. Assert.That.cs (keep, shrink to ~120 lines)

    • Contents: namespace/class declaration, constants, FailedToEvaluateSentinel, the public That extension method.
    • Responsibility: Public API surface — the single entry point for callers.
  2. Assert.That.ExpressionEvaluator.cs (new, ~360 lines)

    • Contents: EvaluateExpression, CreateEvaluationCache, RequiresSinglePassEvaluation, EvaluateAllSubExpressions, TryEvaluateShortCircuitBinary, TryEvaluateCoalesce, TryEvaluateConditional.
    • Responsibility: Recursively walk and evaluate LINQ expression trees.
  3. Assert.That.ExpressionReplacer.cs (new, ~130 lines)

    • Contents: ReplaceSubExpressionsWithConstants, ReplaceChildWithConstant.
    • Responsibility: Substitute evaluated sub-expressions back into the tree for display simplification.
  4. Assert.That.DetailExtractor.cs (new, ~295 lines)

    • Contents: ExtractDetails, ExtractVariablesFromExpression, HandleArrayIndexExpression, AddMemberExpressionToDetails, HandleMethodCallExpression, TryAddExpressionValue, TranslateFailureSentinel.
    • Responsibility: Walk the expression tree to collect variable names and runtime values for the failure message.
  5. Assert.That.ExpressionFormatter.cs (new, ~650 lines)

    • Contents: GetCleanMemberName, GetIndexArgumentDisplay, IsVariableReference, IsSimpleExpression, FormatValue, CleanExpressionText, RemoveAnonymousTypeWrappers, CleanListInitializers, TryMatchListInitPattern, CleanTypeName, CleanParentheses, RemoveOuterParentheses, CleanExcessiveParentheses, RemoveCompilerGeneratedWrappers, TryRemoveWrapper, IsFuncOrActionType, CompilerGeneratedDisplayClassRegex.
    • Responsibility: Convert raw expression text and runtime values into human-readable diagnostic strings.

Implementation Guidelines

  1. Preserve Behavior: All existing functionality must work identically after the split.
  2. Maintain Public API: AssertExtensions is partial — no signatures change, no new public members.
  3. No Import Changes Needed: All files share the same namespace (Microsoft.VisualStudio.TestTools.UnitTesting); no using updates required across the codebase.
  4. Test After Each Split: Run the MSTest unit-test suite after each incremental file move.
  5. One Group at a Time: Move one logical group per commit to keep diffs reviewable.

Acceptance Criteria

  • Original file is split into focused partial-class files (all in the same Assertions/ directory)
  • Each new file is under 700 lines (target: under 300)
  • All tests pass after refactoring
  • No breaking changes to the public API
  • IsFuncOrActionType and CompilerGeneratedDisplayClassRegex helpers stay private

Priority: Medium
Effort: Medium (35 private methods, no cross-assembly dependencies, all changes contained to one directory)
Expected Impact: Improved code navigability, easier targeted testing, reduced merge conflicts on the Assert.That area

Generated by Daily File Diet · ● 2.2M ·

Metadata

Metadata

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions