С# модульное тестирование, издевательство над хранимой процедурой

В прошлом, когда я реализовывал модульные тесты, я изо всех сил пытался настроить «приличные» модульные тесты для слоев доступа к данным, потому что они часто имеют базу данных в качестве внешней зависимости. В идеальном мире я бы издевался над вызовами хранимых процедур, чтобы удалить внешнюю зависимость.

Однако мне не удалось понять, как это сделать с помощью MOQ mocking framework, или найти любую другую инфраструктуру, которая это поддерживает. Вместо этого я вернулся к созданию тестовой базы данных по сценарию с известными данными (чтобы я всегда мог получить ожидаемые результаты), но это немного отличается от имитации этого слоя.

Может ли кто-нибудь предложить, как смоделировать эту часть уровня доступа к данным [я знаю, что для Entity Framework https://effort.codeplex.com/ существует]?


Подробности Например, если у меня есть следующий метод

public object RunStoredProc()
{
    //Some Setup

    using (SqlConnection conn = new SqlConnection(CONNNECTION_STRING))
    {
        using (SqlCommand comm = new SqlCommand("storedProcName", conn))
        {
            conn.Open();
            comm.CommandType = CommandType.StoredProcedure;
            using (SqlDataReader reader = comm.ExecuteReader())
            {
                while (reader.Read())
                {
                    //Logic
                }
            }
        }
    }

    //Return object based on logic
}

тогда как мне смоделировать вывод хранимой процедуры, чтобы SQLDataReader содержал указанные данные. На более высоком уровне я мог бы имитировать метод RunStoredProc(), но это не поможет мне проверить правильность логики этого метода. В качестве альтернативы я мог бы выделить SQLReader другим методом.

public object RunStoredProc()
{
    //Some Setup

    List<object> data = GetData();
    //Logic

    //Return object based on logic
}

private List<object> GetData()
{
    using (SqlConnection conn = new SqlConnection(CONNNECTION_STRING))
    {
        using (SqlCommand comm = new SqlCommand("storedProcName", conn))
        {
            conn.Open();
            comm.CommandType = CommandType.StoredProcedure;
            using (SqlDataReader reader = comm.ExecuteReader())
            {
                while (reader.Read())
                {
                    //place into return object
                }
            }
        }
    }
}

но поскольку метод «GetData» должен быть закрытым (не частью опубликованного интерфейса), я тогда не смогу издеваться над этим, и поэтому проблема остается.


person C. Knight    schedule 15.08.2016    source источник


Ответы (1)


Я думаю, что у нас есть все интерфейсы (IDbConnection, IDbTransaction, IDbCommand, IDataReader) и заимствование идеи из EF (IDbConnectionFactory) для абстрагирования всего необходимого, чтобы их можно было имитировать и использовать с внедрением зависимостей. Я думаю, что SqlConnection и остальные - это скорее детали реализации, и их можно абстрагировать.

Следуя идее из Entity Framework, вы можете создать фабрику соединений.

public interface IDbConnectionFactory {
    /// <summary>
    ///  Creates a connection based on the given database name or connection string.
    IDbConnection CreateConnection(string nameOrConnectionString);
}

Затем вы можете реорганизовать свой примерный метод, чтобы использовать только абстракции.

public class MyDataAccessClass {
    private IDbConnectionFactory dbConnectionFactory;
    private string CONNNECTION_STRING = "Connection string here";

    public MyDataAccessClass(IDbConnectionFactory dbConnectionFactory) {
        this.dbConnectionFactory = dbConnectionFactory;
    }

    public object RunStoredProc() {
        //Some Setup
        List<object> result = new List<object>();

        using (IDbConnection conn = dbConnectionFactory.CreateConnection(CONNNECTION_STRING)) {
            using (IDbCommand comm = conn.CreateCommand()) {
                comm.CommandText = "storedProcName";
                conn.Open();
                comm.CommandType = CommandType.StoredProcedure;
                using (IDataReader reader = comm.ExecuteReader()) {
                    while (reader.Read()) {
                        //...Logic to populate result
                    }
                }
            }
        }

        //Return object based on logic
        return result;
    }
}

Оттуда вы имитируете интерфейсы, используя выбранную вами фиктивную среду, или создаете свои собственные подделки для внедрения и тестирования вашего метода.

[TestClass]
public class StoredProcedureUnitTest {
    [TestMethod]
    public void TestRunStoredProc() {
        //Arrange
        var connectionFactory = new Mock<IDbConnectionFactory>();
        //..Setup...

        var sut = new MyDataAccessClass(connectionFactory.Object);

        //Act
        var actual = sut.RunStoredProc();

        //Assert
        //...
    }
}
person Nkosi    schedule 15.08.2016
comment
спасибо, похоже, это сработает, если я правильно читаю, единственный метод, который мне нужно будет имитировать, это IDbCommand.ExecuteReader() [мне также нужно будет имитировать IDbConnection.CreatCommand, чтобы вернуть имитированный IDbCommand, но это должно быть тривиальным]. Я собираюсь оставить вопрос открытым на некоторое время, если у кого-то еще есть какие-либо идеи. - person C. Knight; 15.08.2016