如何正确地对该方法进行单元测试?

How to UnitTest this method properly?

我有一个方法(如下),它从 MVC 应用程序中的控制器调用,然后输出一个 excel 文件。

方法:

public static void ExportToExcel(IEnumerable<dynamic> data, string bookName, string sheetName)
{
    XLWorkbook workbook = new XLWorkbook();
    var worksheet = workbook.Worksheets.Add(sheetName);
    worksheet.Cell(1, 1).InsertTable(data);

    HttpContext.Current.Response.Clear();
    HttpContext.Current.Response.ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
    HttpContext.Current.Response.AddHeader("content-disposition", String.Format(@"attachment;filename={0}.xlsx", bookName.Replace(" ", "_")));

    using (MemoryStream memStream = new MemoryStream())
    {
        workbook.SaveAs(memStream);
        memStream.WriteTo(HttpContext.Current.Response.OutputStream);
        memStream.Close();
    }
    HttpContext.Current.Response.End();
}

控制器:

[ActionName("ExportData")]
public ActionResult ExportData()
{
    ExcelExport.ExportToExcel(_dbaccess.GetAllData()), "Workbook", "Worksheet");

    return RedirectToAction("Index");
}

如何测试此方法?

您可能应该将方法进一步拆分。创建一个方法,该方法 returns 一个字节 [] 生成表示文件的字节数组:

public static byte[] ExportToExcel(IEnumerable<dynamic> data, string bookName, string sheetName)

然后创建一个实际保存到磁盘的方法:

public static bool SaveFileToDisk(byte[] file, string path)

然后您可以测试 ExportToExcel 方法断言它 returns 一个已知字节[]

首先,您需要重构该方法,使其更易于测试。那里混杂了太多的问题,使测试变得容易。 (我的意见)。

删除工作簿生成,它可以属于相同的 class 或某些依赖项

public interface IGetWorkBook {
    XLWorkbook GetWorkBook(IEnumerable<dynamic> data, string sheetName);
}

其中的实现与您在原始方法中所拥有的完全一样。

public XLWorkbook GetWorkBook(IEnumerable<dynamic> data, string sheetName) {    
    XLWorkbook workbook = new XLWorkbook();
    var worksheet = workbook.Worksheets.Add(sheetName);
    worksheet.Cell(1, 1).InsertTable(data);    
}

接下来您需要抽象出与 HttpContext

的紧耦合

有一些关于使用 HttpContext

绕过测试的好文章

Don't mock HttpContext他不喜欢被嘲笑! :)

你要问的问题是what am I trying to achieve?。在这种情况下,我们希望将工作簿保存到某个地方。是的,在这种情况下是 HttpContext 响应,但也可能是其他响应。为此我们需要一个抽象

public interface IWriteWorkbook {
    void Write(XLWorkbook workbook, string bookName); 
}

您稍后可以将其包含在派生的 class 中,您非常喜欢使用 HttpContext

void Write(XLWorkbook workbook, string bookName) {
    HttpContext.Current.Response.Clear();
    HttpContext.Current.Response.ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
    HttpContext.Current.Response.AddHeader("content-disposition", String.Format(@"attachment;filename={0}.xlsx", bookName.Replace(" ", "_")));

    using (MemoryStream memStream = new MemoryStream())
    {
        workbook.SaveAs(memStream);
        memStream.WriteTo(HttpContext.Current.Response.OutputStream);
        memStream.Close();
    }
    HttpContext.Current.Response.End();
}

这也可以改进,但这超出了 post。

经过所有更改后,您的重构方法可能看起来像这样。

public interface IExcelExporter {
    void ExportToExcel(IEnumerable<dynamic> data, string bookName, string sheetName);
}

public class ExcelExport : IExcelExporter {
    IGetWorkBook workbookgGetter;
    IWriteWorkbook workbookWriter;

    public ExcelExport (IGetWorkBook workbookgGetter,IWriteWorkbook workbookWriter) {
        this.workbookgGetter = workbookgGetter;
        this.workbookWriter = workbookWriter;
    }

    public void ExportToExcel(IEnumerable<dynamic> data, string bookName, string sheetName)
    {
        XLWorkbook workbook = workbookgGetter.GetWorkBook(data, sheetName);
        void workbookWriter.Write(workbook,bookName);        
    }    
}

好吧,呸……太多了。没想到会这么多吧?但最终还是值得的。我认为? :)

现在我们需要定位控制器。

public class MyExcelController: Controller {

    public MyExcelController(IExcelExporter exporter){
        ExcelExport = exporter;
    }

    IExcelExporter ExcelExport{get; private set;}

    [ActionName("ExportData")]
    public ActionResult ExportData()
    {
        ExcelExport.ExportToExcel(_dbaccess.GetAllData(), "Workbook", "Worksheet");

        return RedirectToAction("Index");
    }
}

请注意,原来的控制器操作并没有改变,而它周围的一切都改变了 :) 太棒了!!

现在我们准备好尽情地模拟和测试了。

你可以测试IGetWorkBook.GetWorkBook

你可以测试IWriteWorkbook.Write

你可以测试ExcelExport.ExportToExcel

确保它们都按预期运行。