문제

C#, Nunit 및 Rhino Mocks, 그것이 적용 가능한 것으로 판명되면.

복잡한 기능을 중심으로 테스트를 마무리하려고 시도하면서 TDD와의 퀘스트가 계속됩니다. 저장 될 때 저장 될 때 양식 내에 종속 객체를 저장 해야하는 양식을 코딩하고 있다고 가정 해 봅시다. 질문에 대한 답변, 질문에 대한 답변, 가능한 경우 첨부 파일 및 "로그"항목 (예 : "Blahblah가 양식을 업데이트했습니다"또는 "블라 블라는 파일을 첨부했다."). 이 저장 기능은 또한 저장 기능 중에 양식 상태가 어떻게 변경되었는지에 따라 다양한 사람들에게 이메일을 발사합니다.

즉, 모든 종속성으로 양식의 저장 기능을 완전히 테스트하기 위해서는이 하나의 기능을 테스트하고 올바른 방식으로 모든 것이 발사되도록 5-6 개의 데이터 제공 업체를 주입해야합니다. 이는 조롱 된 제공 업체를 삽입하기 위해 양식 개체에 대한 다중 체인 생성자를 작성할 때 번거 롭습니다. 나는 리팩토링을 방해하거나 조롱 된 데이터 제공 업체를 설정하는 더 나은 방법으로 무언가를 놓치고 있다고 생각합니다.

이 기능을 단순화 할 수있는 방법을 확인하기 위해 리팩토링 방법을 추가로 연구해야합니까? 옵저버 패턴 소리는 어떻습니까? 종속 물체가 부모 형태가 저장되고 스스로 처리 될 때를 감지 할 수 있습니까? 사람들은 기능을 분할하여 테스트 할 수 있다고 말합니다. 즉, 각 종속 객체의 개별 저장 기능을 테스트하지만 양식 자체의 저장 기능은 아닙니다. 처음?

도움이 되었습니까?

해결책

오토킹 컨테이너를 사용하십시오. Rhinomocks를 위해 쓰여진 것이 있습니다.

생성자 주입을 통해 많은 종속성이 주입 된 클래스가 있다고 상상해보십시오. 다음은 Rhinomocks, Automocking 컨테이너가없는 것으로 보이는 모습입니다.

private MockRepository _mocks;
private BroadcastListViewPresenter _presenter;
private IBroadcastListView _view;
private IAddNewBroadcastEventBroker _addNewBroadcastEventBroker;
private IBroadcastService _broadcastService;
private IChannelService _channelService;
private IDeviceService _deviceService;
private IDialogFactory _dialogFactory;
private IMessageBoxService _messageBoxService;
private ITouchScreenService _touchScreenService;
private IDeviceBroadcastFactory _deviceBroadcastFactory;
private IFileBroadcastFactory _fileBroadcastFactory;
private IBroadcastServiceCallback _broadcastServiceCallback;
private IChannelServiceCallback _channelServiceCallback;

[SetUp]
public void SetUp()
{
    _mocks = new MockRepository();
    _view = _mocks.DynamicMock<IBroadcastListView>();

    _addNewBroadcastEventBroker = _mocks.DynamicMock<IAddNewBroadcastEventBroker>();

    _broadcastService = _mocks.DynamicMock<IBroadcastService>();
    _channelService = _mocks.DynamicMock<IChannelService>();
    _deviceService = _mocks.DynamicMock<IDeviceService>();
    _dialogFactory = _mocks.DynamicMock<IDialogFactory>();
    _messageBoxService = _mocks.DynamicMock<IMessageBoxService>();
    _touchScreenService = _mocks.DynamicMock<ITouchScreenService>();
    _deviceBroadcastFactory = _mocks.DynamicMock<IDeviceBroadcastFactory>();
    _fileBroadcastFactory = _mocks.DynamicMock<IFileBroadcastFactory>();
    _broadcastServiceCallback = _mocks.DynamicMock<IBroadcastServiceCallback>();
    _channelServiceCallback = _mocks.DynamicMock<IChannelServiceCallback>();


    _presenter = new BroadcastListViewPresenter(
        _addNewBroadcastEventBroker,
        _broadcastService,
        _channelService,
        _deviceService,
        _dialogFactory,
        _messageBoxService,
        _touchScreenService,
        _deviceBroadcastFactory,
        _fileBroadcastFactory,
        _broadcastServiceCallback,
        _channelServiceCallback);

    _presenter.View = _view;
}

이제 Automocking 컨테이너와 같은 것이 있습니다.

private MockRepository _mocks;
private AutoMockingContainer _container;
private BroadcastListViewPresenter _presenter;
private IBroadcastListView _view;

[SetUp]
public void SetUp()
{

    _mocks = new MockRepository();
    _container = new AutoMockingContainer(_mocks);
    _container.Initialize();

    _view = _mocks.DynamicMock<IBroadcastListView>();
    _presenter = _container.Create<BroadcastListViewPresenter>();
    _presenter.View = _view;

}

더 쉬워요.

Automocking 컨테이너는 생성자의 모든 종속성에 대한 모의를 자동으로 생성하며 다음과 같은 테스트에 액세스 할 수 있습니다.

using (_mocks.Record())
    {
      _container.Get<IChannelService>().Expect(cs => cs.ChannelIsBroadcasting(channel)).Return(false);
      _container.Get<IBroadcastService>().Expect(bs => bs.Start(8));
    }

도움이되기를 바랍니다. Automocking 컨테이너의 출현으로 테스트 수명이 훨씬 쉬워 졌다는 것을 알고 있습니다.

다른 팁

먼저, TDD를 따르는 경우 복잡한 기능을 중심으로 테스트를 마무리하지 않습니다. 테스트 주위에 기능을 감싸십시오. 사실, 그조차도 옳지 않습니다. 당신은 테스트와 함수를 짜서 거의 정확히 같은 시간에 작문을 작성하고, 테스트는 기능보다 조금 앞서 있습니다. 보다 TDD의 세 가지 법칙.

이 세 가지 법칙을 따르고 리팩토링에 부지런히 할 때는 "복잡한 기능"으로 결코 감기되지 않습니다. 오히려 당신은 많은 테스트를 거친 간단한 기능으로 감습니다.

이제 당신의 요점에. 이미 "복잡한 기능"이 있고 그 주위에 테스트를 마무리하려면 다음을해야합니다.

  1. DI를 통해서 대신 모의를 명시 적으로 추가하십시오. (예 : '테스트'플래그와 같은 끔찍한 것과 실제 객체 대신 모의를 선택하는 'if'문장).
  2. 구성 요소의 기본 작동을 다루기 위해 몇 가지 테스트를 작성하십시오.
  3. Refactor는 무자비하게, 복잡한 기능을 많은 작은 간단한 기능으로 나누고 가능한 한 자주 함께 테스트를 실행하면서 복잡한 기능을 분류합니다.
  4. '테스트'플래그를 가능한 한 높게 밀어 넣으십시오. 리팩터를 리팩터링 할 때 데이터 소스를 작은 간단한 기능으로 전달하십시오. '테스트'플래그가 최상위 기능 외에는 감염되지 않도록하십시오.
  5. 테스트를 다시 작성하십시오. 리팩터를 리팩토링하면 가능한 많은 테스트를 다시 작성하여 큰 최상위 기능 대신 간단한 작은 기능을 호출하십시오. 조롱을 테스트에서 간단한 기능으로 전달할 수 있습니다.
  6. '테스트'플래그를 제거하고 실제로 얼마나 필요한지 결정하십시오. Areguments를 통해 모의를 삽입 할 수있는 낮은 레벨로 작성된 테스트가 있기 때문에 더 이상 최상위 수준에서 많은 데이터 소스를 조롱 할 필요가 없습니다.

이 결국 DI가 여전히 번거롭고 모든 데이터 소스에 대한 참조를 보유하는 단일 객체를 주입하는 것에 대해 생각해보십시오. 많은 사람들보다 한 가지를 주사하는 것이 항상 쉽습니다.

당신은 그것이 번거 롭을 수 있다는 것이 옳습니다.

조롱 방법론의 지지자는 코드가 함께있는 것으로 부적절하게 기록되어 있다고 지적 할 것이다. 즉,이 방법 내에서 종속 객체를 구성해서는 안됩니다. 오히려, 주입 API는 적절한 객체를 만드는 기능이 있어야합니다.

6 개의 다른 물체를 조롱하는 것은 사실입니다. 그러나 단위 테스트 인 경우 저것들 시스템, 이러한 객체에는 이미 사용할 수있는 조롱 인프라가 있어야합니다.

마지막으로, 일부 작업을 수행하는 조롱 프레임 워크를 사용하십시오.

코드가 없지만 첫 번째 반응은 귀하의 테스트가 귀하의 객체에 너무 많은 공동 작업자를 가지고 있음을 알려주려고한다는 것입니다. 이와 같은 경우, 나는 항상 더 높은 레벨 구조로 포장 해야하는 구조물이 누락되어 있음을 알게됩니다. 오토막 컨테이너를 사용하면 테스트에서 얻은 피드백만을 고집합니다. 보다 http://www.mockobjects.com/2007/04/test-smell-bloated-constructor.html 더 긴 토론을 위해.

이러한 맥락에서, 나는 보통 "이것은 당신의 객체가 너무 많은 종속성을 가지고 있음을 나타냅니다"또는 "당신의 객체에는 너무 많은 협업자가 있음을 나타냅니다." 물론 MVC 컨트롤러 또는 양식은 의무를 이행하기 위해 많은 다른 서비스와 물건을 부를 것입니다. 결국, 그것은 응용 프로그램의 최상층에 앉아 있습니다. 이러한 종속성 중 일부를 더 높은 수준의 객체 (예 : 배송 메토 드레 포지티브 및 TransittimeCalculator는 ShippingRateFinder로 결합)로 함께 뿌릴 수 있지만, 이는 특히 이러한 최상위 프리젠 테이션 지향 개체에 대해서만 진행됩니다. 이는 조롱에 대한 객체가 적지 만, 하나의 간접 계층을 통해 실제 종속성을 난독 화했는데 실제로 제거하지는 않았습니다.

한 가지 신성 모독 조언은 당신이 객체를 주입하고 그에 대한 인터페이스를 생성하는 경우, 그에 대한 인터페이스를 만들지 않는다면 (코드를 변경하는 동안 실제로 새로운 MessageBoxService를 삭제 할 것인가?) 귀찮게하지 마십시오. 그 종속성은 객체의 예상 동작의 일부이며 통합 테스트는 실제 비즈니스 가치가있는 위치이므로 함께 테스트해야합니다.

다른 신성 모독 조언은 일반적으로 MVC 컨트롤러 또는 Windows 형태를 테스트하는 데 유용한 유틸리티가 거의 보이지 않는다는 것입니다. 누군가가 httpcontext를 조롱하고 쿠키가 설정되어 있는지 테스트하는 것을 볼 때마다 비명을 지르고 싶습니다. AccountController가 쿠키를 설정하면 누가 신경 쓰나요? 나는 아니에요. 쿠키는 컨트롤러를 블랙 박스로 취급하는 것과 관련이 없습니다. 통합 테스트는 기능을 테스트하는 데 필요한 것입니다 (HMM, 통합 테스트에서 login () 후에는 ExperileDarea ()에 대한 호출이 실패)입니다. 이런 식으로 로그인 쿠키 형식이 변경되면 백만 개의 쓸모없는 단위 테스트를 무효화하지 않습니다.

객체 모델의 단위 테스트를 저장하고 프리젠 테이션 계층의 통합 테스트를 저장하며 가능한 경우 모의 개체를 피하십시오. 특정 의존성을 조롱하는 것이 어려운 경우, 실용적이어야합니다. 단위 테스트를하지 말고 대신 통합 테스트를 작성하지 말고 시간을 낭비하지 마십시오.

간단한 대답은 테스트하려는 코드입니다. 너무 많이하고 있습니다. 나는 생각을 고수한다고 생각한다 단일 책임 원칙 도움이 될 수 있습니다.

저장 버튼 메소드 다른 개체에 물건을 위임하기 위해 최상위 호출 만 포함해야합니다.. 그런 다음이 객체는 인터페이스를 통해 추상화 될 수 있습니다. 그런 다음 저장 버튼 방법을 테스트하면 상호 작용 만 테스트합니다. 조롱 된 물체.

다음 단계는 이러한 하위 수준 클래스에 테스트를 작성하는 것이지만,이를 분리하여 테스트하기 때문에 더 쉬워 질 것입니다. 복잡한 테스트 설정 코드가 필요한 경우, 이것은 잘못된 디자인 (또는 나쁜 테스트 접근법)의 좋은 지표입니다.

권장 독서 :

  1. 클린 코드 : 민첩한 소프트웨어 장인 핸드북
  2. 테스트 가능한 코드 작성에 대한 구글 가이드

생성자 DI만이 DI를 수행하는 유일한 방법은 아닙니다. C#을 사용하고 있으므로 생성자가 중요한 작업을 수행하지 않으면 속성 DI를 사용할 수 있습니다. 그것은 당신의 기능의 복잡성을 희생시키면서 물체의 생성자 측면에서 사물을 크게 단순화합니다. 당신의 기능은 종속 속성의 무효 수준을 점검하고 작동하기 전에 무효화 된 경우 무효화를 던져야합니다.

무언가를 테스트하기 어려운 경우, 일반적으로 코드 품질의 증상이며 코드를 테스트 할 수 없습니다 (언급 이 팟 캐스트, IIRC). 권장 사항은 코드를 쉽게 테스트 할 수 있도록 코드를 리팩터링하는 것입니다. 코드를 클래스로 분할하는 방법을 결정하기위한 일부 휴리스틱은 다음과 같습니다. SRP 및 OCP. 보다 구체적인 지침을 위해서는 해당 코드를 볼 필요가 있습니다.

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