如何测试 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。
我有一个有金币的游戏。有一个 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。