과도한 조롱 필요성으로 인한 취성 단위 테스트


21

팀에서 구현하고있는 단위 테스트와 관련하여 점점 성가신 문제로 어려움을 겪고 있습니다. 우리는 잘 설계되지 않은 레거시 코드에 단위 테스트를 추가하려고 시도하고 실제로 테스트를 추가하는 데 어려움이 없었지만 테스트가 어떻게 진행되는지에 어려움을 겪고 있습니다.

문제의 예로, 실행의 일부로 5 개의 다른 메서드를 호출하는 메서드가 있다고 가정 해 봅시다. 이 방법에 대한 테스트는 이러한 5 가지 다른 방법 중 하나의 결과로 동작이 발생하는지 확인하는 것입니다. 따라서 단위 테스트는 한 가지 이유만으로 실패하기 때문에 이러한 다른 4 가지 메소드를 호출하여 발생하는 잠재적 인 문제를 제거하고이를 모방하려고합니다. 큰! 단위 테스트가 실행되고, 조롱 된 메소드가 무시되고 (다른 단위 테스트의 일부로 동작이 확인 될 수 있음) 확인이 작동합니다.

그러나 새로운 문제가 있습니다-단위 테스트는 미래에 다른 4 가지 방법으로 동작과 서명이 변경되었음을 확인하는 방법에 대한 친밀한 지식 또는 '부모 방법'에 추가 해야하는 새로운 방법이 있습니다. 가능한 실패를 피하기 위해 단위 테스트를 변경해야합니다.

당연히 더 많은 방법으로 더 적은 행동을하게함으로써 문제를 다소 완화 할 수 있었지만 아마도 더 우아한 솔루션이 가능하기를 바랐습니다.

다음은 문제를 포착하는 예제 유닛 테스트입니다.

빠른 참고로 'MergeTests'는 테스트중인 클래스에서 상속하고 필요에 따라 동작을 무시하는 단위 테스트 클래스입니다. 이것은 외부 클래스 / 종속성에 대한 호출을 재정의 할 수 있도록 테스트에서 사용하는 '패턴'입니다.

[TestMethod]
public void VerifyMergeStopsSpinner()
{
    var mockViewModel = new Mock<MergeTests> { CallBase = true };
    var mockMergeInfo = new MergeInfo(Mock.Of<IClaim>(), Mock.Of<IClaim>(), It.IsAny<bool>());

    mockViewModel.Setup(m => m.ClaimView).Returns(Mock.Of<IClaimView>);
    mockViewModel.Setup(
        m =>
        m.TryMergeClaims(It.IsAny<Func<bool>>(), It.IsAny<IClaim>(), It.IsAny<IClaim>(), It.IsAny<bool>(),
                         It.IsAny<bool>()));
    mockViewModel.Setup(m => m.GetSourceClaimAndTargetClaimByMergeState(It.IsAny<MergeState>())).Returns(mockMergeInfo);
    mockViewModel.Setup(m => m.SwitchToOverviewTab());
    mockViewModel.Setup(m => m.IncrementSaveRequiredNotification());
    mockViewModel.Setup(m => m.OnValidateAndSaveAll(It.IsAny<object>()));
    mockViewModel.Setup(m => m.ProcessPendingActions(It.IsAny<string>()));

    mockViewModel.Object.OnMerge(It.IsAny<MergeState>());    

    mockViewModel.Verify(mvm => mvm.StopSpinner(), Times.Once());
}

당신의 나머지는 이것을 어떻게 다루었습니까? 아니면 그것을 처리하는 훌륭한 '간단한'방법이 없습니까?

업데이트-모든 사람의 의견에 감사드립니다. 불행히도, 놀랍지도 않고, 테스트되는 코드가 열악한 경우 단위 테스트에서 따를 수있는 훌륭한 솔루션, 패턴 또는 연습이없는 것 같습니다. 나는이 단순한 진실을 가장 잘 포착 한 답을 표시했다.


와우, 모의 설정, SUT 인스턴스화 등이 보이지 않습니다. 실제 구현을 테스트하고 있습니까? 누가 StopSpinner를 호출해야합니까? 온 머지? 당신은 그것이 부를 수는 있지만 그 자체가 아닌 의존성을 조롱해야합니다.
Joppe

보기 힘들지만 Mock <MergeTests>는 SUT입니다. CallOn 플래그를 설정하여 'OnMerge'메소드가 실제 객체에서 실행되도록하지만 'OnMerge'에 의해 호출 된 메소드를 모방하여 종속성 문제 등으로 인해 테스트가 실패 할 수 있습니다. 테스트의 목표는 마지막 행입니다 -이 경우 스피너를 중지했는지 확인하십시오.
PremiumTier

MergeTests는 다른 계측 클래스처럼 들리므로 프로덕션 환경에 혼동되지 않습니다.
Joppe


1
다른 문제를 제외하고는 SUT가 Mock <MergeTests>이라는 것이 잘못되었습니다. 왜 모의를 테스트하겠습니까? 왜 MergeTests 클래스 자체를 테스트하지 않습니까?
Eric King

답변:


18
  1. 코드가 더 잘 설계되도록 수정하십시오. 테스트에 이러한 문제가 있으면 변경하려고 할 때 코드에 더 나쁜 문제가 있습니다.

  2. 당신이 할 수 없다면 아마도 덜 이상적이어야 할 것입니다. 분석법의 사전 및 사후 조건에 대해 테스트합니다. 다른 5 가지 방법을 사용하고 있다면 누가 신경 쓰나요? 아마도 자체 단위 테스트를 수행하여 테스트 실패시 실패 원인을 명확하게합니다.

"단위 테스트는 실패 할 이유가 하나만 있어야합니다"는 좋은 지침이지만 내 경험상 비현실적입니다. 쓰기 어려운 테스트는 작성되지 않습니다. 깨지기 쉬운 테스트는 믿지 않습니다.


나는 코드의 디자인을 고치는 것에 완전히 동의하지만, 촉박 한 타임 라인을 가진 대기업을위한 이상적인 개발 환경에서는 과거 팀이나 잘못된 의사 결정으로 인해 발생한 기술 부채를 '지급'하는 것이 어려울 수 있습니다. 일단. 두 번째 요점으로, 많은 조롱은 단지 한 가지 이유로 테스트가 실패하기를 원하기 때문이 아닙니다. 실행중인 코드가 해당 코드 내에 생성 된 많은 수의 종속성을 먼저 처리하지 않고는 실행될 수 없기 때문입니다. . 목표 게시물을 이동하여 죄송합니다.
PremiumTier

더 나은 디자인이 현실적이지 않다면 '다른 5 가지 방법을 사용하는 사람은 누구입니까?'에 동의합니다. 메소드가 수행하는 방식이 아니라 필요한 기능을 수행하는지 확인하십시오.
Kwebble

@Kwebble-이해하지만 문제의 목표는 테스트를 전혀 실행하기 위해 메소드 내에서 호출 된 다른 동작을 모의해야 할 때 메소드의 동작을 확인하는 간단한 방법이 있는지 확인하는 것이 었습니다. '방법'을 제거하고 싶지만 방법을 모르겠습니다. :)
PremiumTier

마법의은 총알이 없습니다. 열악한 코드를 테스트하는 "간단한 방법"은 없습니다. 테스트 대상 코드를 리팩터링해야하거나 테스트 코드 자체도 좋지 않습니다. 테스트는 내부 세부 사항에 너무 구체적이거나, btilly가 제안한 대로 작업 환경에 대해 테스트를 실행할 수 있지만 테스트가 훨씬 느리고 복잡하기 때문에 좋지 않습니다. 어느 쪽이든, 테스트는 작성하기가 어렵고, 유지하기가 어렵고, 부정적 경향이 있습니다.
Steven Doggart

8

큰 방법을보다 집중된 작은 방법으로 나누는 것이 가장 좋습니다. 단위 테스트 동작을 확인하는 데 어려움이 있지만 다른 방법으로 고통을 겪고 있습니다.

즉, 이것은 이단이지만 개인적으로 현실적인 임시 테스트 환경을 만드는 팬입니다. 즉, 다른 방법 안에 숨겨져있는 모든 것을 모방하는 대신 임시 환경을 쉽게 설정할 수 있는지 확인하십시오 (개인 데이터베이스 및 스키마로 완료-SQLite가 여기에 도움이 될 수 있음). 그러면 모든 것을 실행할 수 있습니다. 해당 테스트 환경을 구축 / 해체하는 방법을 아는 책임은이를 요구하는 코드에 따라 달라 지므로 변경 될 때 존재 여부에 따라 모든 단위 테스트 코드를 변경할 필요는 없습니다.

그러나 나는 이것이 나의 이단임을 주목한다. 단위 테스트를 많이하는 사람들은 "순수한"단위 테스트를 옹호하고 내가 "통합 테스트"라고 설명한 것을 부릅니다. 나는 그 차이에 대해 개인적으로 걱정하지 않습니다.


3

나는 모의를 완화하고 호출하는 메소드를 포함 할 수있는 테스트를 공식화하려고합니다.

방법 을 테스트 하지 말고 무엇을 테스트하십시오 . 중요한 결과는 필요한 경우 하위 방법을 포함시키는 것입니다.

다른 각도에서 테스트를 공식화하고 하나의 큰 방법으로 리팩토링하고 리팩토링 한 후 메소드 트리로 끝날 수 있습니다. 각각을 개별적으로 테스트 할 필요는 없습니다. 중요한 것은 최종 결과입니다.

하위 메소드로 인해 일부 측면을 테스트하기 어려운 경우 별도의 클래스로 분류하여 테스트중인 클래스를 크게 계측 / 봉인하지 않고도 더 깔끔하게 조롱 할 수 있습니다. 예제 테스트에서 실제 구현을 테스트하고 있는지 말하기는 어렵습니다.


문제는 '무엇을'테스트하기 위해 '어떻게'를 조롱해야한다는 것입니다. 코드 디자인에 의해 부과 된 제한 사항입니다. 나는 그것이 시험을 취하기 쉬운 방법으로 '모의'하고 싶지는 않다.
PremiumTier

메서드 이름을 보면 테스트 된 클래스가 너무 많은 책임을 맡고 있다고 생각합니다. 단일 책임 원칙을 읽으십시오. MVC에서 빌리면 약간 도움이 될 수 있습니다. 클래스는 UI, 인프라 및 비즈니스 문제를 모두 처리하는 것으로 보입니다.
Joppe

예 :( 저는 저 잘못 설계 한 레거시 코드 일 것입니다. 우리는 재 설계 및 리팩토링 작업을하고 있지만 소스를 먼저 테스트하는 것이 최선이라고 생각했습니다.
PremiumTier
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.