Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/unit-testing/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 如何在单元测试期间重写被测类中的某些属性_C#_Unit Testing_Design Patterns - Fatal编程技术网

C# 如何在单元测试期间重写被测类中的某些属性

C# 如何在单元测试期间重写被测类中的某些属性,c#,unit-testing,design-patterns,C#,Unit Testing,Design Patterns,我有一个类,我想用单元测试来测试。它有一些逻辑来查找本地xml文件中的某些值,如果找不到该值,它将读取一些外部源(SqlServer DB)。但是在单元测试期间,我根本不希望这个组件与SQLServer交互。在单元测试期间,我想将外部源代码读取器的SqlServer实现替换为一些Noop读取器。实现这一目标的好方法是什么?重要提示:我不能定义接受reader类型实例的构造函数,因为它是面向客户端的API,我想限制它们对我的类可以做什么。 我目前在单元测试中使用了几种方法: 使用反射将私有/受保

我有一个类,我想用单元测试来测试。它有一些逻辑来查找本地xml文件中的某些值,如果找不到该值,它将读取一些外部源(SqlServer DB)。但是在单元测试期间,我根本不希望这个组件与SQLServer交互。在单元测试期间,我想将外部源代码读取器的SqlServer实现替换为一些Noop读取器。实现这一目标的好方法是什么?重要提示:我不能定义接受reader类型实例的构造函数,因为它是面向客户端的API,我想限制它们对我的类可以做什么。 我目前在单元测试中使用了几种方法:

  • 使用反射将私有/受保护属性的值设置为读取器的模拟实现
  • 定义将创建具体类的工厂。在Unity容器中注册工厂&测试中的类将从DI容器中获取工厂对象,并根据我的需要实例化读取器
  • 子类测试下的类,并在其中设置属性
但在我看来,它们都不够干净。有没有更好的方法来实现这一点? 下面是示例代码,用于演示测试类的示例:

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.
        }
    }
名称空间UnitTestProject1
{
使用System.Collections.Generic;
使用System.Data.SqlClient;
公共类数据读取器
{
私有字典存储=新字典();
//我需要覆盖此属性的功能
private readonly IExternalStoreReader ExternalStore=new SqlExternalStoreReader(null,false,new List(){“blah”});
公共字符串读取(字符串arg1、整数arg2、布尔arg3)
{
如果(!store.ContainsKey(arg1))
{
返回ExternalStore.ReadSource().ToString();
}
返回null;
}
}
内部接口IExternalStoreReader
{
对象ReadSource();
}
//这个
内部类SqlExternalStoreReader:IExternalStoreReader
{
公共SqlExternalStoreReader(对象arg1、布尔arg2、列表arg3)
{
}
公共对象读取源()
{
使用(var db=new-SqlConnection(“.”)
{
返回新对象();
}
}
}
内部类NoOpExternalStoreReader:IEExternalStoreReader
{
公共对象读取源()
{
返回null;
}
}
}
[测试类]
公共类UnitTest1
{
[测试方法]
公共void TestMethod1()
{
var objectToTest=new SomeDataReader();
objectToTest.Read(“,-5,false);//这将尝试读取DB,我不希望这样。
}
}

简单的答案是:更改代码。因为您有一个私有的只读字段,它可以创建自己的值,所以如果没有真正的黑客攻击,您就无法改变这种行为。所以,不要那样做。将代码更改为:

public class SomeDataReader
{
    private Dictionary<string, string> store = new Dictionary<string, string>();

    private readonly IExternalStoreReader externalStore;

    public SomeDataReader(IExternalStoreReader externalStore)
    {
        this.externalStore = externalStore;
    }
公共类SomeDataReader
{
私有字典存储=新字典();
私有只读IExternalStoreReader外部存储;
公共SomeDataReader(IExternalStoreReader外部存储)
{
this.externalStore=externalStore;
}

换句话说,将
IExternalStoreReader
实例注入到类中。这样,您可以为单元测试创建
noop
版本,为生产代码创建真实版本。

如果您自己编译完整的代码,您可以创建一个假的
SqlExternalStoreReader
,其中包含一个存根实现单元测试项目。
此存根实现允许您访问所有字段并将调用转发到模拟框架/单元测试。在这种情况下,您可以使用该属性,该属性将所有内部成员公开给

您可以首先创建一个单独的
内部
构造函数重载,它可以接受
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")]

如果你真的担心有人试图访问你的内部成员,你也可以使用
InternalsVisibleTo
属性来确保没有人试图模拟你的单元测试程序集。

同意,这会让事情变得更简单,但该签名会看到此类用户,我不希望他们看到并使用它(这个类是我们提供给使用我们服务的其他团队的库的一部分)您库的客户端真的需要更新一些DataReader对象吗?或者您可以使用属性注入而不是构造函数注入(即在单元测试中使用myDataReader.InitializeExternalStore(fakeStore))。如果您不想完全公开私有字段,请查看以使属性仅在测试中可设置。@VitaliyGanzha,该类是
public
,因此API的用户可以查看和使用它。因此,我不确定向其中注入接口实例的问题。请参阅