如何在Excel VSTO插件中模拟行?

14

我试图将一个包含数值单元格的模拟 Range 放置在新的 Range 的行中,但是当我尝试访问 Range 的特定元素时,会抛出异常。

我已经尝试了一切,有人知道我在这里做错了什么吗?

异常信息

Message: Test method xxx.MockUtilsTest.MockRowsTest threw exception: Microsoft.CSharp.RuntimeBinder.RuntimeBinderException: Cannot apply indexing with [] to an expression of type 'Castle.Proxies.RangeProxy'

测试用例

[TestMethod]
public void MockRowsTest()
{
    var row1 = MockUtils.MockCells("test_row_1", "test_row_1");
    var row2 = MockUtils.MockCells("test_row_2", "test_row_2");
    var range = MockUtils.MockRows(row1, row2);

    Assert.IsNotNull(range);
    Assert.AreEqual(2, range.Count);
    Assert.IsNotNull(range.Rows);
    Assert.AreEqual(2, range.Rows.Count);
    Assert.AreSame(row1, range.Rows[1].Cells[1]); // exception is thrown here
    Assert.AreSame(row2, range.Rows[2].Cells[1]);
    Assert.AreEqual("test_row_1", range.Rows[1].Cells[1].Value2);
    Assert.AreEqual("test_row_2", range.Rows[2].Cells[1].Value2);
}
MockUtils
public static Range MockCellValue2(Object value)
{
    var cell = new Moq.Mock<Range>();
    cell.Setup(c => c.Value2).Returns(value);

    return cell.Object;
}

public static Range MockCells(params Object[] values)
{
    var cells = new Moq.Mock<Range>();
    for (int i = 0; i < values.Length; i++)
    {
        var cell = MockCellValue2(values[i]);
        cells.SetupGet(c => c[i + 1, Moq.It.IsAny<Object>()]).Returns(cell);
    }

    var row = new Moq.Mock<Range>();
    row.SetupGet(r => r.Cells).Returns(cells.Object);
    row.SetupGet(r => r.Count).Returns(values.Length);

    return row.Object;
}

public static Range MockRows(params Range[] rows)
{
    var mergedRows = MergeRanges(rows);
    var range = new Moq.Mock<Range>();
    range.SetupGet(r => r.Count).Returns(rows.Length);
    range.SetupGet(r => r.Rows).Returns(() => mergedRows);
    range.Setup(r => r.GetEnumerator()).Returns(rows.GetEnumerator());

    return range.Object;
}

public static Range MergeRanges(params Range[] ranges)
{
    var range = new Moq.Mock<Range>();
    for (int i = 0; i < ranges.Length; i++)
    {
        range.SetupGet(r => r[i + 1, Moq.It.IsAny<Object>()]).Returns(ranges[i]);
    }

    range.SetupGet(r => r.Count).Returns(ranges.Length);
    range.Setup(r => r.GetEnumerator()).Returns(ranges.GetEnumerator());

    return range.Object;
}

1
MergeRanges(rows) 返回什么? - AlanT
@AlanT 抱歉回复晚了。我已经在我的问题中添加了 MergeRanges 的方法体,请看一下。 - tomwassing
@tomwassing 尝试使用非静态方法和类,将您的方法声明为虚拟的或实现/提取模拟工具类的接口,并使用该接口创建您的模拟。例如:mock<InterfaceMockUtils>(); - Mario Guadagnin
1个回答

11

Range的索引器返回一个动态对象,这就是你的问题所在。

enter image description here

Moq使用Castle Dynamic proxy生成假对象,Castle.Proxies.RangeProxy是在您的情况下生成的类。由于此对象不是COM对象,因此调用了C#运行时绑定器的处理。运行时绑定器解析类型并查找索引器方法,但由于生成的类没有它,因此解析失败。

解决您的问题最简单的方法是将索引器结果返回到严格的Range本地变量:

enter image description here

那么你的测试将会失败,因为range.Rows[1]等于row1...

所以请将你的测试代码更改为:

[TestMethod]
public void MockRowsTest()
{
    var row1 = MockUtils.MockCells("test_row_1", "test_row_1");
    var row2 = MockUtils.MockCells("test_row_2", "test_row_2");
    var range = MockUtils.MockRows(row1, row2);

    Assert.IsNotNull(range);
    Assert.AreEqual(2, range.Count);
    Assert.IsNotNull(range.Rows);
    Assert.AreEqual(2, range.Rows.Count);
    Range x = range.Rows[1];
    Range y = range.Rows[2];
    var xCell = x.Cells[1];
    var yCell = y.Cells[1];
    Assert.AreSame(row1, x); 
    Assert.AreSame(row2, y);
    Assert.AreEqual("test_row_1", xCell.Value2);
    Assert.AreEqual("test_row_2", yCell.Value2);
}

上述UT将通过测试。我认为你应该将聚合调用分解为“原子OPS(多行)和方法”,不是因为它将通过测试,而是因为它将使您的代码成为易于调试的代码(我称之为“第11条规则”)。在编写代码后,代码至少会被阅读10次以上...所以让编译器删除传递性局部变量,并使您的代码成为易于调试的代码。 在这里,您可以阅读有关C#中动态工作方式的简单而简短的说明及链接。 在这里,您可以阅读有关Castle Dynamic Proxy的更多信息。 顺便说一下:您也可以执行以下操作:
Range x = range.Rows[1].Cells;
var str = x[1].Value2;

接收{{value}}的值


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接