如果您要在要测试的类中具有依赖项的具体实现,那么经典的解决方案是:在您完全控制的位置添加一个间接层。
因此,需要添加越来越多的间接层(在单元测试中可以是存根/模拟的接口)。
但是:在我的测试的某个地方,我必须拥有"真实"实现我的依赖。那么这个怎么样?测试?不要测试吗?
举个例子:
我对应用程序中需要的路径有一些依赖关系。所以我提取了一个接口IPathProvider
(我可以在测试中假装)。以下是实施:
public interface IPathProvider
{
string AppInstallationFolder { get; }
string SystemCommonApplicationDataFolder { get; }
}
具体实现PathProvider
如下所示:
public class PathProvider : IPathProvider
{
private string _appInstallationFolder;
private string _systemCommonApplicationDataFolder;
public string AppInstallationFolder
{
get
{
if (_appInstallationFolder == null)
{
try
{
_appInstallationFolder =
Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
}
catch (Exception ex)
{
throw new MyException("Error reading Application-Installation-Path.", ex);
}
}
return _appInstallationFolder;
}
}
public string SystemCommonApplicationDataFolder
{
get
{
if (_systemCommonApplicationDataFolder == null)
{
try
{
_systemCommonApplicationDataFolder =
Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData);
}
catch (Exception ex)
{
throw new MyException("Error reading System Common Application Data Path.", ex);
}
}
return _systemCommonApplicationDataFolder;
}
}
}
您是否测试过此类代码以及是否如何?
答案 0 :(得分:1)
PathProvider
类看起来很像数据库存储库类 - 它与外部系统(例如,文件系统,数据库等)集成。
这些类位于系统的边界 - 其他类依赖于它们,它们仅依赖于外部系统。它们将您的系统连接到外部世界。
因此,他们需要integration testing - 而不是单元测试。
我通常只是标记我的测试(例如使用xUnit' s Trait
属性)以使它们与常规测试分开,因此我可以在隔离运行时禁用它们(例如,没有数据库)。另一种方法是在相同的解决方案中将它们分成一个全新的项目,但我个人认为这有点过分。
[Fact, Trait("Category", "Integration")]
public void ReturnsCorrectAppInstallationFolder()
{
// Arrange
var assemblyFilename = Path.GetFilename(typeof(SomeType).Assembly.Location);
var provider = new PathProvider();
// Act
var location = provider.AppInstallationFolder;
// Assert
Assert.NotEmpty(Directory.GetFiles(location, assemblyFilename))
}
答案 1 :(得分:0)
答案取决于您对“单位”的定义。在使用类作为一个单元的情况下,答案是肯定的。但是,除非您正在开发某种类库,否则测试单个类的业务价值非常有限。
另一种替代方法是将单元定义为用户级(或系统)要求。对这种单元的测试将遵循:给定用户输入X,测试系统输出Y.以这种方式定义的单位具有明确的,可测量的商业价值,可以追溯到用户要求。
例如,使用user stories时,您可能会遇到以下要求:
作为管理员,我想将软件包安装到特定文件夹。
这可能有许多相关的方案来涵盖不同的允许和禁止的文件夹。其中每个都有一个单元测试,提供用户输入并验证输出。
给定:用户已指定
C:\Program Files\MyApp
作为安装 文件夹中。何时:安装程序已运行。
然后:驱动器上存在
C:\Program Files\MyApp
和所有 应用程序文件存在于该位置。
在您正在进行的类接口级别的单元测试时,您可以轻松地将您的软件设计与您的单元测试框架和实现紧密结合,而不是根据用户的要求。
答案 2 :(得分:0)
这两个答案有很多价值,但我还要补充一点,我认为这个问题非常重要。
我觉得人们经常会讨论应该测试什么,不应该测试什么。那些东西应该被视为单位或不应该被视为单位。它是否是一种整合。
通常,单元测试的目标是确保我们的代码按预期工作。 IMO我们应该测试一切(无论我们能做什么),这可能会被打破。将其组织成一些正式的测试并命名它是必要的,但对测试本身来说是次要的。我的建议是,如果你有任何疑问,总要问自己一个问题:"我是否害怕它可以被打破?" 。所以..."在哪里停止"? 你不怕功能被破坏。
回到特定案例。正如@dcastro注意到的那样,这看起来确实是集成测试的一个例子,而不是经典的逻辑单元测试。不管我们如何命名它 - 它可以被打破吗?是的,它可以。它应该测试吗? 它可能会被测试,虽然我还远未命名它至关重要。几乎没有自己增加的逻辑,复杂性很低。我会测试一下吗?是的,如果我有时间这样做的话。我该怎么办? @dcastro的示例代码是我解决问题的方式。