如何测试 public 方法,该方法使用已定义的 getter 更改变量

How to test a public method that changes a variable with defined getter

我有一个有金币的游戏。有一个 Add() 方法可以更改硬币。我想测试下是否添加正确

public class CoinsService 
{
    public ReactiveProperty<long> Coins { get { return State.SaveGame.Coins; } }

    public int Add(int coins)
    {
         Coins.Value += coins;
         return coins;
    }
}

测试:

public class CoinServiceTests
{
     [Test]
     public void AddCoins_WhenCalled_AddsUpToTotalCoins()
     {
          var coinsService = new CoinsService();
          coinsService.Add(10);
          Assert.That(coinsService.Coins.Value, Is.EqualTo(10));
     }
}

我试过使用 NSubstitute 来替代 class:

var coinsService = Substitute.For<CoinsService>();

并像那样实例化 Coins 的新实例

coinsService.Coins.Returns(new ReactiveProperty<long>());

也喜欢

var coins = new ReactiveProperty<long>();
coinsService.Coins.Returns(coins);

我希望在执行上述任何操作后,我将能够检查硬币价值。 相反,我得到一个空引用对象异常,即 coinsService.Coins 对象为 null

澄清一下,空引用出现在行

public ReactiveProperty<long> Coins { get { return State.SaveGame.Coins; } }

您正在尝试测试 CoinsService,因为它执行加法。因此,您必须使用 real CoinsService 而不是模拟它。模拟适用于与您要测试的 class 协作的 classes。

查看您的代码,我明白了为什么您认为这应该有效...您有这行代码...

coinsService.Coins.Returns(new ReactiveProperty<long>());

这会导致每次访问时都会创建一个新的 Coins 属性,这就是您看到错误的原因。

我怀疑根本原因是 CoinsService 是一个很大的 class,具有很多功能,您不想实例化它只是为了测试添加硬币的能力.这导致想要嘲笑它。 (如果那不是真的,我们可以到此为止 - 不要嘲笑它!)

如果CoinsService是"too big to test",那么它需要以使用合作者的方式分解。例如,假设 Coins 是一个 class 而不是一个 long。它可以有一个 Add 方法... Add(long howmuch) 例如。

然后 CoinsService 稍微改变一下...

public int Add(int coins)
{
     Coins.Add(coins);
     return Coins.Value; // I believe your original return is in error
}

现在这使一切变得更加间接,但给您带来的好处是您可以通过测试硬币 class 来测试加法功能,而无需使用服务。

您还可以(并且应该)通过为硬币创建模拟并确保在调用 CoinService.Add 时调用其 Add 方法来测试服务本身。

不要mock任何对象都应该被测试。模拟实例用于某些事情 "I tested it, but I need a fake one for another test"

在你的情况下,State.SaveGame.Coins 应该被模拟而不是 CoinsService,因为 CoinsService 将被测试。

为了使 CoinsService 可测试,最常见的方法是重构 CoinsService 以接受 Dependency Injection container 并摆脱静态 State.

// An interface to provide Coins
    public interface ICoinProvider
    {
        ReactiveProperty<long> Coins{ get; }
    }

    public class CoinsService{

        // Ctor take the instance in it
        public CoinsService( ICoinProvider provider )
        {
            _CoinProvider = provider;
        }

        private ICoinProvider _CoinProvider;

        public CoinWrap Coins
        {
            get
            {
                return _CoinProvider.Coins;
            }
        }

        public int Add( int coins )
        {
            Coins.Value += coins;
            return coins;
        }
    }

然后模拟ICoinProvider

     [Test]
     public void AddCoins_WhenCalled_AddsUpToTotalCoins()
     {
          var mock = Substitute.For<ICoinProvider>();
          mock.Coins.Returns( new ReactiveProperty<long>( 10 ) );
          var coinsService = new CoinsService( mock );
          coinsService.Add(10);
          Assert.That(coinsService.Coins.Value, Is.EqualTo(10));
     }

如果您发现此重构很难完成,那么您可以阅读 Charlie 的 post。