用最小起订量模拟 OpenXML

Mocking OpenXML with Moq

我应该如何测试以下 GetWorksheetPart 方法:

public class ExcelDocument : IExcelDocument
{
    private readonly string _filePath;

    public ExcelDocument(string filePath)
    {
        _filePath = filePath;
    }

    public WorksheetPart GetWorksheetPart(ISpreadsheetDocument excelDoc, string sheetName)
    {
        Sheet sheet = excelDoc.GetSheet(sheetName);
        if (sheet == null)
        {
            throw new ArgumentException(
                String.Format("No sheet named {0} found in spreadsheet {1}",
                    sheetName, _filePath), "sheetName");
        }
        return excelDoc.GetPartById(sheet.Id);
    }
}

其中 IExcelDocument 和 wrapper 的 SpreadsheetDocumentWrapper 接口是:

public interface IExcelDocument
{
    WorksheetPart GetWorksheetPart(ISpreadsheetDocument excelDoc, string sheetName);
}

public interface ISpreadsheetDocument
{
    Sheet GetSheet(string name);

    WorksheetPart GetPartById(string id);
}

这是包装器本身。

public class SpreadsheetDocumentWrapper : ISpreadsheetDocument
{
    private SpreadsheetDocument excelDoc;

    public SpreadsheetDocumentWrapper(SpreadsheetDocument excelDoc)
    {
        this.excelDoc = excelDoc;
    }

    public Sheet GetSheet(string sheetName)
    {
        return excelDoc.WorkbookPart.Workbook.Descendants<Sheet>()
                   .SingleOrDefault(s => s.Name == sheetName);
    }

    public WorksheetPart GetPartById(string id)
    {
        return (WorksheetPart)excelDoc.WorkbookPart.GetPartById(id);
    }
}

最后,我尝试为 GetWorksheetPart 实施的测试。问题在于我不确定如何测试此功能。想法是在给定 sheet 名称和传播 sheet 文档的情况下获得 WorksheetPart

public class Test
{
    [TestClass()]
    public class ExcelUpdateLogicTests
    {
        [TestMethod()]
        public void Excel_GetWorkseetPartTest()
        {
            Mock<ISpreadsheetDocument> mockSpreadhseet = new Mock<ISpreadsheetDocument>();
            Sheet sheet = new Sheet();
            string id = "1";
            sheet.Name = "sheet";
            sheet.Id = id;

            mockSpreadhseet.Setup(doc => doc.GetSheet("sheet")).Returns(sheet);

            mockSpreadhseet.Setup(doc => doc.GetPartById(id)).Returns(????);

            Mock<IExcelDocument> mockExcelDocument = new Mock<IExcelDocument>();
            WorksheetPart mockWorkseet = mockExcelDocument.Object
                .GetWorksheetPart(mockSpreadhseet.Object, "sheet");

            Assert.IsTrue(mockWorkseet.GetIdOfPart(mockWorkseet) == id);
        }
    }
}

这是 Spreadsheets 的通用 OpenXML 结构树:

                Spreadsheet
                      |         
                 WorkbookPart    
           /         |             \
   Workbook WorkbookStylesPart WorksheetPart
         |          |               |
    Sheets     StyleSheet        Worksheet
        |                        /        \       
  (refers to               SheetData        Columns  
   Worksheetparts)            |   
                             Rows

由于 ExcelDocument 是被测系统,因此无需模拟 IExcelDocument。您应该 mock/stub/fake 测试 SUT

所需的依赖项

现在我可以让你的测试像这样通过了...

public class Test {
    [TestClass()]
    public class ExcelUpdateLogicTests {
        [TestMethod()]
        public void Given_SheetName_ExcelDocument_Should_GetWorkseetPart() {
            //Arrange
            var stream = new MemoryStream();//Avoid having to use actual file on disk
            var spreadsheetDocument = SpreadsheetDocument.Create(stream, SpreadsheetDocumentType.Workbook);

            // Add a WorkbookPart.
            WorkbookPart workbookpart = spreadsheetDocument.AddWorkbookPart();
            workbookpart.Workbook = new Workbook();

            // Add a WorksheetPart.
            WorksheetPart worksheetPart = workbookpart.AddNewPart<WorksheetPart>();
            worksheetPart.Worksheet = new Worksheet(new SheetData());

            // Add a sheets list.
            Sheets sheets = spreadsheetDocument.WorkbookPart.Workbook.AppendChild<Sheets>(new Sheets());

            // Append the new worksheet and associate it with the workbook.
            string expectedId = workbookpart.GetIdOfPart(worksheetPart);
            string sheetName = "mySheet";

            Sheet sheet = new Sheet() {
                Id = expectedId,
                SheetId = 1,
                Name = sheetName
            };
            sheets.Append(sheet);

            var wrapper = new SpreadsheetDocumentWrapper(spreadsheetDocument);

            string fakeFilePath = "path";
            var sut = new ExcelDocument(fakeFilePath);

            //Act
            WorksheetPart result = sut.GetWorksheetPart(wrapper, sheetName);

            //Assert
            Assert.IsNotNull(result);
            var actualId = workbookpart.GetIdOfPart(result);
            Assert.AreEqual(expectedId, actualId);
        }
    }
}

然而,这样做的过程对当前的设计提出了一些问题。

如果创建抽象的全部目的是隐藏实现细节并减少与外部框架的紧密耦合以使事情更容易模拟和测试,那么必须创建一个实际的 SpreadsheetDocument 并包装它因为测试似乎是多余的。

考虑到内部生成,框架的很多部分都很难模拟。我会将这些隐藏在其他抽象之后,但鉴于我对该系统的最终目标了解不够,我无法就您应该采用哪种设计结构提出建议。