如何在C#单元测试中进行MapPath映射

14

我想在单元测试中加载外部XML文件以测试对该XML的处理代码。 如何获取该文件的路径?

通常在Web应用程序中,我会执行以下操作:

XDocument.Load(Server.MapPath("/myFile.xml"));

但是显然在我的单元测试中,我没有对Server或HttpContext的引用,那么我如何映射路径,以便不必指定完整路径?

更新:

我只想澄清一下,我实际上正在测试的代码是一个XML解析器类,类似于:

public static class CustomerXmlParser {
  public static Customer ParseXml(XDocument xdoc) {
    //...
  }
}

因此,为了测试这个方法,我需要解析一个有效的XDocument。被测试的方法本身不访问文件系统。我可以在测试代码中直接从字符串创建XDocument,但我认为从文件加载会更容易。

6个回答

24

另一个想法是利用依赖注入。

public interface IPathMapper {
string MapPath(string relativePath);
}

然后只需使用两种实现

public class ServerPathMapper : IPathMapper {
     public string MapPath(string relativePath){
          return HttpContext.Current.Server.MapPath(relativePath);
     }
}

然后您还需要您的模拟实现

public class DummyPathMapper : IPathMapper {
    public string MapPath(string relativePath){
        return "C:/Basedir/" + relativePath;
    }
}

然后所有需要映射路径的函数都只需要访问IPathMapper实例 - 在您的Web应用程序中它需要是ServerPathMapper,在单元测试中是DummyPathMapper - 基本的依赖注入(DI)。


这里使用接口的好处是什么?相反,我们是否可以简单地创建两个类文件,并根据需要从实际应用程序和测试端点中调用它们?有人能否解释一下这个接口的好处? - Sebastian
通过使用类,您需要决定让一个类继承另一个类,以使类型系统满意。让测试实现继承真实实现,或者反过来,都不是一个理想的解决方案。这样做,您无需让真实和测试实现彼此了解 - 它们只需要满足一个共同的接口即可。 - kastermester

5
个人而言,我会对任何依赖于后端资源存储(无论是文件系统还是数据库)的代码持谨慎态度——您正在引入一个依赖项到您的单元测试中,这可能会导致假阴性,即测试失败不是因为您的特定测试代码,而是因为文件不存在或服务器不可用等原因。
请参见此链接(英文),其中有 IMO 对单元测试的很好定义,更重要的是说明了什么不是单元测试。
您的单元测试应该测试一个原子化、明确定义的功能,而不是测试文件是否可以加载。一种解决方案是“模拟”文件加载,有各种方法可以做到这一点,但个人认为只需要模拟您正在使用的文件系统接口,而不尝试进行任何完整的文件系统模拟。这里(英文)是一个很好的 SO 帖子和(英文)是一个很好的 SO 讨论关于文件系统模拟的问题。
希望这能有所帮助。

只是为了明确,我不是在测试XML文件是否可以加载或测试文件的内容,而是在测试需要解析XDocument的代码片段。如果文件位于测试中,则如果文件无法加载,则测试将显示错误而不是虚假负面结果。我意识到这并不理想,但我不知道还有其他方法。我已更新我的问题。 - David Glenn
当然,这就是重点,不是吗?您的代码正在测试您的解析器,因此它获取XML数据的位置无关紧要,并且不应因文件不存在而失败。就我个人而言,我会选择字符串选项,或者将其作为嵌入式资源。 - zebrabox

3
通常对于单元测试,我会将xml文件作为嵌入式资源添加到项目中,并使用以下方法加载它们:
public static string LoadResource(string name)
{
  Type thisType = MethodBase.GetCurrentMethod().DeclaringType;
  string fullName = thisType.Namespace + "." + name + ".xml";

  using (Stream stream = thisType.Module.Assembly.GetManifestResourceStream(fullName))
  {
      if(stream==null)
      {
        throw new ArgumentException("Resource "+name+" not found.");
      }

      StreamReader sr = new StreamReader(stream);
      return sr.ReadToEnd();
  }
}

2

编辑:由于我最初可能错误地解释了你的问题,所以我从零开始。

在MS单元测试中,将XML文件加载到单元测试中以便注入到某些类中的最佳方法是使用DeploymentItem属性。

代码如下:

[TestMethod]
[DeploymentItem(@"DataXmlFiles\MyTestFile.xml", "DataFiles")]
public void LoadXMLFileTest()
{
   //instead of "object" use your returning type (i.e. string, XDocument or whatever)
   //LoadXmlFile could be a method in the unit test that actually loads an XML file from the File system
   object myLoadedFile = LoadXmlFile(Path.Combine(TestContext.TestDeploymentDir, "DataFiles\\MyTestFile.xml"));

   //do some unit test assertions to verify the outcome
}

我现在没有在调试器上测试代码,但它应该可以工作。

编辑: 顺便提一下,当您使用DeploymentItem时,请考虑这篇文章这里


1

类:

internal class FakeHttpContext : HttpContextBase
{
    public override HttpRequestBase Request { get { return new FakeHttpRequest(); } }
}

internal class FakeHttpRequest : HttpRequestBase
{
    public override string MapPath(string virtualPath)
    {
        return /* your mock */
    }
}

使用方法:

[TestMethod]
public void TestMethod()
{
    var context = new FakeHttpContext();
    string pathToFile = context.Request.MapPath("~/static/all.js");
}

0

这可能对某些人有所帮助。我遇到了相关问题,想从 c# 单元测试项目中的根级文件夹中使用 Excel 文件。

我有一个名为 TestFiles 的根级文件夹。其中包含了 Test.xlsx。

我的做法是:

右键单击“Test.xlsx”,进入属性并设置“复制到输出目录”="始终复制"

现在,该文件及其包含文件夹 TestFiles 总是会被复制到单元测试项目的 bin 文件夹中。这样我就能像这样使用它:

var filePath = "TestFiles/Test.xlsx";
var strConn = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" + filePath + ";Extended Properties=\"Excel 12.0;HDR=Yes;IMEX=0\"";
using (var conn = new OleDbConnection(strConn))
{
                conn.Open();
...
}

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