当多个单元测试复制同一个文件时,运行 所有单元测试都失败
When multiple unit tests copy the same file, running all unit tests fail
描述
我正在为一种方法编写单元测试,该方法将文件从源复制到目标。基本上它包括这段代码:
public void MyMethod()
{
// ...
File.Copy(source, destination, true);
// ...
}
在我的单元测试项目中,我有一个测试文件:(test.png
),它位于我的单元测试项目的Resources
文件夹中。我已经将 Copy to Output
属性 设置为 Always
。
我有 3 个单元测试正在测试此方法。
当他们点击复制文件的代码行时:source = "Resources\test.png"
.
问题
当我运行单独进行单元测试时,它们都通过了,一切都很好。
但是,当我 运行 Visual Studio 中的所有测试时,我得到这个 运行 时间错误并且单元测试失败:
System.IO.DirectoryNotFoundException
Could not find a part of the path 'Resources\test.png'.
我的想法...(更新)
可能是因为 Visual Studio 运行s 每个单元在单独的线程中同时测试并且它们都同时访问同一个文件?
我认为对于每个单元测试,Visual Studio 正在清理 bin/Debug
和 bin/Release
文件夹。然后它将所有必需的项目文件复制到该文件夹中。这导致有时文件实际上不存在?
问题
我该如何解决这个问题?
有什么配置设置可以解决这个问题吗?
当多个单元测试访问同一个文件时,我如何 运行 Visual Studio(和 Team City)中的所有单元测试?
我推测您的问题是其中一种测试方法更改了目录,给出了明确的 "Directory Not Found" 异常。文件锁定或任何并发问题不太可能导致所描述的行为。
访问同一个文件应该没有问题。确保您没有用于删除文件的 cleanUp Fixture(TestSuite 级别)。因为从异常来看,文件似乎在 运行 测试后被删除。
并发读取操作也很好并且完全合法。如果您的单元测试正在覆盖文件,那么这是一个问题。
您可以按照 MSDN: Executing Unit Tests in parallel on a multi-CPU/core machine 中的说明,将 parallelTestCount
设置为 1
,尝试排除多线程问题。如果现在测试通过,您就缩小了问题范围。
但是,如果当你 运行 他们在一个组中时你的测试仍然失败 - 我认为这是更有可能的情况 - 那么我的建议是检查这些测试共享的任何状态.您描述的模式(即隔离通过;不隔离时失败)是(错误地)共享状态的测试通常表现出的一种症状,并且这些测试正在修改该状态,导致一个或多个测试失败。
如果进行单元测试,您实际上不应该测试 File.Copy(或任何 File class 方法)是否有效,因为您没有编写该代码。相反,您应该测试您的代码是否与文件类型正确交互(即它是否在您调用 "Copy" 时传递了正确的源文件名、目标文件名和覆盖值)。首先为 File class 创建一个接口,并为它创建一个实现该接口的包装器;
public interface IFileWrapper
{
void Copy(string sourceFileName,string destFileName,bool overwrite);
//Other required file system methods and properties here...
}
public class FileWrapper : IFileWrapper
{
public void Copy(string sourceFileName, string destFileName, bool overwrite)
{
File.Copy(sourceFileName, destFileName, overwrite);
}
}
然后您应该使您正在测试的 class 包含一个 IFileWrapper 参数 (dependency injection)。在您的单元测试中,您可以使用 Moq 等模拟框架,或者您可以编写自己的模拟;
public class MockFileWrapper : IFileWrapper
{
public string SoureFileName { get; set; }
public string DestFileName { get; set; }
public bool Overwrite { get; set; }
public void Copy(string sourceFileName, string destFileName, bool overwrite)
{
SoureFileName = sourceFileName;
DestFileName = destFileName;
Overwrite = overwrite;
}
}
在实际实现中,将 FileWrapper 作为 IFileWrapper 参数传入,但在您的单元测试中,将 MockFileWrapper 传入。通过在单元测试中检查 mockFileWrapper 的属性,您现在可以确定是否 class 调用了 Copy 以及它是如何调用的。由于您不再在单元测试之间共享真实文件,因此您将避免测试共享状态或可能锁定文件的机会。
发生的事情是,由于我使用的是测试文件的相对路径,出于某种原因,当 运行 单元测试批量时,测试运行器工作目录与 运行 单独测试时不同, 因此找不到目录。
所以我使用这个函数来构建测试文件的绝对路径:
private string GetFilePath([CallerFilePath] string path = "")
{
return path;
}
然后:
string projectDir = Path.GetDirectoryName(GetFilePath());
string testFile = Path.Combine(projectDir, @"Resources\test.png";
正如您提到的 ,测试框架并不总是 运行 将工作目录设置为构建输出文件夹进行测试。
要指示测试框架将构建工件或构建输出中的其他文件放入测试目录,您需要使用 DeploymentItemAttribute
。对于你的情况,你会做类似的事情:
const string destination = "Destination.txt";
const string source = "MyData.txt";
[DeploymentItem(source)]
[TestMethod]
public void MyMethod()
{
// …
File.Copy(source, destination, true);
// …
}
[TestCleanup]
public void Cleanup()
{
// Clean up the destination so that subsequent tests using
// the same deploy don’t collide.
File.Delete(destination);
}
还要确保您的文件标有内容构建操作和始终复制。否则,它们不会在构建输出目录中,也无法复制到测试目录中。
描述
我正在为一种方法编写单元测试,该方法将文件从源复制到目标。基本上它包括这段代码:
public void MyMethod()
{
// ...
File.Copy(source, destination, true);
// ...
}
在我的单元测试项目中,我有一个测试文件:(test.png
),它位于我的单元测试项目的Resources
文件夹中。我已经将 Copy to Output
属性 设置为 Always
。
我有 3 个单元测试正在测试此方法。
当他们点击复制文件的代码行时:source = "Resources\test.png"
.
问题
当我运行单独进行单元测试时,它们都通过了,一切都很好。 但是,当我 运行 Visual Studio 中的所有测试时,我得到这个 运行 时间错误并且单元测试失败:
System.IO.DirectoryNotFoundException
Could not find a part of the path 'Resources\test.png'.
我的想法...(更新)
可能是因为 Visual Studio 运行s 每个单元在单独的线程中同时测试并且它们都同时访问同一个文件?
我认为对于每个单元测试,Visual Studio 正在清理
bin/Debug
和bin/Release
文件夹。然后它将所有必需的项目文件复制到该文件夹中。这导致有时文件实际上不存在?
问题
我该如何解决这个问题?
有什么配置设置可以解决这个问题吗?
当多个单元测试访问同一个文件时,我如何 运行 Visual Studio(和 Team City)中的所有单元测试?
我推测您的问题是其中一种测试方法更改了目录,给出了明确的 "Directory Not Found" 异常。文件锁定或任何并发问题不太可能导致所描述的行为。
访问同一个文件应该没有问题。确保您没有用于删除文件的 cleanUp Fixture(TestSuite 级别)。因为从异常来看,文件似乎在 运行 测试后被删除。
并发读取操作也很好并且完全合法。如果您的单元测试正在覆盖文件,那么这是一个问题。
您可以按照 MSDN: Executing Unit Tests in parallel on a multi-CPU/core machine 中的说明,将 parallelTestCount
设置为 1
,尝试排除多线程问题。如果现在测试通过,您就缩小了问题范围。
但是,如果当你 运行 他们在一个组中时你的测试仍然失败 - 我认为这是更有可能的情况 - 那么我的建议是检查这些测试共享的任何状态.您描述的模式(即隔离通过;不隔离时失败)是(错误地)共享状态的测试通常表现出的一种症状,并且这些测试正在修改该状态,导致一个或多个测试失败。
如果进行单元测试,您实际上不应该测试 File.Copy(或任何 File class 方法)是否有效,因为您没有编写该代码。相反,您应该测试您的代码是否与文件类型正确交互(即它是否在您调用 "Copy" 时传递了正确的源文件名、目标文件名和覆盖值)。首先为 File class 创建一个接口,并为它创建一个实现该接口的包装器;
public interface IFileWrapper
{
void Copy(string sourceFileName,string destFileName,bool overwrite);
//Other required file system methods and properties here...
}
public class FileWrapper : IFileWrapper
{
public void Copy(string sourceFileName, string destFileName, bool overwrite)
{
File.Copy(sourceFileName, destFileName, overwrite);
}
}
然后您应该使您正在测试的 class 包含一个 IFileWrapper 参数 (dependency injection)。在您的单元测试中,您可以使用 Moq 等模拟框架,或者您可以编写自己的模拟;
public class MockFileWrapper : IFileWrapper
{
public string SoureFileName { get; set; }
public string DestFileName { get; set; }
public bool Overwrite { get; set; }
public void Copy(string sourceFileName, string destFileName, bool overwrite)
{
SoureFileName = sourceFileName;
DestFileName = destFileName;
Overwrite = overwrite;
}
}
在实际实现中,将 FileWrapper 作为 IFileWrapper 参数传入,但在您的单元测试中,将 MockFileWrapper 传入。通过在单元测试中检查 mockFileWrapper 的属性,您现在可以确定是否 class 调用了 Copy 以及它是如何调用的。由于您不再在单元测试之间共享真实文件,因此您将避免测试共享状态或可能锁定文件的机会。
发生的事情是,由于我使用的是测试文件的相对路径,出于某种原因,当 运行 单元测试批量时,测试运行器工作目录与 运行 单独测试时不同, 因此找不到目录。
所以我使用这个函数来构建测试文件的绝对路径:
private string GetFilePath([CallerFilePath] string path = "")
{
return path;
}
然后:
string projectDir = Path.GetDirectoryName(GetFilePath());
string testFile = Path.Combine(projectDir, @"Resources\test.png";
正如您提到的
要指示测试框架将构建工件或构建输出中的其他文件放入测试目录,您需要使用 DeploymentItemAttribute
。对于你的情况,你会做类似的事情:
const string destination = "Destination.txt";
const string source = "MyData.txt";
[DeploymentItem(source)]
[TestMethod]
public void MyMethod()
{
// …
File.Copy(source, destination, true);
// …
}
[TestCleanup]
public void Cleanup()
{
// Clean up the destination so that subsequent tests using
// the same deploy don’t collide.
File.Delete(destination);
}
还要确保您的文件标有内容构建操作和始终复制。否则,它们不会在构建输出目录中,也无法复制到测试目录中。