티스토리 툴바


분류없음2009/10/09 01:44

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using NUnit.Framework;
using Rhino.Mocks;
using Rhino.Mocks.Constraints;
using Rhino.Mocks.Impl;
using Rhino.Mocks.Interfaces;

namespace LogAnalyzer
{
  [TestFixture]
  public class LogAnalyzerTests
  {
    [Test]
    public void Analyze()
    {
      MockRepository mocks = new MockRepository();
      IWebService simulatedService =
        //mocks.StrictMock();
        mocks.DynamicMock();

      using( mocks.Record() )
      {
        simulatedService.LogError("too short:abc.txt");
      }

      LogAnalyzer log = new LogAnalyzer(simulatedService);
      log.Analyze("abc.txt");

      //mocks.Verify(simulatedService);
      mocks.VerifyAll();
    }

    [Test]
    public void ReturnResultsFromMock()
    {
      MockRepository mocks = new MockRepository();
      IGetResults resultGetter = mocks.DynamicMock();

      using( mocks.Record() )
      {
        resultGetter.GetSomeNumber("a");
        LastCall.Return(1);

        resultGetter.GetSomeNumber("b");
        LastCall.Return(2);

        resultGetter.GetSomeNumber("c");
        LastCall.Return(3);
      }

      int result = resultGetter.GetSomeNumber("b");
      Assert.AreEqual(2, result);

      int result2 = resultGetter.GetSomeNumber("a");
      Assert.AreEqual(1, result2);

      int result3 = resultGetter.GetSomeNumber("c");
      Assert.AreEqual(3, result3);

      mocks.VerifyAll();
    }

    [Test]
    public void ReturnResultsFromStub()
    {
      MockRepository mocks = new MockRepository();
      IGetResults result = mocks.Stub();

      using( mocks.Record() )
      {
        result.GetSomeNumber("a");
        LastCall.Return(1);
      }

      int res = result.GetSomeNumber("a");
      Assert.AreEqual(1, res);
    }

    [Test]
    public void StubSimulatingException()
    {
      MockRepository mocks = new MockRepository();
      IGetResults result = mocks.Stub();

      using( mocks.Record() )
      {
        result.GetSomeNumber("A");
        LastCall.Throw(new OutOfMemoryException("not enough"));
      }

      //result.GetSomeNumber("A");
    }

    [Test]
    public void Analyze_WebServiceThrows_SendEmail()
    {
      MockRepository mocks = new MockRepository();
      IWebService stubService = mocks.Stub();
      IEmailService mockEmail = mocks.DynamicMock();

      using( mocks.Record() )
      {
        stubService.LogError("whatever");
        LastCall.Constraints(Rhino.Mocks.Constraints.Is.Anything());
        LastCall.Throw(new Exception("fake exception"));
        mockEmail.SendEmail("a", "subject", "fake exception");
      }

      LogAnalyzer log = new LogAnalyzer();
      log.Service = stubService;
      log.Email = mockEmail;

      string tooShortFileName = "abc.txt";
      log.Analyze(tooShortFileName);

      mocks.VerifyAll();
    }
  }

  public interface IGetResults
  {
    int GetSomeNumber(string str);
  }

  public class ManualMockService : IWebService
  {
    public string LastError;

    public void LogError(string message)
    {
      LastError = message;
    }
  }

  public interface IEmailService
  {
    void SendEmail(string to, string subject, string message);
  }

  public class LogAnalyzer
  {
    private IWebService service;
    private IEmailService email;

    public LogAnalyzer(IWebService service)
    {
      this.service = service;
    }

    public LogAnalyzer() { }


    public IWebService Service
    {
      get { return service; }
      set { service = value; }
    }

    public IEmailService Email
    {
      get { return email; }
      set { email = value; }
    }

    public void Analyze(string fileName)
    {
      if( fileName.Length < 8 )
      {
        try
        {
          service.LogError("too short:" + fileName);
        }
        catch( Exception e )
        {
          email.SendEmail("a", "subject", e.Message);
        }

      }

    }
  }


  public interface IWebService
  {
    void LogError(string message);
  }

  class Program
  {
    static void Main(string[] args)
    {
    }
  }
}
the art of unit testing에서 적당히 짬뽕....;;;
저작자 표시 비영리 동일 조건 변경 허락
Posted by nekolatte nekolatte
TAG PRG, TDD
분류없음2009/10/09 00:34
Rhino Mocks의 두 가지 모델 중에 하나인 record-and-replay 모델 예제.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using NUnit.Framework;
using Rhino.Mocks;

namespace LogAnalyzer
{
  [TestFixture]
  public class LogAnalyzerTests
  {
    [Test]
    public void Analyze()
    {
      MockRepository mocks = new MockRepository();
      IWebService simulatedService =
        mocks.StrictMock();

      using( mocks.Record() )
      {
        simulatedService.LogError("too short:abc.txt");
      }

      LogAnalyzer log = new LogAnalyzer(simulatedService);
      log.Analyze("abc.txt");

      mocks.Verify(simulatedService);
    }
  }

  public class ManualMockService : IWebService
  {
    public string LastError;

    public void LogError(string message)
    {
      LastError = message;
    }
  }

  public interface IEmailService
  {
    void SendEmail(string to, string subject, string message);
  }

  public class LogAnalyzer
  {
    private IWebService service;
    private IEmailService email;

    public LogAnalyzer(IWebService service)
    {
      this.service = service;
    }

    public IWebService Service
    {
      get { return service; }
      set { service = value; }
    }

    public IEmailService Email
    {
      get { return email; }
      set { email = value; }
    }

    public void Analyze(string fileName)
    {
      if( fileName.Length < 8 )
      {
        try
        {
          service.LogError("too short:" + fileName);
        }
        catch( Exception e )
        {
          email.SendEmail("a", "subject", e.Message);
        }

      }

    }
  }


  public interface IWebService
  {
    void LogError(string message);
  }

  class Program
  {
    static void Main(string[] args)
    {
    }
  }
}

저작자 표시 비영리 동일 조건 변경 허락
Posted by nekolatte nekolatte
TAG PRG, TDD
분류없음2009/10/07 21:37

  public interface IDataAccessLayer
  {
    string GetData(int id);
  }

데이터베이스에서 데이터를 가져온다든가 하면
객체가 강하게 결합되어 있는데 틈새(Seam)를 만들어주고,
인터페이스로부터 Mock 객체를 만들면 테스트를 쉽게
할 수 있다.

moq를 사용하고, 위 인터페이스에 대해 동작하는 코드는
다음처럼 만들어서 테스트할 수 있다.


    [TestMethod]
    public void TestMethod1()
    {
      //
      // TODO: Add test logic here
      //
      var dataAccess = new Mock<IDataAccessLayer>();
      dataAccess.Setup(p => p.GetData(1)).Returns("hello");

      Assert.AreEqual("hello", dataAccess.Object.GetData(1));
      }

저작자 표시 비영리 동일 조건 변경 허락
Posted by nekolatte nekolatte
TAG moq, PRG, TDD
분류없음2009/10/07 21:20
public class LogAnalyzer
{
  public bool IsValidLogFileName(string fileName)
  {
    return true;
  }
}

코드가 이런식으로 구현되어 있다면 테스트하기 어렵다.
LogAnalyzer는 반드시 파일시스템과 직접 연결되어 있다.
여기에 테스트 코드를 넣기는 어렵다. 이럴 때는 코드를 아래와 같이
수정해야 한다.
public class LogAnalyzer
{
  public bool IsValidLogFileName(string fileName)
  {
    IExtensionManager mgr = new FileExtensionManager();
    return mgr.IsValid(fileName);
  }
}

public interface IExtensionManager
{
  bool IsValid(string fileName);
}

public class FileExtensionManager: IExtenstionManager
{
  public bool IsValid(string fileName)
  {
    return true;
  }
}

public class StubExtensionManager : IExtensionManager
{
  public bool IsValid(string fileName)
  {
    return true;
  }
}

 

IExtensionManager를 소개해서 실제 파일시스템과 연동하는
FileExtensionManager와 더미 데이터와 동작을 만들어내는
StubExtensionManager를 서로 바꿔가며 사용할 수 있다.

public class LogAnalyzer
{
  private IExtensionManager manager;

  public LogAnalyzer()
  {
    manager = new FileExtensionManager();
  }

  public LogAnalyzer(IExtensionManager mgr)
  {
    manager = mgr;
  }

  public bool IsValidLogFileName(string fileName)
  {
    return manager.IsValid(fileName);
  }
}

public interface IExtensionManager
{
  bool IsValid(string fileName);
}

public class FileExtensionManager : IExtenstionManager
{
  public bool IsValid(string fileName)
  {
    return true;
  }
}

public class StubExtensionManager : IExtensionManager
{
  public bool IsValid(string fileName)
  {
    return true;
  }
}
생성자 주입(Constructor Injection)을 이용한 방법으로 바꿀 수도 있다.
기본 생성자는 파일시스템을 이용하고,
두 번째 생성자는 직접 사용할 클래스를 선택하게 되어 있다.

속성 주입(property injection)을 이용한 방법으로 바꿀 수도 있다.

public class LogAnalyzer
{
  private IExtensionManager manager;

  public IExtensionManager ExtensionManager
  {
    get { return manager; }
    set { manager = value; }
  }

  public bool IsValidLogFileName(string fileName)
  {
    return manager.IsValid(fileName);
  }
}
어떤 방법을 선택해도 괜찮다.
여기서 얻을 수 있는 것은 강하게 결합된 코드에 틈새(Seam)를 만들어주는 것이다.
틈새를 만드는 방법은 다양하지만, 여기서는 인터페이스를 이용했다.
이처럼 간접층(Indirection Layer)를 추가해 결합을 느슨하게 만들면
틈새(Seam)가 생기고, 그 틈새에 테스트를 넣기가 좋다.
ASP.NET이 테스트하기 어려웠던 이유는 클래스가 지나치게 강하게 묶여 있었기
때문이었던 것이고, ASP.NET MVC가 테스트하기 좋은 이유는
클래스가 느슨하게 연결되어 있기 때문이다.
그래서 ASP.NET에서 TDD는 어려웠지만, ASP.NET MVC에서는 TDD가 쉽다.
위 코드는 <The art of unit testing>에서 가져왔음.

ps. 보통 테스트용으로 사용할 데이터를 반환하는 코드를 추가하고
이를 스텁 코드(Stub Code)라 부른다. 하지만, 이와 달리 테스트 용이성을 위해
스텁 코드를 추가할 수 있는 간접층을 추가하는 것이고, 이를 틈새 주입(Seam Injection)이라 부른다.
그래서 딱히 용어를 스텁, 틈새를 엄격하게 구분하지 않았다.
(사실, 구분할만한 내공도 없다)
Seam Injection에 대해서는 <Working Effectively with Legacy Code> 참고.
 
ps2. 책에는 factory method로 구현하는 버전도 있는데 별 필요없어서 생략함.
저작자 표시 비영리 동일 조건 변경 허락
Posted by nekolatte nekolatte
TAG PRG, TDD