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

如何用DUnitX测试无构造函数的Delphi类?

问题分析与解决方案

一、当前类结构的可测试性问题

  • DoSomething是私有方法,无法直接在测试用例中调用,只能通过Execute触发,但Execute内部实例化的逻辑被硬编码封装,没法替换或监控
  • 如果DoSomething的逻辑依赖外部资源(比如数据库、文件)或修改全局状态,没有抽象层的话根本没法隔离依赖,验证结果全靠碰运气
  • 你遇到的TestCase编译错误,大概率是DUnitX对TMyRecord这种自定义记录类型的参数支持问题——要么是[TestCase]属性没法直接传记录,要么是测试方法的参数签名不匹配

二、重构建议(提升可测试性)

1. 让核心逻辑可直接访问

把DoSomething改成protected或者public(如果业务逻辑允许),或者提取接口,让核心逻辑脱离类方法的封装:

type
  IMyClassLogic = interface
    ['{GUID}']
    procedure DoSomething(const AData: TMyRecord);
  end;

  TMyClass = class(TInterfacedObject, IMyClassLogic)
  public
    procedure DoSomething(const AData: TMyRecord);
    class procedure Execute(const AData: TMyRecord);
  end;

implementation

class procedure TMyClass.Execute(const AData: TMyRecord);
var
  Logic: IMyClassLogic;
begin
  Logic := TMyClass.Create;
  Logic.DoSomething(AData);
end;

这样测试时可以直接创建实例调用DoSomething,不用绕Execute的实例化逻辑。

2. 隔离外部依赖

如果DoSomething依赖日志、数据库这类外部资源,必须把这些依赖抽象成接口,通过构造函数注入:

type
  ILogger = interface
    ['{GUID}']
    procedure Log(const Msg: string);
  end;

  TMyClass = class(TInterfacedObject, IMyClassLogic)
  private
    FLogger: ILogger;
  public
    constructor Create(const ALogger: ILogger);
    procedure DoSomething(const AData: TMyRecord);
    class procedure Execute(const AData: TMyRecord; const ALogger: ILogger); overload;
    class procedure Execute(const AData: TMyRecord); overload; // 保留原有接口,内部用默认实现
  end;

implementation

constructor TMyClass.Create(const ALogger: ILogger);
begin
  inherited Create;
  FLogger := ALogger;
end;

class procedure TMyClass.Execute(const AData: TMyRecord; const ALogger: ILogger);
var
  Logic: IMyClassLogic;
begin
  Logic := TMyClass.Create(ALogger);
  Logic.DoSomething(AData);
end;

class procedure TMyClass.Execute(const AData: TMyRecord);
begin
  Execute(AData, TDefaultLogger.Create); // 原有逻辑不变,兼容旧代码
end;

测试时传个Mock的ILogger,就能验证DoSomething是不是按预期调用了日志方法。

三、DUnitX测试用例编写

1. 解决TestCase编译错误

DUnitX的[TestCase]属性没法直接传自定义记录,改用[TestCaseSource]提供测试数据:

type
  TMyClassTest = class(TTestCase)
  private
    function GetTestData: TArray<TMyRecord>;
  public
    [TestCaseSource('GetTestData')]
    procedure TestDoSomething(const AData: TMyRecord);
  end;

implementation

function TMyClassTest.GetTestData: TArray<TMyRecord>;
begin
  SetLength(Result, 2);
  Result[0].Value := 10; // 假设TMyRecord有Value字段
  Result[1].Value := 20;
end;

procedure TMyClassTest.TestDoSomething(const AData: TMyRecord);
var
  MyClass: TMyClass;
  MockLogger: TMock<ILogger>;
begin
  // 初始化Mock依赖
  MockLogger := TMock<ILogger>.Create;
  MyClass := TMyClass.Create(MockLogger);
  try
    // 执行测试方法
    MyClass.DoSomething(AData);

    // 验证行为:检查MockLogger是否按预期被调用
    MockLogger.Received(TMockMode.ExactlyOnce).Log('处理数据: ' + IntToStr(AData.Value));
  finally
    MyClass.Free;
    MockLogger.Free;
  end;
end;

2. 验证DoSomething的行为

  • 状态验证:如果DoSomething修改了传入的记录(注意记录是值类型,得是var参数才会影响外部),或者修改了某个可观察对象,直接断言结果是否符合预期
  • 行为验证:依赖外部服务的话,用DUnitX自带的TMock验证方法调用次数、参数是否正确
  • 输出验证:如果逻辑有输出(比如写文件、返回值),直接检查输出内容是否符合预期

四、临时测试方案(不重构原有类)

如果暂时不想动原有代码,可以子类化TMyClass来暴露私有方法:

type
  TTestableMyClass = class(TMyClass)
  public
    procedure TestDoSomething(const AData: TMyRecord);
  end;

procedure TTestableMyClass.TestDoSomething(const AData: TMyRecord);
begin
  inherited DoSomething(AData);
end;

测试时创建TTestableMyClass实例,调用TestDoSomething即可。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 06:12:22