이 팩토리 유형 메서드와 데이터베이스 호출을 테스트 가능하도록 리팩토링하려면 어떻게 해야 합니까?

StackOverflow https://stackoverflow.com/questions/1233486

문제

단위 테스트 및 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);
}

}

이 두 메서드는 단일 클래스에 있습니다.GetAgentFromDatabase의 데이터베이스 관련 코드는 엔터프라이즈 라이브러리와 관련되어 있습니다.

이것을 테스트 가능하게 만들려면 어떻게 해야 합니까?GetAgentFromDatabase 메서드를 다른 클래스로 추상화해야 합니까?GetAgentFromDatabase가 IDataReader가 아닌 다른 것을 반환해야 합니까?외부 링크에 대한 제안이나 조언을 주시면 감사하겠습니다.

도움이 되었습니까?

해결책

당신은 움직일 때 맞습니다 getagentfromdatabase () 별도의 수업으로. 내가 재정의 한 방법은 다음과 같습니다 에이전트 재구성:

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

그래서, 에이전트 재구성 테스트중인 수업입니다. 우리는 조롱 할 것이다 iAgentDataprovider 의존성을 주입하십시오. (나는 그것을 가지고 있었다 모크, 그러나 다른 격리 프레임 워크로 쉽게 다시 실행할 수 있습니다).

[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 그리고 사소한 일입니다. 구현하면됩니다 읽다() 그리고 dispose () 테스트를 작동시키기 위해.)

목적 에이전트 재구성 다음은 가져갈 것입니다 Idatareader 물체를 제대로 형성하고 바꿉니다 에이전트 사물. 위의 테스트 고정물을 확장하여 더 흥미로운 사례를 테스트 할 수 있습니다.

단위 테스트 후 에이전트 재구성 실제 데이터베이스와 분리하여 구체적인 구현을 위해 단위 테스트가 필요합니다. iAgentDataprovider, 그러나 그것은 별도의 질문에 대한 주제입니다. HTH

다른 팁

여기서 문제는 SUT와 테스트가 무엇인지 결정하는 것입니다. 당신의 예제와 함께 당신은 그것을 테스트하려고합니다 Select() 따라서 메소드를 데이터베이스에서 분리하려고합니다. 몇 가지 선택이 있습니다.

  1. 가상화 GetAgentFromDatabase() 따라서 올바른 값을 반환하기 위해 파생 클래스에 코드를 제공 할 수 있습니다.이 경우 제공하는 객체를 만듭니다. IDataReaderFunctionaity DB와 이야기하지 않고

    class MyDerivedExample : YourUnnamedClass
    {
        protected override IDataReader GetAgentFromDatabase()
        {
            return new MyDataReader({"AgentId", "1"}, {"FirstName", "Fred"},
              ...);
        }
    }
    
  2. 처럼 Gishu는 제안했다 ISA 관계 (상속)를 사용하는 대신, 모의 생성을 처리하는 수업이 다시있는 Hasa (객체 구성)를 사용합니다. IDataReader, 그러나 이번에는 상속없이.

    그러나이 두 가지 결과는 쿼리시 반환되는 일련의 결과를 단순히 정의하는 많은 코드가 있습니다. 분명히 우리는이 코드를 메인 코드 대신 테스트 코드에 보관할 수 있지만 그 노력은 노력할 수 있습니다. 당신이 정말로하는 일은 특정 쿼리에 대한 결과 세트를 정의하는 것입니다.

  3. 나는 잠시 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")]. 하나의 테스트 세트 만 특정 데이터베이스에 대해 실행 중인지 확인하십시오.

나는 몇 가지 아이디어를 제시하기 시작하고 그 과정에서 업데이트할 것입니다.

  • SqlDatabase sqlDb = new SqlDatabase("MyConnectionString");- 피해야 한다 새로운 연산자가 논리와 혼동되었습니다.xor 논리 연산을 구성해야 합니다.동시에 발생하지 않도록 하세요.종속성 주입을 사용하여 이 데이터베이스를 매개변수로 전달하면 이를 모의할 수 있습니다.단위 테스트를 하려는 경우에 의미합니다(데이터베이스로 이동하지 않고 나중에 수행해야 함).
  • IDataReader 에이전트 정보 = GetAgentFromDatabase(agentId) - 리더 검색을 다른 클래스로 분리하여 팩토리 코드를 테스트하는 동안 이 클래스를 모의할 수 있습니다.

IMO 일반적으로 공개 속성/방법을 테스트 할 수있는 것에 대해서만 걱정해야합니다. 즉, 길게 선택 (int agentid) 일반적으로 당신은 그것이 어떻게 그것을 통해 그것을하는지 신경 쓰지 않습니다. getagentfromdatabase (int agentid).

다음과 같은 것으로 테스트 할 수 있다고 생각하기 때문에 합리적으로 보입니다 (수업이 Agentrepository라고 가정 함).

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

제안 된 개선 사항에 관해서는. 공개 또는 내부 액세스로 에이전트 리포피토리의 연결 문자열을 변경하는 것이 좋습니다.

공개 클래스의 대중 선택 메소드 [Noname]를 테스트하려고한다고 가정합니다.

  1. getAgentFromDatabase () 메소드를 인터페이스로 이동하십시오. Noname에 CTOR 매개 변수 또는 속성으로 설정할 수있는 인터페이스 멤버가 있도록하십시오. 이제 이음새가 있으면 메소드의 코드를 수정하지 않고 동작을 변경할 수 있습니다.
  2. 위의 방법의 반환 유형을 변경하여보다 일반적인 것을 반환합니다. 해시 가능처럼 사용하는 것 같습니다. IDB_ACCESS의 생산 구현이 IDATAREADER를 사용하여 내부적으로 해시 테이블을 생성하도록하십시오. 또한 기술 의존성이 적습니다. MySQL 또는 비 MS/.NET 환경을 사용 하여이 인터페이스를 구현할 수 있습니다.private Hashtable GetAgentFromDatabase(int agentId)
  3. 다음으로 단위 테스트의 경우 스터브로 작업하거나 모의 프레임 워크처럼 더 발전된 것을 사용할 수 있습니다).

.

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...

내 의견은 getAgentFromDatabase () 메소드가 추가 테스트를 통해 테스트가되어서는 안됩니다. 코드는 select () 메소드의 테스트로 완전히 덮여 있기 때문입니다. 코드가 걸을 수있는 가지가 없으므로 여기에 추가 테스트를 할 필요가 없습니다. GetAgentFromDatabase () 메소드가 여러 메소드에서 호출되면 자체적으로 테스트해야합니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top