如何正确地对该方法进行单元测试?
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
确保它们都按预期运行。
我有一个方法(如下),它从 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
确保它们都按预期运行。