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
相关产品推荐
相关产品推荐

