Closed gaufung closed 1 month ago
对于单元测试,通常的做法是将一些外部依赖抽象成接口,这样可以通过接口不同的实现来进行单元测试。但是 C#
除了接口,还可以通过其他方式完成单元测试:
C# 中 Virtual
关键字用来表示该属性或者方法可以在子类中进行重载,所以在单元测试中可以构造新的子类来重写部分方法。
public class NumbersRepository
{
private readonly int[] _allNumbers;
public NumbersRepository(){
_allNumbers = Enumerable.Range(0, 100).ToArray();
}
public virtual IEnumerable<int> GetNumbers() => Random.Shared.GetItems(_allNumbers, 50);
}
public class NumbersSearchService
{
private readonly NumbersRepository _repository;
public NumbersSearchService(NumbersRepository repository) {
_repository = repository;
}
public bool Contains(int number){
var numbers = GetNumbers();
return numbers.Contains(number);
}
public IEnumerable<int> GetNumbers() => _repository.GetNumbers();
}
// 单元测试
internal class StubNumberRepo : NumbersRepository
{
private IEnumerable<int> _numbers;
public void SetNumbers(params int[] numbers) => _numbers = numbers;
public override IEnumerable<int> GetNumbers() => _numbers;
}
[TestMethod]
public void Should_WorkWithStubRepo() {
// Arrange
var repository = new StubNumberRepo();
repository.SetNumbers(1, 2, 3);
var service = new NumbersSearchService(repository);
// Act
var result = service.Contains(3);
// Assert
Assert.AreEqual(result, true);
}
C# 中 new
关键字也能隐藏父类的方法和属性,这样我们只需要测试其他内容就可以完成单元测试
public class NumbersSearchService {
private readonly NumbersRepository _repository;
public NumbersSearchService(NumbersRepository repository) {
_repository = repository;
}
public bool Contains(int number) {
var numbers = GetNumbers();
return numbers.Contains(number);
}
public IEnumerable<int> GetNumbers() => _repository.GetNumbers();
}
internal class StubNumberSearch : NumbersSearchService {
private IEnumerable<int> _numbers;
private bool _useStubNumbers;
public void SetNumbers(params int[] numbers) {
_numbers = numbers.ToArray();
_useStubNumbers = true;
}
public new IEnumerable<int> GetNumbers() => _useStubNumbers ?
_numbers:base.GetNumbers();
}
在单元你测试中只需要测试 StubNumberSearch
即可。
Moq
是 .NET
社区广泛使用的单元测试框架,使用 Moq
可以很方便地构造测试子类
public class NumbersRepository {
private readonly int[] _allNumbers;
public NumbersRepository() {
_allNumbers = Enumerable.Range(0, 100).ToArray();
}
public virtual IEnumerable<int> GetNumbers() => Random.Shared.GetItems(
_allNumbers, 50);
}
[TestMethod]
public void Should_WorkWithMockRepo() {
// Arrange
var repository = new Moq.Mock<NumbersRepository>();
repository.Setup(_ => _.GetNumbers()).Returns(new int[]{1, 2, 3});
var service = new NumbersSearchService(repository.Object);
// Act
var result = service.Contains(3);
// Assert
Assert.AreEqual(result, true);
}
https://www.code4it.dev/blog/unit-tests-without-interfaces/