Подтвердить что ты не робот

Как я могу реорганизовать этот метод factory -type и вызов базы данных для проверки?

Я пытаюсь научиться делать Unit Testing и Mocking. Я понимаю некоторые принципы TDD и базового тестирования. Тем не менее, я смотрю на рефакторинг ниже кода, который был написан без тестов, и я пытаюсь понять, как это нужно изменить, чтобы сделать его проверяемым.

public class AgentRepository
{

public Agent Select(int agentId)
{
    Agent tmp = null;
    using (IDataReader agentInformation = GetAgentFromDatabase(agentId))
    {
        if (agentInformation.Read())
        {
            tmp = new Agent();
            tmp.AgentId = int.Parse(agentInformation["AgentId"].ToString());
            tmp.FirstName = agentInformation["FirstName"].ToString();
            tmp.LastName = agentInformation["LastName"].ToString();
            tmp.Address1 = agentInformation["Address1"].ToString();
            tmp.Address2 = agentInformation["Address2"].ToString();
            tmp.City = agentInformation["City"].ToString();
            tmp.State = agentInformation["State"].ToString();
            tmp.PostalCode = agentInformation["PostalCode"].ToString();
            tmp.PhoneNumber = agentInformation["PhoneNumber"].ToString();
        }
    }

    return tmp;
}

private IDataReader GetAgentFromDatabase(int agentId)
{
    SqlCommand cmd = new SqlCommand("SelectAgentById");
    cmd.CommandType = CommandType.StoredProcedure;

    SqlDatabase sqlDb = new SqlDatabase("MyConnectionString");
    sqlDb.AddInParameter(cmd, "AgentId", DbType.Int32, agentId);
    return sqlDb.ExecuteReader(cmd);
}

}

Эти два метода находятся в одном классе. Код, связанный с базой данных в базе данных GetAgentFrom, связан с корпоративными библиотеками.

Как я могу сделать это для проверки? Должен ли я абстрагировать метод GetAgentFromDatabase в другом классе? Должен ли GetAgentFromDatabase возвращать нечто иное, чем IDataReader? Любые предложения или указатели на внешние ссылки были бы весьма полезны.

4b9b3361

Ответ 1

Вы правы в перемещении GetAgentFromDatabase() в отдельный класс. Вот как я переопределил AgentRepository:

public class AgentRepository {
    private IAgentDataProvider m_provider;

    public AgentRepository( IAgentDataProvider provider ) {
        m_provider = provider;
    }

    public Agent GetAgent( int agentId ) {
        Agent agent = null;
        using( IDataReader agentDataReader = m_provider.GetAgent( agentId ) ) {
            if( agentDataReader.Read() ) {
                agent = new Agent();
                // set agent properties later
            }
        }
        return agent;
    }
}

где я определил интерфейс IAgentDataProvider следующим образом:

public interface IAgentDataProvider {
    IDataReader GetAgent( int agentId );
}

Итак, AgentRepository - тестируемый класс. Мы будем издеваться над IAgentDataProvider и введем зависимость. (Я сделал это с помощью Moq, но вы можете легко переделать его с другой средой изоляции).

[TestFixture]
public class AgentRepositoryTest {
    private AgentRepository m_repo;
    private Mock<IAgentDataProvider> m_mockProvider;

    [SetUp]
    public void CaseSetup() {
        m_mockProvider = new Mock<IAgentDataProvider>();
        m_repo = new AgentRepository( m_mockProvider.Object );
    }

    [TearDown]
    public void CaseTeardown() {
        m_mockProvider.Verify();
    }

    [Test]
    public void AgentFactory_OnEmptyDataReader_ShouldReturnNull() {
        m_mockProvider
            .Setup( p => p.GetAgent( It.IsAny<int>() ) )
            .Returns<int>( id => GetEmptyAgentDataReader() );
        Agent agent = m_repo.GetAgent( 1 );
        Assert.IsNull( agent );
    }

    [Test]
    public void AgentFactory_OnNonemptyDataReader_ShouldReturnAgent_WithFieldsPopulated() {
        m_mockProvider
            .Setup( p => p.GetAgent( It.IsAny<int>() ) )
            .Returns<int>( id => GetSampleNonEmptyAgentDataReader() );
        Agent agent = m_repo.GetAgent( 1 );
        Assert.IsNotNull( agent );
                    // verify more agent properties later
    }

    private IDataReader GetEmptyAgentDataReader() {
        return new FakeAgentDataReader() { ... };
    }

    private IDataReader GetSampleNonEmptyAgentDataReader() {
        return new FakeAgentDataReader() { ... };
    }
}

(я отказался от реализации класса FakeAgentDataReader, который реализует IDataReader и является тривиальным - вам нужно реализовать Read() и Dispose(), чтобы тесты работали.)

Цель AgentRepository состоит в том, чтобы взять объекты IDataReader и превратить их в правильно сформированные объекты Агент. Вы можете расширить вышеуказанное тестовое устройство, чтобы проверить более интересные случаи.

После модульного тестирования AgentRepository в изоляции от фактической базы данных вам понадобятся модульные тесты для конкретной реализации IAgentDataProvider, но это тема для отдельного вопроса. НТН

Ответ 2

Проблема здесь в том, чтобы решить, что такое SUT и что такое Test. В вашем примере вы пытаетесь протестировать метод Select() и, следовательно, хотите выделить это из базы данных. У вас есть несколько вариантов,

  • Виртуализируйте GetAgentFromDatabase(), чтобы вы могли предоставить производный класс с кодом для возврата правильных значений, в этом случае создавая объект, который предоставляет IDataReaderFunctionaity, не разговаривая с БД i.e.

    class MyDerivedExample : YourUnnamedClass
    {
        protected override IDataReader GetAgentFromDatabase()
        {
            return new MyDataReader({"AgentId", "1"}, {"FirstName", "Fred"},
              ...);
        }
    }
    
  • Как Гишу предложил вместо использования связей IsA (наследование) использовать HasA (композиция объекта), где у вас снова есть класс, который обрабатывает создание mock IDataReader, но на этот раз без наследования.

    Однако оба из них приводят к большому количеству кода, который просто определяет набор результатов, которые мы возвращаем при запросе. По общему признанию, мы можем сохранить этот код в тестовом коде вместо нашего основного кода, но его усилия. Все, что вы действительно делаете, это определение набора результатов для определенных запросов, и вы знаете, что действительно хорошо при этом... База данных

  • Я использовал LinqToSQL некоторое время назад и обнаружил, что объекты DataContext имеют некоторые очень полезные методы, включая DeleteDatabase и CreateDatabase.

    public const string UnitTestConnection = "Data Source=.;Initial Catalog=MyAppUnitTest;Integrated Security=True";
    
    
    [FixtureSetUp()]
    public void Setup()
    {
      OARsDataContext context = new MyAppDataContext(UnitTestConnection);
    
      if (context.DatabaseExists())
      {
        Console.WriteLine("Removing exisitng test database");
        context.DeleteDatabase();
      }
      Console.WriteLine("Creating new test database");
      context.CreateDatabase();
    
      context.SubmitChanges();
    }
    

Рассмотрим это некоторое время. Проблема с использованием базы данных для модульных тестов заключается в том, что данные будут изменены. Удалите свою базу данных и используйте свои тесты для развития ваших данных, которые могут быть использованы в будущих тестах.

Есть две вещи, которые нужно соблюдать осторожно: Убедитесь, что ваши тесты выполняются в правильном порядке. Синтаксис MbUnit для этого - [DependsOn("NameOfPreviousTest")]. Убедитесь, что для конкретной базы данных работает только один набор тестов.

Ответ 3

Я начну выдвигать некоторые идеи и буду обновлять по пути:

  • SqlDatabase sqlDb = новая SqlDatabase ( "MyConnectionString" ); - Вам следует избегать новых операторов, смешанных с логикой. Вы должны построить xor иметь логические операции; избегайте их в одно и то же время. Используйте инъекцию зависимости, чтобы передать эту базу данных в качестве параметра, чтобы вы могли ее издеваться. Я имею в виду это, если вы хотите unit test (он не идет в базу данных, что должно быть сделано в некоторых случаях позже)
  • IDataReader agentInformation = GetAgentFromDatabase (agentId). Возможно, вы можете отделить извлечение Reader к другому классу, поэтому вы можете издеваться над этим классом при тестировании кода factory.

Ответ 4

ИМО, вы должны, как правило, беспокоиться только о том, чтобы ваши общедоступные свойства/методы проверялись. То есть до тех пор, пока работает Select (int agentId), вам, как правило, не важно, как это делается через GetAgentFromDatabase (int agentId).

То, что у вас кажется разумным, поскольку я предполагаю, что его можно протестировать с чем-то вроде следующего (предполагая, что ваш класс называется AgentRepository)

AgentRepository aRepo = new AgentRepository();
int agentId = 1;
Agent a = aRepo.Select(agentId);
//Check a here

Что касается предлагаемых улучшений. Я бы рекомендовал изменить строку соединения AgentRepository, либо путем открытого или внутреннего доступа.

Ответ 5

Предполагая, что вы пытаетесь протестировать общедоступный метод выбора класса [NoName]..

  • Переместите метод GetAgentFromDatabase() в интерфейс, например, IDB_Access. Пусть NoName имеет член интерфейса, который может быть установлен как параметр ctor или свойство. Итак, теперь у вас есть шов, вы можете изменить поведение, не изменяя код в методе.
  • Я бы изменил возвращаемый тип вышеупомянутого метода, чтобы вернуть что-то более общее - похоже, вы используете его как хэш-таблицу. Пусть производственная реализация IDB_Access использует IDataReader для создания хэш-таблицы внутри. Это также делает его менее зависимым от технологии; Я могу реализовать этот интерфейс, используя MySql или некоторую среду, отличную от MS/.net. private Hashtable GetAgentFromDatabase(int agentId)
  • Далее для вашего unit test, вы можете работать с заглушкой (или использовать что-то более продвинутое, как фреймворк)

.

public MockDB_Access : IDB_Access
{
  public const string MY_NAME = "SomeName;
  public Hashtable GetAgentFromDatabase(int agentId)
  {  var hash = new Hashtable();
     hash["FirstName"] = MY_NAME; // fill other properties as well
     return hash;
  }
}

// in the unit test
var testSubject = new NoName( new MockDB_Access() );
var agent = testSubject.Select(1);
Assert.AreEqual(MockDB_Access.MY_NAME, agent.FirstName); // and so on...

Ответ 6

По моему мнению, метод GetAgentFromDatabase() не должен быть testet дополнительным тестом, потому что его код полностью покрывается тестом метода Select(). Нет никаких ветвей, код которых мог бы продвигаться, поэтому нет смысла создавать дополнительный тест здесь. Если метод GetAgentFromDatabase() вызывается из нескольких методов, вы должны проверить его самостоятельно, хотя.