如何在单元测试期间覆盖被测 class 中的某些 属性
How to override some property in the class under test during unit testing
我有一个 class 我想用单元测试来测试。它有一些在本地 xml 文件中查找某些值的逻辑,如果未找到该值,它将读取一些外部源(Sql 服务器数据库)。但是在单元测试期间,我根本不希望此组件与 Sql 服务器交互。在单元测试期间,我想将外部源 reader 的 Sql 服务器实现替换为一些 Noop reader。实现这一目标的好方法是什么?重要说明:我无法定义接受 reader 类型实例的构造函数,因为它是面向客户端的 API 并且我想限制它们可以对我的 class 执行的操作。
我目前在单元测试中使用了几种方法:
- 使用反射将 private/protected 属性 的值设置为我对 reader
的模拟实现
- 定义将创建混凝土的工厂 class。在Unity容器中注册工厂 & class 测试中会从DI容器中获取工厂对象并根据我的需要实例化reader
- Subclass class-测试中并在那里设置 属性。
但是 none 对我来说似乎足够干净了。有没有更好的方法来实现这一目标?
下面是演示 class-under-the-test 示例的示例代码:
namespace UnitTestProject1
{
using System.Collections.Generic;
using System.Data.SqlClient;
public class SomeDataReader
{
private Dictionary<string, string> store = new Dictionary<string, string>();
// I need to override what this property does
private readonly IExternalStoreReader ExternalStore = new SqlExternalStoreReader(null, false, new List<string>() {"blah"});
public string Read(string arg1, int arg2, bool arg3)
{
if (!store.ContainsKey(arg1))
{
return ExternalStore.ReadSource().ToString();
}
return null;
}
}
internal interface IExternalStoreReader
{
object ReadSource();
}
// This
internal class SqlExternalStoreReader : IExternalStoreReader
{
public SqlExternalStoreReader(object arg1, bool arg2, List<string> arg3)
{
}
public object ReadSource()
{
using (var db = new SqlConnection("."))
{
return new object();
}
}
}
internal class NoOpExternalStoreReader : IExternalStoreReader
{
public object ReadSource()
{
return null;
}
}
}
[TestClass]
public class UnitTest1
{
[TestMethod]
public void TestMethod1()
{
var objectToTest = new SomeDataReader();
objectToTest.Read("", -5, false); // This will try to read DB, I don't want that.
}
}
简单的答案是:更改您的代码。因为您有一个私有的只读字段,它可以创建自己的值,所以您不能在不真正变态的情况下改变这种行为,如果有的话。所以,不要那样做。将代码更改为:
public class SomeDataReader
{
private Dictionary<string, string> store = new Dictionary<string, string>();
private readonly IExternalStoreReader externalStore;
public SomeDataReader(IExternalStoreReader externalStore)
{
this.externalStore = externalStore;
}
换句话说,将 IExternalStoreReader
实例注入到 class 中。这样您就可以为单元测试创建一个 noop
版本,为生产代码创建一个真实版本。
如果您自己编译完整的代码,您可以在单元测试项目中创建一个带有存根实现的假 SqlExternalStoreReader
。
此存根实现允许您访问所有字段并将调用转发给您的模拟 framework/unit 测试
在这种情况下,您可以使用 InternalsVisibleTo
attribute, which exposes all internal members to friend assemblies。
您可以先创建一个单独的 internal
构造函数重载,它可以接受 IExternalStoreReader
:
的不同实例
public class SomeDataReader
{
// publicly visible
public SomeDataReader()
: this(new SqlExternalStoreReader(...))
{ }
// internally visible
internal SomeDataReader(IExternalStoreReader storeReader)
{
ExternalStore = storeReader;
}
...
}
然后通过将 InternalsVisibleTo
属性添加到您的 AssemblyInfo.cs
:
允许单元测试程序集访问内部成员
[assembly: InternalsVisibleTo("YourUnitTestingAssembly")]
如果您真的担心有人试图访问您的内部成员,您还可以使用 strong naming 和 InternalsVisibleTo
属性来确保没有人试图冒充您的单元测试程序集。
我有一个 class 我想用单元测试来测试。它有一些在本地 xml 文件中查找某些值的逻辑,如果未找到该值,它将读取一些外部源(Sql 服务器数据库)。但是在单元测试期间,我根本不希望此组件与 Sql 服务器交互。在单元测试期间,我想将外部源 reader 的 Sql 服务器实现替换为一些 Noop reader。实现这一目标的好方法是什么?重要说明:我无法定义接受 reader 类型实例的构造函数,因为它是面向客户端的 API 并且我想限制它们可以对我的 class 执行的操作。 我目前在单元测试中使用了几种方法:
- 使用反射将 private/protected 属性 的值设置为我对 reader 的模拟实现
- 定义将创建混凝土的工厂 class。在Unity容器中注册工厂 & class 测试中会从DI容器中获取工厂对象并根据我的需要实例化reader
- Subclass class-测试中并在那里设置 属性。
但是 none 对我来说似乎足够干净了。有没有更好的方法来实现这一目标? 下面是演示 class-under-the-test 示例的示例代码:
namespace UnitTestProject1
{
using System.Collections.Generic;
using System.Data.SqlClient;
public class SomeDataReader
{
private Dictionary<string, string> store = new Dictionary<string, string>();
// I need to override what this property does
private readonly IExternalStoreReader ExternalStore = new SqlExternalStoreReader(null, false, new List<string>() {"blah"});
public string Read(string arg1, int arg2, bool arg3)
{
if (!store.ContainsKey(arg1))
{
return ExternalStore.ReadSource().ToString();
}
return null;
}
}
internal interface IExternalStoreReader
{
object ReadSource();
}
// This
internal class SqlExternalStoreReader : IExternalStoreReader
{
public SqlExternalStoreReader(object arg1, bool arg2, List<string> arg3)
{
}
public object ReadSource()
{
using (var db = new SqlConnection("."))
{
return new object();
}
}
}
internal class NoOpExternalStoreReader : IExternalStoreReader
{
public object ReadSource()
{
return null;
}
}
}
[TestClass]
public class UnitTest1
{
[TestMethod]
public void TestMethod1()
{
var objectToTest = new SomeDataReader();
objectToTest.Read("", -5, false); // This will try to read DB, I don't want that.
}
}
简单的答案是:更改您的代码。因为您有一个私有的只读字段,它可以创建自己的值,所以您不能在不真正变态的情况下改变这种行为,如果有的话。所以,不要那样做。将代码更改为:
public class SomeDataReader
{
private Dictionary<string, string> store = new Dictionary<string, string>();
private readonly IExternalStoreReader externalStore;
public SomeDataReader(IExternalStoreReader externalStore)
{
this.externalStore = externalStore;
}
换句话说,将 IExternalStoreReader
实例注入到 class 中。这样您就可以为单元测试创建一个 noop
版本,为生产代码创建一个真实版本。
如果您自己编译完整的代码,您可以在单元测试项目中创建一个带有存根实现的假 SqlExternalStoreReader
。
此存根实现允许您访问所有字段并将调用转发给您的模拟 framework/unit 测试
在这种情况下,您可以使用 InternalsVisibleTo
attribute, which exposes all internal members to friend assemblies。
您可以先创建一个单独的 internal
构造函数重载,它可以接受 IExternalStoreReader
:
public class SomeDataReader
{
// publicly visible
public SomeDataReader()
: this(new SqlExternalStoreReader(...))
{ }
// internally visible
internal SomeDataReader(IExternalStoreReader storeReader)
{
ExternalStore = storeReader;
}
...
}
然后通过将 InternalsVisibleTo
属性添加到您的 AssemblyInfo.cs
:
[assembly: InternalsVisibleTo("YourUnitTestingAssembly")]
如果您真的担心有人试图访问您的内部成员,您还可以使用 strong naming 和 InternalsVisibleTo
属性来确保没有人试图冒充您的单元测试程序集。