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

TDD单元测试中如何向私有列表添加对象以测试方法与属性

解决TDD场景下私有交易列表的测试问题

嘿,很高兴看到你在实践TDD!针对你的问题,我们不需要直接操作私有列表来测试——TDD的核心是验证类的公共行为,而非内部状态。我们一步步来实现并测试你的Account类:

第一步:完善核心业务逻辑

首先,先实现AddTransaction方法和Balance属性的逻辑(假设Transaction类有一个Amount属性来表示交易金额):

public class Account 
{ 
    private readonly List<Transaction> _transactions = new List<Transaction>(); 

    public decimal Balance 
    { 
        get => _transactions.Sum(t => t.Amount);
    } 

    public void AddTransaction(Transaction transaction) 
    { 
        if (transaction == null)
            throw new ArgumentNullException(nameof(transaction), "交易不能为空");
            
        _transactions.Add(transaction); 
    }
}

// 配套的Transaction类示例
public class Transaction
{
    public decimal Amount { get; }

    public Transaction(decimal amount)
    {
        Amount = amount;
    }
}

第二步:编写TDD测试用例

我们用xUnit(你也可以用NUnit等其他测试框架)来验证Account的行为,完全不需要碰私有列表:

public class AccountTests
{
    [Fact]
    public void AddTransaction_IncreasesBalanceByTransactionAmount()
    {
        // 准备测试环境
        var account = new Account();
        var deposit = new Transaction(150.75m);

        // 执行操作
        account.AddTransaction(deposit);

        // 验证结果
        Assert.Equal(150.75m, account.Balance);
    }

    [Fact]
    public void AddMultipleTransactions_SumsAllAmountsForBalance()
    {
        var account = new Account();
        var transaction1 = new Transaction(50m);
        var transaction2 = new Transaction(-20m); // 模拟取款

        account.AddTransaction(transaction1);
        account.AddTransaction(transaction2);

        Assert.Equal(30m, account.Balance);
    }

    [Fact]
    public void AddNullTransaction_ThrowsArgumentNullException()
    {
        var account = new Account();

        // 验证抛出异常
        Assert.Throws<ArgumentNullException>(() => account.AddTransaction(null));
    }
}

第三步:Mocking的应用场景

如果Transaction类依赖外部服务(比如需要记录交易时间),我们可以用Moq这类框架Mock依赖,而不用关心具体实现:

修改Transaction类引入依赖

public interface IDateTimeProvider
{
    DateTime Now { get; }
}

public class Transaction
{
    public decimal Amount { get; }
    public DateTime Timestamp { get; }

    public Transaction(decimal amount, IDateTimeProvider dateTimeProvider)
    {
        Amount = amount;
        Timestamp = dateTimeProvider.Now;
    }
}

编写带Mock的测试

[Fact]
public void AddTransaction_RecordsExpectedTimestamp()
{
    // Mock时间服务
    var mockDateTime = new Mock<IDateTimeProvider>();
    var expectedTime = new DateTime(2024, 6, 1);
    mockDateTime.Setup(d => d.Now).Returns(expectedTime);

    var account = new Account();
    var transaction = new Transaction(100m, mockDateTime.Object);

    account.AddTransaction(transaction);

    // 特殊情况:如果需要验证私有列表中的交易(不推荐,优先测试行为),可以用反射
    var transactionsField = typeof(Account).GetField("_transactions", BindingFlags.NonPublic | BindingFlags.Instance);
    var transactions = (List<Transaction>)transactionsField.GetValue(account);

    Assert.Single(transactions);
    Assert.Equal(expectedTime, transactions[0].Timestamp);
}

关键提示

TDD的原则是测试行为而非内部细节,所以优先通过AddTransaction和Balance这些公共接口来验证逻辑,尽量避免直接操作私有列表。只有在极端场景下,才考虑用反射访问私有成员——这应该是最后的选择。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 03:37:19