gpt4 book ai didi

c# - 使用 Moq 的 ElasticClient 的代码覆盖率

转载 作者:行者123 更新时间:2023-12-02 23:14:55 24 4
gpt4 key购买 nike

我正在尝试通过单元测试分析代码覆盖率,我目前正在使用 Moq 库执行单元测试,不知何故我走错了路,想知道以下场景是否适用于使用 Moq

下面是一段代码

public interface ISearchWorker
{
void DeleteIndex(string indexName);

T GetClient<T>();
}

public class ElasticSearchWorker : ISearchWorker
{
public void DeleteIndex(string indexName)
{
IElasticClient elasticClient = GetClient<IElasticClient>();

if (elasticClient.IndexExists(indexName).Exists)
{
_ = elasticClient.DeleteIndex(indexName);
}
}

public T GetClient<T>()
{
string nodeList = "http://localhost:9200/";

List<Node> nodes = nodeList.Split(',').Select(uri => new Node(new Uri(uri))).ToList();

IConnectionPool sniffingConnectionPool = new SniffingConnectionPool(nodes);

IConnectionSettingsValues connectionSettings = new ConnectionSettings(sniffingConnectionPool);

return (T)(IElasticClient)new ElasticClient(connectionSettings);
}
}


下面是单元测试的代码片段


[TestClass]
public class SearchTestClass
{
private ISearchWorker searchWorker;

private Mock<ISearchWorker> searchWorkerMoq;

private readonly string indexName = "testIndex";

[TestInitialize]
public void SetupElasticClient()
{
searchWorkerMoq = new Mock<ISearchWorker>();

var elasticClient = new Mock<IElasticClient>();

searchWorkerMoq.Setup(c => c.GetClient<IElasticClient>()).Returns(elasticClient.Object).Verifiable();

searchWorker = searchWorkerMoq.Object;
}

[TestMethod]
public void DeleteIndexTest()
{
try
{
searchWorker.DeleteIndex(indexName);

searchWorkerMoq.Verify(c => c.GetClient<IElasticClient>(), Times.Once());
}
catch (System.Exception)
{
throw;
}
}
}


线

searchWorkerMoq.Verify(c => c.GetClient<IElasticClient>(), Times.Once());


抛出以下异常
(Moq.MockException: '
Expected invocation on the mock once, but was 0 times: c => c.GetClient<IElasticClient>())


阅读大多数与 Moq 相关的信息,这似乎不是执行 Moq 测试的合适方式,IElasticClient 对象应在外部提供给 ElasticSearchWorker 类

不从外部注入(inject)提供 IElasticClient 对象的原因是我们计划为另一个搜索提供程序(Azure Search)实现 ISearchWorker,因此希望将客户端实体包含在实现 ISearchWorker 接口(interface)的类中

想知道是否有更好的方法来执行此测试,以及我们将如何实现此场景的代码覆盖率。

最佳答案

因此,我假设您理解为什么这不起作用,并且您只是在寻求解决此问题的“好方法”。

现在我并不是说这是最好的方法,但它可能是最快的,同时仍然是“干净的”。

应用界面隔离(SOLID 中的“I”)。制作两个接口(interface)而不是一个,然后在稍后阶段实现它们。

// Don't have a C# IDE with me, so sorry if I leave some syntax errors. 
public interface ISearchClientProvider
{
T GetClient<T>();
}

public interface ISearchWorker
{
void DeleteIndex(string indexName);
}

public class ElasticSearchWorker : ISearchWorker{

private readonly ISearchClientProvider _clientProvider;

public ElasticSearchWorker(ISearchClientProvider clientProvider){
_clientProvider = clientProvider;
}

public void DeleteIndex(string indexName)
{
var elasticClient = _clientProvider.GetClient<IElasticClient>();

if (elasticClient.IndexExists(indexName).Exists)
{
_ = elasticClient.DeleteIndex(indexName);
}
}
}

public class ElasticSearchClientProvider : ISearchClientProvider{/*some implementation*/}
public class AzureSearchWorker : ISearchWorker{/*some implementation*/}
public class AzureSearchClientProvider : ISearchClientProvider{/*some implementation*/}

那么测试代码应该是这样的:
// would actually prefer to name it ElasticSearchWorkerTests
[TestClass]
public class SearchTestClass
{
private readonly ElasticSearchWorker _searchWorker;
private readonly ISearchClientProvider _elasticClientProvider;

private readonly string indexName = "testIndex";

// would prefer to name it SetupElasticSearchWorker
[TestInitialize]
public void SetupElasticClient()
{
var elasticClient = new Mock<IElasticClient>();
// Setup for IElasticClient.IndexExists() function:
// I don't know what is the return type of IndexExists,
// so I am assuming here that it is some dynamic Object
elasticClient.Setup(c => c.IndexExists(indexName)).Returns(new {Exists = true});
// Setup for IElasticCleint.DeleteIndex might also be necessary here.

_elasticClientProvider = new Mock<ISearchClientProvider>();
_elasticClientProvider.Setup(c => c.GetClient<IElasticClient>()).Returns(elasticClient.Object).Verifiable();

_searchWorker = new ElasticSearchWorker(_elasticClientProvider);
}

// would prefer to name it DeleteIndexTest_GetsSearchClient,
// because the function does more than is checked here, e.g., Checks index, deletes index.
[TestMethod]
public void DeleteIndexTest()
{
try
{
searchWorker.DeleteIndex(indexName);

searchWorkerMoq.Verify(c => c.GetClient<IElasticClient>(), Times.Once());
}
catch (System.Exception)
{
throw;
}
}
}

这样本次测试就没有http请求了。

一般来说(可以说是固执己见的部分),如果您希望您的代码更具单元测试性:
  • 使用Ports and Adapters (六边形,或者你想怎么称呼它)架构
  • 经验法则。不要在其他公共(public)方法中调用公共(public)方法。使用composition解决这个问题。我在这个例子中使用了合成。
  • 经验法则。不要在类中创建具有行为的对象。让您的composition root创建它们并将它们作为依赖项(接口(interface)的实现)注入(inject)到您的类中。如果无法在您的组合根目录中创建它们(也许您只在运行时知道您需要什么样的对象),那么请使用工厂模式。
  • 免责声明。您不需要 DI 容器即可使用组合根模式。您可以开始with poor man's DI .
  • 总会有不可单元测试的类,因为来自 3rd 方的一些依赖项是不可模拟的。您的目标是制作这些类(class) very small然后与他们进行一些集成测试。 (我相信,测试覆盖率工具也会用到)
  • 免责声明。我已经看到人们通过 HttpClientHandler 替换成功地模拟了 HttpClient。但是我还没有看到一个成功的 EF db 上下文模拟——它们都在某个时候失败了。
  • 笔记。人们普遍认为您应该 not mock interfaces you do not own .所以,从这个意义上说,我上面的解决方案也不干净,将来可能会给你带来麻烦。
  • 关于c# - 使用 Moq 的 ElasticClient 的代码覆盖率,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56489114/

    24 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com