【发布时间】:2021-02-11 06:28:44
【问题描述】:
对于冗长的帖子,我深表歉意。我自己不喜欢看到那些,但我的问题是关于结构的,我认为所有的部分都需要问它。 不过,有趣的部分在底部,所以请随意向下滚动到问题。
这是一个控制器。我正在注入一个上下文和一个命令工厂。控制器返回从 Oracle 数据库读取的对象列表。
public class aController : ControllerBase
{
protected readonly IDB db;
public aController(IContext context, ICommandFactory factory)
{
db = IDB.dbFactory(context, factory);
}
[HttpGet]
public ActionResult<s> GetS()
{
return Ok(db.DbGetS());
}
}
目前没有注入的是持久化类。将有可管理数量的存储过程映射到模型,全部手动编码为规范。这个接口有一个工厂来构造一个实现(这样我可以在需要时模拟它),以及我们的数据检索方法。
public interface IDB
{
public static IDB dbFactory(
IContext context,
ICommandFactory factory)
{
return new DB(context, factory);
}
public S DbGetS();
}
这个类实现了接口。它有一个构造函数,将注入的项目传递给基构造函数,否则通过调用基类中的通用访问方法进行 Oracle 交互。
public class DB: dbBase, IDB
{
public DB(
IContext context,
ICommandFactory factory)
: base(context, factory)
{ }
public S DbGetS()
{
S s = new S();
IEnumerable<S> ss = GetData("proc-name");
return ss.SingleOrDefault();
}
}
然后,所有模型类都有一个基类,它使用泛型并完成繁重的工作。这非常简化。
public abstract class dbBase
{
private readonly IContext _context;
private readonly ICommandFactory _commandFactory;
protected delegate IEnumerable<T> ParseResult<T>(IDbCommand cmd);
protected dbBase(IContext context, ICommandFactory factory)
{
_context = context;
_commandFactory = factory;
}
protected IEnumerable<T> GetData<T>(string sproc)
{
IEnumerable<T> results = null;
var cmd = this._commandFactory.GetDbCommand(sproc, this._context);
// boilerplate code omitted that sets up the command and executes the query
results = parseResult<T>(cmd); // this method will read from the refCursor
return results;
}
private IEnumerable<T> parseResult<T>(IDbCommand cmd) where T : ModelBase, new()
{
// This cast is the problem:
OracleRefCursor rc = (OracleRefCursor)cmd.Parameters["aCursor"];
using (OracleDataReader reader = rc.GetDataReader())
{
while (reader.Read())
{
// code omitted that reads the data and returns it
这是应该测试控制器的单元测试:
public void S_ReturnsObject()
{
// Arrange
var mockFactory = new Mock<ICommandFactory>();
var mockContext = new Mock<IContext>();
var mockCommand = new Mock<IDbCommand>();
var mockCommandParameters = new Mock<IDataParameterCollection>();
mockCommandParameters.SetupGet(p => p[It.IsAny<string>()]).Returns(mockParameter.Object);
// Set up the command and parameters
mockCommand.SetupGet(x => x.Parameters)
.Returns(mockCommandParameters.Object);
mockCommand.Setup(x => x.ExecuteNonQuery()).Verifiable();
// Set up the command factory
mockFactory.Setup(x => x.GetDbCommand(
It.IsAny<string>(),
mockContext.Object))
.Returns(mockCommand.Object)
.Verifiable();
var controller = new aController(mockContext.Object, mockFactory.Object);
// Act
var result = controller.GetS();
// omitted verification
所有存储过程都有包含结果的refCursor 输出参数。为此获得OracleDataReader 的唯一方法是将查询输出参数强制转换为OracleRefCursor。因此无法模拟阅读器,因为即使我可以获得模拟参数,测试也会因 ParseResult 方法中的强制转换异常而失败。除非我错过了什么。
我担心我需要删除 Oracle API 交互,尽管至少输入 parseResults() 作为测试的一部分会很好。
我可以注入 IDB 并将 DbGetS() 替换为模拟版本,但是我的测试不会覆盖太多代码,我将无法模拟任何数据库连接问题等。此外,大约有十几个 IDB 级别的接口都必须注入。
我应该如何重组它才能编写有意义的测试?
(免责声明:我在这里粘贴的代码sn-ps是为了说明目的,经过大量编辑。结果未经测试,不会编译或运行。)
【问题讨论】:
标签: c# oracle unit-testing moq ref-cursor