You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Spring4D模拟方法时出现疑似内存泄漏问题

问题:Spring4D模拟IFileManager时FastMM检测到TIndexWrapper内存泄漏

我有一个类TMyClass,其中注入了接口IFileManager。该接口包含方法GetCompanyWorkbook(const ACompanyId: System.Integer; const AStream: TStream),此方法会根据ACompanyId填充AStream。真实代码运行一切正常,但在使用Spring4D框架对TMyClass进行单元测试并模拟IFileManager时,FastMM报告存在内存泄漏:13-20 bytes: TIndexWrapper x 1。我使用的是仓库main/master分支上的最新Spring4D 1.26版本。

以下是完整代码:

unit Unit1.pas

interface
  DUnitX.TestFramework,
  Spring.Mocking;

type
  IFileManager = interface (IInvokable)
   
   procedure GetCompanyWorkbook(const ACompanyId: System.Integer; const AStream: TStream);
  end;

  TMyClass = class
  strict private
    FFileManager: IFileManager;
  
  public
    constructor Create(const AFileManager: IFileManager);
    procedure GenerateInvoice(const ACompanyId: System.Integer);
  end;

  [TestFixture]
  TMyClassTests = class
  strict private
    FMockStream: TStream;
    FMyClass: TMyClass;
    FFileManager: Mock<IFileManager>;

    procedure SetupFileManagerMock();
    procedure InitMockStream(const AMockFile: string);

  public
    [Setup]
    procedure Setup();

    [TearDown]
    procedure TearDown();

    [TestCase('Test invoice generation', '2|invoice_2023.xls', '|')]
    procedure TestGenerateInvoice(const ACompanyId: System.Integer; const AMockFile: string);
  end;

implementation

uses
  System.Classes,
  Spring;

constructor TMyClass.Create(const AFileManager: IFileManager);
begin
  Guard.CheckNotNull(AFileManager, 'AFileManager');
  inherited Create();
  Self.FFileManager := AFileManager;
end;

procedure TMyClass.GenerateInvoice(const ACompanyId: System.Integer);
begin
  var sTmpFile := Self.GetTempInvoiceFile(ACompanyId);
  var fs := TFileStream.Create(sTmpFile, fmCreate);
  try
    Self.FFileManager.GetComparyWorkbook(ACompanyId, fs);
    // Do some operations with stream
  finally
    fs.Free();
  end;
end;

procedure TMyClassTests.Setup();
begin
  Self.FMockStream := nil;
  Self.FMyClass := TMyClass.Create(Self.FFileManager);
end;

procedure TMyClassTests.TearDown();
begin
  Self.FMyClass.Free();
  Self.FMockStream.Free();
end;

procedure TMyClassTests.InitMockStream(const AMockFile: string);
begin
  Self.FMockStream := TFileStream.Create(AMockFile, fmOpenRead);
end;

procedure TMyClassTests.SetupFileManagerMock();
begin
  Self.FFileManager.Setup.Executes(
    function(const callInfo: TCallInfo): TValue
    begin
      callInfo.Args[1].AsType<TStream>.CopyFrom(Self.FMockStream);
    end)
    .When(Args.Any)
    .GetCompanyWorkbook(Arg.IsAny<System.Integer>, Arg.IsAny<TStream>);
end;

procedure TMyClassTests.TestGenerateInvoice(const ACompanyId: System.Integer; const AMockFile: string);
begin
  Self.InitMockStream(AMockFile);
  Self.SetupFileManagerMock();
  Assert.WillNotRaiseAny(
    procedure
    begin
      Self.FMyClass.GenerateInvoice(ACompanyId);
    end
   );
end;

问题原因与修复方案

核心原因

TIndexWrapper是Spring4D内部用于处理参数索引的辅助类,泄漏源于Mock的设置逻辑:

  1. 测试中同时使用When(Args.Any)和方法参数匹配,导致内部创建的索引包装对象未被正确释放
  2. 匿名函数对类成员FMockStream的引用,可能干扰Spring4D的Mock资源生命周期管理
  3. 代码中存在笔误:TMyClass.GenerateInvoice里调用的GetComparyWorkbook应为GetCompanyWorkbook(少了一个'n'),虽不直接导致泄漏,但可能影响测试逻辑

修复步骤

1. 修正方法名拼写错误

procedure TMyClass.GenerateInvoice(const ACompanyId: System.Integer);
begin
  var sTmpFile := Self.GetTempInvoiceFile(ACompanyId);
  var fs := TFileStream.Create(sTmpFile, fmCreate);
  try
    Self.FFileManager.GetCompanyWorkbook(ACompanyId, fs); // 修正拼写错误
    // Do some operations with stream
  finally
    fs.Free();
  end;
end;

2. 调整Mock设置逻辑,移除冗余匹配条件

procedure TMyClassTests.SetupFileManagerMock();
begin
  Self.FFileManager.Setup.Executes(
    function(const callInfo: TCallInfo): TValue
    begin
      callInfo.Args[1].AsType<TStream>.CopyFrom(Self.FMockStream);
      Self.FMockStream.Position := 0; // 重置流位置,确保后续可重复读取
    end)
    .GetCompanyWorkbook(Arg.IsAny<System.Integer>, Arg.IsAny<TStream>);
    // 移除.When(Args.Any),避免重复匹配逻辑导致的资源泄漏
end;

3. 显式重置Mock释放内部资源

在TearDown方法中添加Mock重置操作,确保内部辅助对象被正确释放:

procedure TMyClassTests.TearDown();
begin
  Self.FMyClass.Free();
  Self.FMockStream.Free();
  Self.FFileManager.Reset; // 显式重置Mock,释放内部资源
end;

备选方案:使用局部变量传递流对象

将MockStream改为测试方法内的局部变量,减少对类成员的引用,帮助Spring4D正确管理生命周期:

procedure TMyClassTests.TestGenerateInvoice(const ACompanyId: System.Integer; const AMockFile: string);
var
  mockStream: TStream;
begin
  mockStream := TFileStream.Create(AMockFile, fmOpenRead);
  try
    Self.FFileManager.Setup.Executes(
      function(const callInfo: TCallInfo): TValue
      begin
        callInfo.Args[1].AsType<TStream>.CopyFrom(mockStream);
        mockStream.Position := 0;
      end)
      .GetCompanyWorkbook(Arg.IsAny<System.Integer>, Arg.IsAny<TStream>);

    Assert.WillNotRaiseAny(
      procedure
      begin
        Self.FMyClass.GenerateInvoice(ACompanyId);
      end
     );
  finally
    mockStream.Free;
  end;
end;

内容的提问来源于stack exchange,提问作者urkainian

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 05:05:19