gpt4 book ai didi

c# - 证明我的契约(Contract)正在验证正确的事情

转载 作者:太空宇宙 更新时间:2023-11-03 12:53:12 25 4
gpt4 key购买 nike

我有这样一个界面:

[ContractClass(typeof(ContractStockDataProvider))]
public interface IStockDataProvider
{
/// <summary>
/// Collect stock data from cache/ persistence layer/ api
/// </summary>
/// <param name="symbol"></param>
/// <returns></returns>
Task<Stock> GetStockAsync(string symbol);

/// <summary>
/// Reset the stock history values for the specified date
/// </summary>
/// <param name="date"></param>
/// <returns></returns>
Task UpdateStockValuesAsync(DateTime date);

/// <summary>
/// Updates the stock prices with the latest values in the StockHistories table.
/// </summary>
/// <returns></returns>
Task UpdateStockPricesAsync();

/// <summary>
/// Determines the last population date from the StockHistories table, and
/// updates the table with everything available after that.
/// </summary>
/// <returns></returns>
Task BringStockHistoryCurrentAsync();

event Action<StockEventArgs> OnFeedComplete;
event Action<StockEventArgs> OnFeedError;
}

我有一个相应的合约类:

[ContractClassFor(typeof (IStockDataProvider))]
public abstract class ContractStockDataProvider : IStockDataProvider
{
public event Action<StockEventArgs> OnFeedComplete;
public event Action<StockEventArgs> OnFeedError;

public Task BringStockHistoryCurrentAsync()
{
return default(Task);
}

public Task<Stock> GetStockAsync(string symbol)
{
Contract.Requires<ArgumentException>(!string.IsNullOrWhiteSpace(symbol), "symbol required.");
Contract.Requires<ArgumentException>(symbol.Equals(symbol.ToUpperInvariant(), StringComparison.InvariantCulture),
"symbol must be in uppercase.");
return default(Task<Stock>);
}

public Task UpdateStockPricesAsync()
{
return default(Task);
}

public Task UpdateStockValuesAsync(DateTime date)
{
Contract.Requires<ArgumentOutOfRangeException>(date <= DateTime.Today, "date cannot be in the future.");
return default(Task);
}
}

我做了这样一个单元测试:

[TestClass]
public class StockDataProviderTests
{
private Mock<IStockDataProvider> _stockDataProvider;

[TestInitialize]
public void Initialize()
{
_stockDataProvider = new Mock<IStockDataProvider>();
}

[TestMethod]
[ExpectedException(typeof(ArgumentException))]
public async Task GetStockAsyncSymbolEmptyThrowsArgumentException()
{
//arrange
var provider = _stockDataProvider.Object;

//act
await provider.GetStockAsync(string.Empty);

//assert
Assert.Fail("Should have thrown ArgumentException");
}
}

根据我的阅读,这应该足以通过单元测试,但在执行时,单元测试因未抛出异常而失败。

我不是要测试合约功能,但我有兴趣测试验证逻辑以确保满足我对 IStockDataProvider 接口(interface)的具体实现的要求。

我做错了吗?我如何使用我的单元测试来验证我是否正确指定了我的输入?

更新

因此,虽然模拟接口(interface)和测试验证逻辑似乎不起作用,但我的具体类(不是从抽象类继承)在测试中正确地验证了输入。所以它可能只是在模拟中不受支持,尽管我不太清楚为什么。

最佳答案

您的模拟没有抛出异常的原因很简单。接口(interface)不能有方法。因此,您不能直接在接口(interface)上指定契约(Contract)。但是,你已经知道了这一点。这就是为什么您为接口(interface)创建了一个契约类(顺便说一下,它应该是一个私有(private)抽象类)。

因为您正试图模拟界面,所以模拟工具对契约(Contract)一无所知。所有模拟工具所做的就是查看接口(interface)的定义并创建一个代理 对象。 proxy 是一个替身,一个替身,它根本没有任何行为!现在,借助像 Moq 这样的库,您可以使用 Returns(It.Is.Any()) 等方法让这些代理具有行为。但是,此时这又将代理 变成了 stub 。此外,更重要的是,由于一个原因,这不适用于模拟库:代理 是在测试期间的运行时动态 创建的。因此,ccrewrite 不会“重写”代理

那么您将如何测试您是否为契约(Contract)指定了正确的条件?

例如,您应该创建一个名为 MyProjectName.Tests.Stubs 的新库。然后,您应该为该项目中的界面创建一个实际的 stub 对象实例。它不必详细说明。足以让您调用单元测试中的方法来测试合约是否按预期工作。哦,还有一件更重要的事情要让它起作用:启用 执行运行时契约(Contract)检查 在这个新创建的用于调试构建的 stub 项目上。否则,您创建的继承自您的接口(interface)的 stub 将不会使用契约进行检测。

在您的单元测试项目中引用这个新的 MyProjectName.Tests.Stubs 程序集。使用 stub 来测试您的接口(interface)。这是一些代码(请注意,我使用的是您帖子中的代码——所以如果契约(Contract)没有按预期工作,请不要怪我——修复你的代码;)):

// Your Main Library Project
//////////////////////////////////////////////////////////////////////

[ContractClass(typeof(ContractStockDataProvider))]
public interface IStockDataProvider
{
/// <summary>
/// Collect stock data from cache/ persistence layer/ api
/// </summary>
/// <param name="symbol"></param>
/// <returns></returns>
Task<Stock> GetStockAsync(string symbol);

/// <summary>
/// Reset the stock history values for the specified date
/// </summary>
/// <param name="date"></param>
/// <returns></returns>
Task UpdateStockValuesAsync(DateTime date);

/// <summary>
/// Updates the stock prices with the latest values in the StockHistories table.
/// </summary>
/// <returns></returns>
Task UpdateStockPricesAsync();

/// <summary>
/// Determines the last population date from the StockHistories table, and
/// updates the table with everything available after that.
/// </summary>
/// <returns></returns>
Task BringStockHistoryCurrentAsync();

event Action<StockEventArgs> OnFeedComplete;
event Action<StockEventArgs> OnFeedError;
}

// Contract classes should:
// 1. Be Private Abstract classes
// 2. Have method implementations that always
// 'throw new NotImplementedException()' after the contracts
//
[ContractClassFor(typeof (IStockDataProvider))]
private abstract class ContractStockDataProvider : IStockDataProvider
{
public event Action<StockEventArgs> OnFeedComplete;
public event Action<StockEventArgs> OnFeedError;

public Task BringStockHistoryCurrentAsync()
{
// If this method doesn't mutate state in the class,
// consider marking it with the [Pure] attribute.

//return default(Task);
throw new NotImplementedException();
}

public Task<Stock> GetStockAsync(string symbol)
{
Contract.Requires<ArgumentException>(
!string.IsNullOrWhiteSpace(symbol),
"symbol required.");
Contract.Requires<ArgumentException>(
symbol.Equals(symbol.ToUpperInvariant(),
StringComparison.InvariantCulture),
"symbol must be in uppercase.");

//return default(Task<Stock>);
throw new NotImplementedException();
}

public Task UpdateStockPricesAsync()
{
// If this method doesn't mutate state within
// the class, consider marking it [Pure].

//return default(Task);
throw new NotImplementedException();
}

public Task UpdateStockValuesAsync(DateTime date)
{
Contract.Requires<ArgumentOutOfRangeException>(date <= DateTime.Today,
"date cannot be in the future.");

//return default(Task);
throw new NotImplementedException();
}
}

// YOUR NEW STUBS PROJECT
/////////////////////////////////////////////////////////////////
using YourNamespaceWithInterface;

// To make things simpler, use the same namespace as your interface,
// but put '.Stubs' on the end of it.
namespace YourNamespaceWithInterface.Stubs
{
// Again, this is a stub--it doesn't have to do anything
// useful. So, if you're not going to use this stub for
// checking logic and only use it for contract condition
// checking, it's OK to return null--as you're not actually
// depending on the return values of methods (unless you
// have Contract.Ensures(bool condition) on any methods--
// in which case, it will matter).
public class StockDataProviderStub : IStockDataProvider
{
public Task BringStockHistoryCurrentAsync()
{
return null;
}

public Task<Stock> GetStockAsync(string symbol)
{
Contract.Requires<ArgumentException>(
!string.IsNullOrWhiteSpace(symbol),
"symbol required.");
Contract.Requires<ArgumentException>(
symbol.Equals(symbol.ToUpperInvariant(),
StringComparison.InvariantCulture),
"symbol must be in uppercase.");

return null;
}

public Task UpdateStockPricesAsync()
{
return null;
}

public Task UpdateStockValuesAsync(DateTime date)
{
Contract.Requires<ArgumentOutOfRangeException>(
date <= DateTime.Today,
"date cannot be in the future.");

return null;
}
}
}

// IN YOUR UNIT TEST PROJECT
//////////////////////////////////////////////////////////////////
using YourNamespaceWithInteface.Stubs

[TestClass]
public class StockDataProviderTests
{
private IStockDataProvider _stockDataProvider;

[TestInitialize]
public void Initialize()
{
_stockDataProvider = new StockDataProviderStub();
}

[TestMethod]
[ExpectedException(typeof(ArgumentException))]
public async Task GetStockAsyncSymbolEmptyThrowsArgumentException()
{
//act
await provider.GetStockAsync(string.Empty);

//assert
Assert.Fail("Should have thrown ArgumentException");
}
}

通过创建包含接口(interface) stub 实现的项目并在 stub 项目上启用执行运行时契约(Contract)检查,您现在可以在单元测试中测试契约(Contract)条件。

我还强烈建议您阅读一些有关单元测试和各种测试替身的角色的文章。有一次,我认为模拟、 stub 、假货不是一回事。好吧,是的,不是。答案有点微妙。不幸的是,像 MoQ 这样的库虽然很棒!但无济于事,因为它们往往会混淆您在使用这些库时在测试中实际使用的内容。同样,这并不是说它们没有帮助、没有用或没有用处——只是说您在使用这些库时需要准确了解您使用的是什么。我可以提出的建议是 xUnit Test Patterns .还有一个网站:http://xunitpatterns.com/ .

关于c# - 证明我的契约(Contract)正在验证正确的事情,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34823531/

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