정적은 단위 테스트를 위해 보편적으로“악”입니까? 그렇다면 Resharper가 권장하는 이유는 무엇입니까? [닫은]


85

C # .NET에서 정적 인 단위 테스트 (모의 / 스텁) 종속성을 3 가지 방법으로 만 발견했습니다.

이 중 두 가지가 무료가 아니며 하나가 릴리스 1.0에 도달하지 않았으므로 정적 항목을 조롱하는 것은 쉽지 않습니다.

정적 메소드와 "악한"(단위 테스트 의미)을합니까? 그렇다면 왜 resharper가 정적, 정적 일 수있는 것을 만들고 싶습니까? (리 샤퍼를 가정하는 것도 "악"이 아닙니다.)

설명 : 메서드를 단위 테스트하고 다른 단위 / 클래스 에서 정적 메서드를 호출하는 시나리오에 대해 이야기하고 있습니다 . 단위 테스트에 대한 대부분의 정의에 따르면 테스트중인 메소드가 다른 단위 / 클래스의 정적 메소드를 호출하게하면 단위 테스트 가 아닌 통합 테스트입니다. (유용하지만 단위 테스트는 아님)


3
TheLQ : 가능합니다. 나는 정적 변수에 많은 시간이 걸리기 때문에 정적 메소드를 테스트 할 수 없다는 것에 대해 이야기하고 있다고 생각합니다. 따라서 테스트 후와 테스트 사이의 상태를 변경합니다.

26
개인적으로 나는 당신이 "단위"를 너무 멀리 정의하고 있다고 생각합니다. "단위"는 "독립적으로 테스트하는 것이 가장 작은 단위"여야합니다. 그것은 방법 일 수도 있고, 그 이상일 수도 있습니다. 정적 메소드에 상태가없고 제대로 테스트 된 경우 두 번째 단위 테스트 호출이 문제가되지 않습니다.
mlk

10
"개인적으로 나는 당신이"단위 "의 정의를 너무 멀리 생각하고 있다고 생각합니다." 아니요, 표준 사용법을 사용하고 자신의 정의를 작성한다는 것입니다.

7
"resharper는 왜 정적이고 정적 일 수있는 것을 만들려고합니까?" Resharper는 당신이 아무것도 하지 않기를 바랍니다. 그것은 단지 당신이 수정이 가능하다는 것을 인식하고하고있다 어쩌면 코드 분석 POV에서 바람직. Resharper는 귀하의 판단을 대체하지 않습니다!
Adam Naylor

4
@ acidzombie24. 일반 메소드는 정적 상태도 수정할 수 있으므로 정적 메소드처럼 "나쁜"상태가됩니다. 더 짧은 수명 주기로 상태를 수정할 수도 있다는 사실은 더욱 위험합니다. (정적 메소드를 선호하지는 않지만 상태 수정에 대한 요점은 일반 메소드에도 적용됩니다.)
mike30

답변:


105

여기에있는 다른 답변을 살펴보면 정적 상태를 유지하거나 부작용을 일으키는 정적 메소드와 값을 반환하는 정적 메소드 사이에 혼란이있을 수 있습니다.

상태를 유지하지 않고 부작용을 일으키지 않는 정적 방법은 쉽게 단위 테스트가 가능해야합니다. 사실, 나는 그러한 방법을 "가난한"형태의 기능적 프로그래밍이라고 생각한다. 메소드에 오브젝트 또는 값을 전달하면 오브젝트 또는 값을 리턴합니다. 더 이상 없습니다. 그러한 방법이 어떻게 단위 테스트에 부정적인 영향을 미치는지 알 수 없습니다.


44
정적 메소드는 유닛 테스트 가능하지만 정적 메소드를 호출하는 메소드는 어떻습니까? 호출자가 다른 클래스에 있으면 단위 테스트를 수행하기 위해 분리해야하는 종속성이 있습니다.
Vaccano

22
@Vaccano : 그러나 상태를 유지하지 않고 부작용이없는 정적 메소드를 작성하면 어쨌든 기능적으로 스텁과 동등합니다.
Robert Harvey

20
단순한 것일 수도 있습니다. 그러나 더 복잡한 것은 예외를 던지고 예상하지 않은 출력을 가질 수 있으며 (정적 메소드의 호출자에 대한 단위 테스트가 아닌 정적 메소드의 단위 테스트에서 포착되어야 함) 또는 적어도 그것이 내가하는 것입니다 내가 읽은 문헌을 믿게되었습니다.
Vaccano

20
@Vaccano : 출력 또는 임의 예외가있는 정적 메소드에는 부작용이 있습니다.
Tikhon Jelvis

10
@TikhonJelvis Robert 출력에 대해 이야기했습니다. "무작위"예외는 부작용이되어서는 안되며, 본질적으로 출력 형식입니다. 요점은 정적 메소드를 호출하는 메소드를 테스트 할 때마다 해당 메소드 및 잠재적 출력의 모든 순열을 랩핑하며 메소드를 개별적으로 테스트 할 수 없다는 것입니다.
Nicole

26

정적 데이터 와 정적 메소드를 혼동하는 것 같습니다 . Resharper는 올바르게 기억한다면 private클래스 내에서 메소드를 정적으로 만들 것을 권장합니다 . 그것은 하지 않는 정적 "할 수있는 일"을 만드는 것이 좋습니다!

정적 메소드에는 아무런 문제가 없으며 정적 데이터를 변경하지 않는 한 테스트하기 쉽습니다. 예를 들어, 정적 메소드가있는 정적 클래스에 적합한 Maths 라이브러리를 생각해보십시오. 다음과 같은 (고려 된) 방법이 있다면 :

public static long Square(int x)
{
    return x * x;
}

그런 다음 이것은 명백히 테스트 가능하며 부작용이 없습니다. 예를 들어 20을 전달하면 400을 다시 얻습니다. 문제 없습니다.


4
이 정적 메서드를 호출하는 다른 클래스가 있으면 어떻게됩니까? 이 경우 단순 해 보이지만 위에 나열된 세 가지 도구 중 하나를 제외하고는 격리 할 수없는 종속성입니다. 분리하지 않으면 "단위 테스트"가 아니라 "통합 테스트"입니다 (다른 단위가 얼마나 "통합"되는지 테스트하기 때문입니다.
Vaccano

3
아무 반응이 없습니다. 왜 그런가요? .NET 프레임 워크는 정적 메소드로 가득합니다. 단위 테스트를 원하는 방법으로도 사용할 수 없다는 말입니까?
Dan Diplo

3
코드가 .NET Framework의 프로덕션 수준 / 품질 인 경우 계속 진행하십시오. 그러나 단위 테스트의 요점은 단위 테스트를 단독으로 테스트하는 것입니다. 다른 유닛에서 메소드를 호출하는 경우 (정적이든 아니든) 이제 유닛과 해당 종속성을 테스트합니다. 나는 그것이 유용한 테스트가 아니라 대부분의 정의에 의한 "단위 테스트"가 아니라고 말하고있다. (이제 테스트 대상 유닛과 정적 메소드가있는 유닛을 테스트하기 때문에).
Vaccano

10
아마도 당신 (또는 다른 사람들)은 이미 정적 메소드를 테스트했으며 너무 많은 코드를 작성하기 전에 (적어도 예상대로) 작동하는 것으로 나타났습니다. 다음에 테스트 할 부분에서 문제가 발생하면 이미 테스트 한 부분이 아니라 먼저 살펴 봐야합니다.
cHao

6
@Vaccano 그렇다면 Microsoft는 어떻게 .NET Framework를 테스트합니까? Framework의 많은 클래스는 다른 System.Math정적 팩토리 메소드 등을 언급하지 않는 다른 클래스의 정적 메소드를 참조 합니다. 또한 확장 메소드 등을 사용할 수는 없습니다. 현대 언어의 기본. 당신은 이것들을 분리해서 테스트 할 수 있습니다 (일반적으로 결정적이기 때문에). 문제가되지 않습니다!
Dan Diplo

18

여기서 실제 질문이 "이 코드를 어떻게 테스트합니까?"인 경우 :

public class MyClass
{
   public void MethodToTest()
   {
       //... do something
       MyStaticClass.StaticMethod();
       //...more
   }
}

그런 다음 코드를 리팩터링하고 평소처럼 정적 클래스에 대한 호출을 다음과 같이 삽입하십시오.

public class MyClass
{
   private readonly IExecutor _externalExecutor;
   public MyClass(_IExecutor executor)
   {
       _exeternalExecutor = executor;
   }

   public void MethodToTest()
   {
       //... do something
       _exetrnalExecutor.DoWork();
       //...more
   }
}

public class MyStaticClassExecutor : IExecutor
{
    public void DoWork()
    {
        MyStaticClass.StaticMethod();
    }
}

9
다행히 우리는 또한 : SO에 너무 코드의 가독성과 키스에 관한 질문이
gbjbaanb

대의원이 쉽지 않고 같은 일을합니까?
jk.

@jk는 가능하지만 IoC 컨테이너를 사용하기는 어렵습니다.
Sunny

15

스태틱은 반드시 악한 것은 아니지만, 가짜 / 모의 / 스텁을 사용한 유닛 테스트와 관련하여 옵션을 제한 할 수 있습니다.

조롱에는 두 가지 일반적인 접근 방식이 있습니다.

첫 번째 것은 (전통적인-RhinoMocks, Moq, NMock2에 의해 구현되었습니다; 수동 모의와 스텁도이 캠프에 있습니다) 테스트 솔기와 의존성 주입에 의존합니다. 정적 코드를 단위 테스트 중이고 종속성이 있다고 가정하십시오. 이런 식으로 설계된 코드에서 자주 발생하는 것은 정적 변수가 자체 종속성을 만들어 종속성 반전을 반전시키는 것 입니다. 이런 식으로 설계된 테스트중인 코드에 조롱 된 인터페이스를 삽입 할 수 없다는 것을 곧 알게 될 것입니다.

두 번째 것은 TypeMock, JustMock 및 Moles에 의해 구현되는 모의 항목은 .NET의 프로파일 링 API 에 의존 합니다 . CIL 명령어를 가로 채어 코드 덩어리를 가짜로 바꿀 수 있습니다. 따라서이 캠프의 TypeMock 및 기타 제품은 정적, 봉인 된 클래스, 개인 메서드 등 테스트 할 수없는 항목을 조롱 할 수 있습니다.

두 생각 학교 사이에 지속적인 논쟁이 있습니다. 하나는 SOLID 원칙 과 테스트 가능성을위한 설계를 따르고 있다고 말합니다 . 다른 하나는 TypeMock을 구입하고 걱정하지 마십시오.


14

이것을 확인하십시오 : "정적 방법은 테스트 가능성으로 인한 죽음" . 인수에 대한 간단한 요약 :

단위 테스트를 위해서는 작은 코드 조각을 가져 와서 코드를 다시 연결 한 후 격리하여 테스트해야합니다. 전역 상태에 액세스하는 경우뿐만 아니라 다른 정적 메서드를 호출하더라도 정적 메서드로는 어렵습니다.


32
정적 메소드에서 전역 상태를 유지하거나 부작용을 일으키지 않으므로이 논쟁은 나에게 적합하지 않은 것 같습니다. 링크 된 기사는 정적 메소드가 간단한 절차 코드에 국한되어 수학 함수와 같은 "일반적인"방식으로 작동하는 경우 기초가없는 미끄러운 기울기 인수를 만듭니다.
Robert Harvey

4
@Robert Harvey-다른 클래스의 정적 메소드를 사용하는 메소드를 어떻게 단위 테스트합니까 (예 : 정적 메소드에 대한 종속성이 있음). 당신이 그냥 그것을 호출하자 당신은 "단위 테스트"가 아니라 "통합 테스트"
Vaccano

10
블로그 기사를 읽지 말고 동의하지 않는 많은 의견을 읽으십시오. 블로그는 사실이 아니라 의견 일뿐입니다.
Dan Diplo

8
@Vaccano : 부작용이나 외부 상태가없는 정적 방법은 기능적으로 값과 같습니다. 그것을 아는 것은 정수를 생성하는 메소드를 단위 테스트하는 것과 다르지 않습니다. 이것은 함수형 프로그래밍이 제공하는 주요 통찰력 중 하나입니다.
Steven Evers

3
임베디드 시스템이 작동하지 않거나 "테스트 가능성"이 아키텍처를 주도 할 때 버그가 국제적 사고를 일으킬 가능성이있는 앱인 IMO는 우선 테스트를 거쳐야합니다. 또한 모든 대화를 지배하는 XP 버전의 모든 것에 지쳤습니다. XP는 권위가 아니며 산업입니다. 여기에 단위 테스트의 원래, 합리적인 정의는 다음과 같습니다 python.net/crew/tbryan/UnitTestTalk/slide2.html은
에릭 Reppen

5

거의 인정되지 않는 단순한 사실은 클래스에 다른 클래스에 대한 컴파일러 표시 종속성이 포함 된 경우 해당 클래스 와 별도로 테스트 할 수 없다는 것입니다. 테스트처럼 보이는 것을 가짜로 만들 수 있으며 마치 테스트 인 것처럼 보고서에 나타납니다.

그러나 테스트의 주요 정의 속성은 없습니다. 일이 잘못되면 실패하고 옳았을 때 통과합니다.

이것은 정적 호출, 생성자 호출 및 기본 클래스 또는 인터페이스에서 상속되지 않은 메소드 또는 필드에 대한 참조에 적용됩니다 . 클래스 이름이 코드에 나타나면 컴파일러에서 볼 수있는 종속성이므로 해당 클래스 이름 없이는 테스트 할 수 없습니다. 더 작은 청크는 단순히 유효한 테스트 가능한 단위아닙니다 . 그것을 마치 마치 마치 테스트 통과라고 말하는 테스트 프레임 워크에서 사용하는 XML을 생성하는 작은 유틸리티를 작성하는 것보다 더 의미있는 결과를 얻지 못하는 것처럼 취급하려는 시도는 더 의미가 없습니다.

이를 감안할 때 세 가지 옵션이 있습니다.

  1. 클래스로 구성된 유닛을 테스트하기 위해 유닛 테스트를 정의하고 하드 코딩 된 종속성입니다. 순환 종속성을 피할 수 있습니다.

  2. 테스트를 담당하는 클래스간에 컴파일 타임 종속성을 만들지 마십시오. 결과 코드 스타일에 신경 쓰지 않고 작동합니다.

  3. 단위 테스트 대신 통합 테스트를 수행하십시오. 통합 테스트라는 용어를 사용해야하는 다른 것과 충돌하지 않는 한 어느 것이 효과적입니다.


3
단위 테스트는 단일 클래스의 테스트라는 말은 없습니다. 단일 단위의 테스트입니다. 단위 테스트의 정의 속성은 빠르고 반복 가능하며 독립적이라는 것입니다. Math.Pi메소드에서 참조 한다고해서 합리적인 정의에 의한 통합 테스트는 아닙니다.
sara

즉, 옵션 1입니다. 이것이 최선의 방법이라는 데 동의하지만 다른 사람들이 용어를 다르게 (합리적으로 또는 다르게) 사용하는 것을 아는 것이 유용 할 수 있습니다.
soru

4

그것에 대해 두 가지 방법이 없습니다. ReSharper의 제안과 C #의 몇 가지 유용한 기능은 모든 코드에 대해 고립 된 원자 단위 테스트를 작성하는 경우 자주 사용되지 않습니다.

예를 들어, 정적 메소드가 있고이를 스텁해야하는 경우 프로파일 기반 격리 프레임 워크를 사용하지 않으면 불가능합니다. 호출 호환 해결 방법은 람다 표기법을 사용하도록 메서드의 상단을 변경하는 것입니다. 예를 들면 다음과 같습니다.

전에:

    public static DBConnection ConnectToDB( string dbName, string connectionInfo ) {
    }

후:

    public static Func<string, string, DBConnection> ConnectToDB (dbName, connectionInfo ) {
    };

둘은 통화 호환됩니다. 발신자는 변경할 필요가 없습니다. 함수의 본문은 동일하게 유지됩니다.

그런 다음 단위 테스트 코드 에서이 호출을 다음과 같이 스텁 할 수 있습니다 (데이터베이스라는 클래스에 있다고 가정).

        Database.ConnectToDB = (dbName, connectionInfo) => { return null|whatever; }

완료 한 후에는 원래 값으로 교체하십시오. try / finally를 통해 또는 단위 테스트 정리에서 모든 테스트 후에 호출되는 코드를 작성하여 다음과 같이 코드를 작성할 수 있습니다.

    [TestCleanup]
    public void Cleanup()
    {
        typeof(Database).TypeInitializer.Invoke(null, null);
    }

클래스의 정적 초기화 프로그램을 다시 호출합니다.

Lambda Funcs는 일반 정적 메서드만큼 지원이 풍부하지 않으므로이 방법에는 다음과 같은 바람직하지 않은 부작용이 있습니다.

  1. 정적 메소드가 확장 메소드 인 경우 먼저 비 확장 메소드로 변경해야합니다. Resharper는 자동으로이를 수행 할 수 있습니다.
  2. 정적 메서드의 데이터 형식 중 하나가 Office와 같은 내장 된 interop 어셈블리 인 경우 메서드를 래핑하거나 형식을 줄 바꿈하거나 'object'형식으로 변경해야합니다.
  3. 더 이상 Resharper의 변경 서명 리팩토링 도구를 사용할 수 없습니다.

그러나 정적을 모두 피하고 이것을 인스턴스 메소드로 변환한다고 가정 해 봅시다. 메소드가 가상이거나 인터페이스의 일부로 구현되지 않는 한 여전히 조롱 할 수 없습니다.

따라서 실제로 정적 메서드를 스터 빙하는 해결 방법을 제안하는 사람은 인스턴스 메서드를 만드는 것입니다. 가상이거나 인터페이스의 일부가 아닌 인스턴스 메서드에 대해서도 반대입니다.

그렇다면 왜 C #에 정적 메서드가 있습니까? 비가 상 인스턴스 메소드를 허용하는 이유는 무엇입니까?

이 "특징"중 하나를 사용하면 분리 된 메소드를 작성할 수 없습니다.

그래서 언제 사용합니까?

누군가가 스텁하기를 기대하지 않는 코드에 사용하십시오. 몇 가지 예 : String 클래스의 Format () 메소드 Console 클래스의 WriteLine () 메소드 Math 클래스의 Cosh () 메소드

그리고 한 가지 더. 대부분의 사람들은 이것에 신경 쓰지 않지만 간접 호출의 성능에 대해 할 수 있다면 인스턴스 메소드를 피하는 또 다른 이유입니다. 성능이 저하되는 경우가 있습니다. 이것이 비가 상 방법이 처음에 존재하는 이유입니다.


3
  1. 정적 메서드가 인스턴스 메서드보다 호출하는 것이 더 빠르기 때문에 부분적으로 믿습니다. (이것은 마이크로 최적화 냄새가 나기 때문에 따옴표로 표시) http://dotnetperls.com/static-method 참조
  2. 상태가 필요하지 않다는 것을 알려주므로 어디에서나 호출 할 수 있으므로 누군가가 필요한 유일한 경우 인스톨레이션 오버 헤드를 제거하십시오.
  3. 그것을 조롱하고 싶다면 인터페이스에 선언 된 것이 일반적이라고 생각합니다.
  4. 인터페이스에 선언 된 경우 R #은 정적이라고 제안하지 않습니다.
  5. 가상으로 선언 된 경우 R #은 정적이라고 제안하지 않습니다.
  6. 상태 (필드)를 정적으로 유지하는 것은 항상 신중하게 고려해야하는 것입니다 . 정적 상태와 스레드는 리튬 및 물과 같이 혼합됩니다.

이 제안을하는 유일한 도구는 R #이 아닙니다. FxCop / MS 코드 분석도 동일하게 수행됩니다.

나는 일반적으로 메소드가 정적 인 경우 일반적으로 테스트 할 수 있어야한다고 말합니다. 그것은 지금 당장 내 손가락보다 디자인에 대한 고려와 토론을 더 많이 해주므로, 참을성있게 투표와 의견을 기다리는 동안 ...;)


인터페이스에서 정적 메소드 / 객체를 선언 할 수 있습니까? (난 그렇게 생각하지 않아). 에테르 방식으로, 테스트중인 메소드가 정적 메소드를 호출 할 때를 말합니다. 호출자가 다른 단위에 있으면 정적 메소드를 분리해야합니다. 이것은 위에 나열된 세 가지 도구 중 하나를 사용하여 수행하기가 매우 어렵습니다.
Vaccano

1
아니요 인터페이스에서 정적을 선언 할 수 없습니다. 의미가 없습니다.
MIA

3

나는 오랜만에 아무도 아직 간단한 사실을 말하지 않았다는 것을 안다. resharper가 메소드를 정적으로 만들 수 있다고 말하면, 그것은 나에게 큰 의미가 있습니다. 그의 목소리가 들립니다. 헬퍼 클래스 또는 무언가에 ".


2
동의하지 않습니다. Resharper가 정적을 만들 수 있다고 말한 대부분의 경우 클래스의 두 개 이상의 메서드에 공통적 인 약간의 코드이므로 자체 절차로 추출했습니다. 이를 도우미로 옮기는 것은 의미가없는 복잡성입니다.
Loren Pechtel

2
도메인이 매우 단순하고 향후 수정에 적합하지 않은 경우에만 요점을 볼 수 있습니다. 달리, "의미없는 복잡성"이라고하는 것은 나에게 독특하고 사람이 읽을 수있는 디자인입니다. 간단하고 명확한 이유가있는 도우미 클래스를 갖는 것은 SoC 및 단일 책임 원칙의 "만트라"입니다. 또한이 새로운 클래스가 주요 클래스에 대한 종속성이된다는 점을 고려하면 일부 공용 멤버를 노출해야하며, 결과적으로 종속성으로 작동 할 때 격리 테스트가 가능하고 조롱하기 쉽습니다.
g1ga

1
질문과 관련이 없습니다.
Igby Largeman

2

정적 메소드가 다른 메소드 내부 에서 호출되면 이러한 호출을 방지하거나 대체 할 수 없습니다. 즉,이 두 가지 방법은 단일 단위를 만듭니다. 모든 종류의 단위 테스트는 둘 다 테스트합니다.

그리고이 정적 메소드가 인터넷과 통신하거나, 데이터베이스를 연결하거나, GUI 팝업을 표시하거나, 유닛 테스트를 완전한 혼란으로 변환하는 경우, 쉽게 해결할 수 있습니다. 이러한 정적 메소드를 호출하는 메소드는 단위 테스트를 통해 얻을 수있는 순전히 계산 코드가 많더라도 리팩토링없이 테스트 할 수 없습니다.


0

나는 Resharper가 당신에게 지침을 제공하고 그것이 설정된 코딩 지침을 적용한다고 생각합니다. Resharper를 사용하고 메소드가 정적이어야한다고 말하면 인스턴스 변수에 영향을 미치지 않는 개인 메소드에 바인딩되어야합니다.

이제 입증 방법에 관해서는이 시나리오는 문제가되지 않습니다. 어쨌든 개인 방법을 테스트해서는 안됩니다.

정적 메소드의 테스트 가능성에 대해서는 정적 메소드가 정적 상태에 닿으면 단위 테스트가 어려워집니다. 개인적으로 나는 이것을 최소로 유지하고 테스트 픽스처를 통해 제어 할 수있는 메소드에 종속성이 전달되는 경우 가능한 한 정적 함수를 순수한 함수로 사용합니다. 그러나 이것은 디자인 결정입니다.


다른 클래스에서 정적 메소드를 호출하는 테스트중인 메소드 (정적 아님)가있을 때를 언급하고 있습니다. 이러한 종속성은 위에 나열된 세 가지 도구 중 하나로 만 분리 할 수 ​​있습니다. 분리하지 않으면 단위 테스트가 아닌 통합 테스트입니다.
Vaccano

인스턴스 변수에 액세스한다는 것은 본질적으로 정적 일 수 없다는 것을 의미합니다. 정적 메소드가 가질 수있는 유일한 상태는 클래스 변수이며, 발생 해야하는 유일한 이유는 싱글 톤을 다루고 있으므로 선택의 여지가 없기 때문입니다.
Loren Pechtel
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.