我怎么能重构这个工厂类型的方法和数据库调用是可测试的?

Jam*_*ers 12 c# unit-testing mocking isolation-frameworks

我正在努力学习如何进行单元测试和模拟.我理解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);
}

}
Run Code Online (Sandbox Code Playgroud)

这两种方法属于单一类.GetAgentFromDatabase中与数据库相关的代码与Enterprise Libraries相关.

我怎样才能使这个可测试?我应该将GetAgentFromDatabase方法抽象为另一个类吗?GetAgentFromDatabase应该返回除IDataReader之外的其他内容吗?任何建议或指向外部链接将不胜感激.

azh*_*lov 9

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

我在哪里定义了IAgentDataProvider接口,如下所示:

public interface IAgentDataProvider {
    IDataReader GetAgent( int agentId );
}
Run Code Online (Sandbox Code Playgroud)

因此,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() { ... };
    }
}
Run Code Online (Sandbox Code Playgroud)

(我省略了类FakeAgentDataReader的实现,它实现了IDataReader并且很简单 - 你只需要实现Read()Dispose()来使测试工作.)

这里AgentRepository的目的是获取IDataReader对象并将它们转换为正确形成的Agent对象.您可以扩展上述测试夹具以测试更有趣的案例.

在将AgentRepository与实际数据库隔离开来进行单元测试后,您将需要对IAgentDataProvider的具体实现进行单元测试,但这是一个单独问题的主题.HTH