c# - 我怎样才能将这个工厂类型的方法和数据库调用重构为可测试的?

标签 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);
}

}

这两个方法在一个类中。 GetAgentFromDatabase中的数据库相关代码与Enterprise Libraries相关。

我怎样才能让它变得可测试?我应该将 GetAgentFromDatabase 方法抽象到另一个类中吗? GetAgentFromDatabase 应该返回 IDataReader 以外的东西吗?任何建议或指向外部链接的指针都将不胜感激。

最佳答案

关于将 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 接口(interface),如下所示:

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

因此,AgentRepository 是被测类。我们将模拟 IAgentDataProvider 并注入(inject)依赖项。 (我是用 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 对象并将它们转换为格式正确的Agent 对象。您可以扩展上面的测试夹具来测试更多有趣的案例。

在独立于实际数据库对 AgentRepository 进行单元测试后,您将需要对 IAgentDataProvider 的具体实现进行单元测试,但这是一个单独问题的主题。

关于c# - 我怎样才能将这个工厂类型的方法和数据库调用重构为可测试的?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1233486/

相关文章:

c# - .NET 的 Array.Sort() 方法使用哪种排序算法?

unit-testing - Kotlin 机器人。断言 assertEquals widtch Pair.Failed 测试

python - django模板中的模拟方法调用

swift - 在 Swift 中模拟单例/sharedInstance

Delphi-Mocks:在构造函数中使用参数模拟类

c# - 我应该将存储库接口(interface)与域模型分离吗

c# - 如何抽象 ASP.NET 页面中的属性

c# - 修改edmx模板文件

unit-testing - 按契约(Contract)设计,编写测试友好的代码,对象构造和依赖注入(inject)将所有最佳实践结合在一起

java - 对象内部方法的模拟创建