Pergunta

Estou tentando definir uma maneira de simular um caso para acessar um banco de dados sem acessar ... isso provavelmente parece muito louco, mas não é.

Aqui está um exemplo sobre um método que eu gostaria de testar:

    public IDevice GetDeviceFromRepository(string name)
    {
        IDevice device = null;
        IDbConnection connection = new SqlConnection(ConnectionString);
        connection.Open();
        try
        {
            IDbCommand command = connection.CreateCommand();
            command.CommandText = string.Format("SELECT DEVICE_ID,DEVICE_NAME FROM DEVICE WHERE DEVICE_NAME='{0}'", name);
            IDataReader dataReader = command.ExecuteReader();
            if(dataReader.NextResult())
            {
                device = new Device(dataReader.GetInt32(0),dataReader.GetString(1));
            }
        }
        finally
        {
            connection.Close();
        }
        return device;
    }

Estou fingindo zombar do idatarader para poder controlar o que está sendo lido. Algo assim (usando a estrutura MOQ):

    [TestMethod()]
    public void GetDeviceFromRepositoryTest()
    {
        Mock<IDataReader> dataReaderMock = new Mock<IDataReader>();
        dataReaderMock.Setup(x => x.NextResult()).Returns(true);
        dataReaderMock.Setup(x => x.GetInt32(0)).Returns(000);
        dataReaderMock.Setup(x => x.GetString(1)).Returns("myName");
        Mock<IDbCommand> commandMock = new Mock<IDbCommand>();
        commandMock.Setup(x => x.ExecuteReader()).Returns(dataReaderMock.Object);
        Mock<RemoveDeviceManager> removeMock = new Mock<RemoveDeviceManager>();
        removeMock.Setup()
        RemoveDeviceManager target =new RemoveDeviceManager(new Device(000, "myName")); 
        string name = string.Empty; 
        IDevice expected = new Device(000, "myName"); // TODO: Initialize to an appropriate value
        IDevice actual;
        actual = target.GetDeviceFromRepository(name);
        Assert.AreEqual(expected.SerialNumber, actual.SerialNumber);
        Assert.AreEqual(expected.Name, actual.Name);
    }

Minha pergunta é se posso forçar o método getDeviceFromRepository para substituir o IDATAREADER por ritmo.

Foi útil?

Solução

Embora você esteja atualmente use MOQ, acho que a funcionalidade que você está procurando não pode ser alcançada sem injeção de dependência, a menos que você use o isolador de tipo de tipo (Isenção de responsabilidade - eu trabalhei na TypeMock).

O isolador possui um recurso chamado "Objetos futuros" que permitem substituir uma instanciação futura de um objeto por um objeto falso criado anteriormente:

 // Create fake (stub/mock whateever) objects
 var fakeSqlConnection = Isolate.Fake.Instance<SqlConnection>();
 var fakeCommand = Isolate.Fake.Instance<SqlCommand>();
 Isolate.WhenCalled(() => fakeSqlConnection.CreateCommand()).WillReturn(fakeCommand);

 var fakeReader = Isolate.Fake.Instance<SqlDataReader>();
 Isolate.WhenCalled(() => fakeCommand.ExecuteReader()).WillReturn(fakeReader);

 // Next time SQLConnection is instantiated replace with our fake
 Isolate.Swap.NextInstance<SqlConnection>().With(fakeSqlConnection);

Outras dicas

Eu acho que o problema aqui é sua dependência direta, em última análise, para SQLConnection. Se você usaria alguma variante da injeção de dependência, de modo que seu código obtenha acesso ao IDBCommand sem saber como ela é construída, você poderá injetar sua simulação sem muito aborrecimento.

Entendo que isso não responde à sua pergunta, mas, a longo prazo, fazer coisas conforme descrito lhe dará muito melhor testabilidade.

Concordo com a resposta de Frank de que mover-se para a injeção de dependência é a melhor solução de longo prazo, mas existem algumas etapas intermediárias que você pode tomar para movê-lo nessa direção sem morder tudo.

Uma coisa é mover a construção da classe do IDBConnection para um método virtual protegido dentro de sua classe:

protected virtual IDbConnection CreateConnection()
{
    return new SqlConnection(ConnectionString);
}

Em seguida, você pode criar uma versão de teste da sua classe como assim:

public class TestingRemoteDeviceManager : RemoteDeviceManager
{
   public override IDbConnection CreateConnection()
   {
         IDbConnection conn = new Mock<IDbConnection>();
         //mock out the rest of the interface, as well as the IDbCommand and
         //IDataReader interfaces
         return conn;
   }
}

Isso retorna uma IDBConnection simulada ou falsa em vez da sqlConnection de concreto. Esse falso pode retornar um objeto Fake IDBCommand, que pode retornar um objeto IDATAREADER FALSO.

O mantra é teste até que o medo seja transformado em tédio. Eu acho que você cruzou essa linha aqui. Se você assumir o controle do leitor de dados, o único código que você está testando é o seguinte:

device = new Device(dataReader.GetInt32(0),dataReader.GetString(1));

Não há quase nada para testar aqui, o que é bom: a camada de dados deve ser simples e estúpida. Portanto, não tente testar sua camada de dados. Se você sentir que deve testá-lo, teste-o de integração em um banco de dados real.

Claro, esconder sua camada de dados atrás de um IDeviceRepository interface para que você possa zombar facilmente para testar outro O código ainda é uma boa ideia.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top