简体   繁体   English

(C#) 使用 DI 和自定义通用 Class 进行单元测试

[英](C#) Unit Test with DI and Custom Generic Class

I am working on doing a Unit Test.我正在做一个单元测试。 My problem is i cant seem to find a way to make a test for the Logic class.我的问题是我似乎找不到对 Logic class 进行测试的方法。 Because the Logic class is dependend with the SQL class.因为逻辑 class 依赖于 SQL class。 i haved tryed some Test but so far no good results.我已经尝试了一些测试,但到目前为止没有好的结果。

Here is the Logic Class.这是逻辑 Class。 Im using a Custom Generic Class for the Database Connection Class.我为数据库连接 Class 使用自定义通用 Class。 AccountFE = is a modified DTO. AccountFE = 是修改后的 DTO。

 public class AccountsLogic
    {
      SQLGeneralDB<AccountFE> Database = new SQLGeneralDB<AccountFE>();

            public List<AccountFE> ReadFromDB()
            {

                string sqlcommand = " SQl query command";

                try
                {            
                    List<AccountFE> accounts = Database.ReadTable(sqlcommand);                            

                    return accounts;
                }
                catch (Exception ex)
                {
                    throw;
                }    
            }
    }

Here is the SQL Class.这是 SQL Class。 This is not that important.这不是那么重要。 This is only to show why i used Generic for this class.这只是为了说明为什么我对这个 class 使用 Generic。 (Serialization and Deserialization) (序列化和反序列化)

public class SQLGeneralDB<T> where T : class, ISQLGeneralDB, new() 
    { 
       public List<T> ReadTable(string sqlcommand)
           {            
               var selectedlist = new List<T>();
               using (SqlConnection cnn = new 
            SqlConnection(XmlWebConfigReader.GetValueFromWebConfig("connectionstring")))
            {
                cnn.Open();
                using (SqlCommand command = new SqlCommand(sqlcommand, cnn))
                {
                    SqlDataReader dataReader;
                    try
                    {
                        dataReader = command.ExecuteReader();

                        while (dataReader.Read())
                        {
                            object[] objectarray = new object[dataReader.FieldCount];
                            dataReader.GetValues(objectarray);
                            ISQLGeneralDB objectfetcher = new T();

                            selectedlist.Add((T)objectfetcher.FetchRow(objectarray));
                        }
                        return selectedlist;
                    }

                    catch (IndexOutOfRangeException)
                    {
                        throw;
                    }
                    catch (Exception ex)
                    {
                        throw;
                    }
                    dataReader.Close();
                }
                cnn.Close();
            }

        }    
}

interface ISQLGeneralDB for multiple DTOs.多个 DTO 的接口 ISQLGeneralDB。

public interface ISQLGeneralDB { object FetchRow(object[] objectarray); }

i hope that its not to confusing and that i explained it good.我希望它不要混淆,我解释得很好。

Some general considerations:一些一般性考虑:

  1. Abstract your dependencies to interfaces.将您的依赖关系抽象为接口。
  2. Inject you dependencies eg via constructor: public AccountsLogic(ISQLGeneralDB db){}注入你的依赖,例如通过构造函数:public AccountsLogic(ISQLGeneralDB db){}
  3. In the test, mock your dependencies, eg with moq在测试中,模拟您的依赖项,例如使用moq

Now let's apply it to your code.现在让我们将它应用到您的代码中。 If you want to abstract your dependency, you need to abstract the SQLGeneralDB as well, eg:如果要抽象依赖关系,还需要抽象 SQLGeneralDB,例如:

public interface IMyDbInterface<T> where T : class, ISQLGeneralDB, new() {
    List<T> ReadTable(string sqlcommand);
}

public class SQLGeneralDB<T> : IMyDbInterface<T> where T : class, ISQLGeneralDB, new() {

    //...

}

and than mock the IMyDbInterface<AccountFE> :而不是模拟IMyDbInterface<AccountFE>

var myDbMock = new Mock<IMyDbInterface<AccountsLogic>();
myDbMock.Setup(c => c.ReadTable(It.Is<string>(d => d....your condition....).Returns(....whatever you want to return....);

Although that would be rather complicated.虽然那会相当复杂。 I'd personally would go here with the repository pattern , so something along the lines:我个人会在这里使用存储库模式go ,所以大致如下:

public interface IRepository<T> where T: class, IEntity
{
  IEnumerable<T> GetAll();
}

public interface IEntity
{
  // E.g.:
  long Id {get;}
}

public class Account : IEntity { ... }

public class AccountRepository : IRepository<Account>
{
  public IEnumerable<Account> GetAll()
  {
    // ... your stuff with SQL commands
  }
}

And than you mock it all.而不是你嘲笑这一切。 Your repository knows how to get accounts, including that SQL command, not the calling code.您的存储库知道如何获取帐户,包括 SQL 命令,而不是调用代码。

The fact that your class is generic doesn't make it special in relation to mocking.您的 class 是通用的这一事实并不能使它与 mocking 相比具有特殊性。 You just mock a generic implementation.你只是模拟一个通用的实现。

Many are getting confused by the interface in the question.许多人对问题中的界面感到困惑。 The generic class is not derived from that interface, the generic argument of the class is what is derived from the interface.通用 class 不是从该接口派生的,class 的通用参数是从该接口派生的。

Current code is tightly coupled to implementation concerns that make it difficult to test it in isolation (unit test)当前代码与实现问题紧密耦合,这使得很难单独对其进行测试(单元测试)

Classes should depend on abstractions and not concretions.类应该依赖于抽象而不是具体。 In this case the generic class should have a generic abstractions.在这种情况下,通用 class 应该具有通用抽象。

The generic interface would look something like the following.通用接口如下所示。

public interface ISqlDatabase<T> where T : class, ISQLGeneralDB, new() {
    List<T> ReadTable(string sqlcommand);
}

The current naming choices may cause some confusion.当前的命名选择可能会引起一些混乱。 The implementation would then be derived from this abstraction然后将从此抽象派生实现

public class DefaultSqlDatabase<T> : ISqlDatabase<T> where T : class, ISQLGeneralDB, new() {
    private IDbConnectionFactory factory;

    public DefaultSqlDatabase(IDbConnectionFactory factory) {
        this.factory = factory;
    }

    public List<T> ReadTable(string sqlcommand) {
        var selectedlist = new List<T>();
        using (IDbConnection connection = factory.CreateConnection()) {
            connection.Open();
            using (IDbCommand command = connection.CreateCommand()) {
                try {
                    command.CommandText = sqlcommand;
                    using (var dataReader = command.ExecuteReader()) {
                        while (dataReader.Read()) {
                            object[] objectarray = new object[dataReader.FieldCount];
                            dataReader.GetValues(objectarray);
                            ISQLGeneralDB objectfetcher = new T();
                            selectedlist.Add((T)objectfetcher.FetchRow(objectarray));
                        }
                        return selectedlist;
                    }
                } catch (IndexOutOfRangeException) {
                    throw;
                } catch (Exception ex) {
                    throw;
                }
            }
        }
    }
}

Note the supporting types注意支持类型

public interface IDbConnectionFactory {
    IDbConnection CreateConnection();
}

public class SqlConnectionFactory : IDbConnectionFactory {
    public IDbConnection CreateConnection() {
        return new SqlConnection(XmlWebConfigReader.GetValueFromWebConfig("connectionstring"));
    }
}

The subject class would be refactored to be dependent on the abstraction主题 class 将被重构为依赖于抽象

public class AccountsLogic {
    private readonly ISqlDatabase<AccountFE> database;

    public AccountsLogic(ISqlDatabase<AccountFE> database) {
        this.database = database;
    }

    public List<AccountFE> ReadFromDB() {
        string sqlcommand = " SQl query command";
        try {}
            List<AccountFE> accounts = database.ReadTable(sqlcommand);
            return accounts;
        } catch (Exception ex) {
            throw;
        }
    }
}

which can be mock as needed when testing AccountsLogic in isolation, since it is no longer dependent on implementation concerns.在单独测试AccountsLogic时可以根据需要进行模拟,因为它不再依赖于实现问题。

At run time all implementations can be mapped to their abstractions and added to the composition root for injection into their dependent classes.在运行时,所有实现都可以映射到它们的抽象并添加到组合根以注入到它们的依赖类中。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM