我正在努力学习如何进行单元测试和模拟。我了解 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/