IT이야기

단위 테스트를 테스트하는 클래스의 친구로 만드는 것이 잘못된 이유

cyworld 2021. 5. 3. 21:52
반응형

단위 테스트를 테스트하는 클래스의 친구로 만드는 것이 잘못된 이유는 무엇입니까?


C ++에서 나는 종종 단위 테스트 클래스를 내가 테스트하는 클래스의 친구로 만들었습니다. 나는 때때로 private 메서드에 대한 단위 테스트를 작성해야 할 필요성을 느끼거나 어떤 private 멤버에 액세스하여 개체의 상태를 더 쉽게 설정하여 테스트 할 수 있기를 원하기 때문에이 작업을 수행합니다. 나에게 이것은 클래스의 공개 또는 보호 된 인터페이스를 수정하지 않기 때문에 캡슐화와 추상화를 보존하는 데 도움이됩니다.

써드 파티 라이브러리를 구입한다면, 단순히 벤더가 단위 테스트를 원했기 때문에 내가 알 필요가없는 많은 공개 메소드로 인해 공개 인터페이스가 오염되는 것을 원하지 않을 것입니다!

또한 클래스에서 상속하는 경우 알 필요가없는 보호 된 멤버에 대해 걱정할 필요가 없습니다.

이것이 추상화와 캡슐화를 보존한다고 말하는 이유입니다.

내 새 직장에서 그들은 단위 테스트에서도 친구 클래스를 사용하는 것에 대해 눈살을 찌푸립니다. 그들은 클래스가 테스트에 대해 어떤 것도 "알지"않아야하고 클래스와 테스트의 긴밀한 결합을 원하지 않기 때문에 말합니다.

누군가가 더 잘 이해할 수 있도록 이러한 이유를 더 많이 설명해 주시겠습니까? 단위 테스트에 친구를 사용하는 것이 왜 나쁜지 모르겠습니다.


이상적으로는 개인 메서드를 단위 테스트 할 필요가 없습니다. 클래스의 소비자가 관심을 가져야하는 모든 것은 공용 인터페이스이므로 테스트해야합니다. 개인 메서드에 버그가있는 경우 클래스에서 일부 공용 메서드를 호출하는 단위 테스트에 의해 포착되어야하며 결국 버그가있는 개인 메서드를 호출하게됩니다. 버그가 사라지면 테스트 케이스가 클래스가 구현하기를 원하는 계약을 완전히 반영하지 않음을 나타냅니다. 이 문제에 대한 해결책은 테스트 케이스가 클래스의 구현 세부 사항을 파헤 치지 않도록보다 면밀하게 공개 메서드를 테스트하는 것이 거의 확실합니다.

다시 말하지만 이것이 이상적인 경우입니다. 현실 세계에서는 상황이 항상 명확하지 않을 수 있으며 단위 테스트 클래스를 테스트하는 클래스의 친구가되는 것이 허용되거나 바람직 할 수 있습니다. 그래도 항상하고 싶은 일은 아닐 것입니다. 충분히 자주 나오는 것 같으면 수업이 너무 크거나 너무 많은 작업을 수행하고 있다는 신호일 수 있습니다. 그렇다면 복잡한 private 메서드 집합을 별도의 클래스로 리팩토링하여 추가로 세분화하면 구현 세부 정보에 대해 알기 위해 단위 테스트가 필요하지 않습니다.


테스트 할 스타일과 방법이 다양하다는 점을 고려해야합니다. 블랙 박스 테스트 는 공용 인터페이스 만 테스트합니다 (클래스를 블랙 박스로 취급). 추상 기본 클래스가있는 경우 모든 구현에 대해 동일한 테스트를 사용할 수도 있습니다.

White box testing 을 사용하는 경우 구현 세부 정보를 볼 수도 있습니다. 클래스에 어떤 private 메서드가 있는지뿐만 아니라 어떤 종류의 조건문이 포함되어 있는지 (즉, 조건을 코딩하기 어렵다는 것을 알고 있기 때문에 조건 적용 범위를 늘리려는 경우). 화이트 박스 테스트에서는 인터페이스가 아닌 구현을 테스트하기를 원하기 때문에 필요한 테스트와 클래스 / 구현 사이에 "높은 결합"이 확실히 있습니다.

bcat이 지적했듯이 많은 private 메서드 대신 컴포지션과 더 작은 클래스를 사용하는 것이 종종 도움이됩니다. 이것은 좋은 테스트 커버리지를 얻기 위해 테스트 케이스를 더 쉽게 지정할 수 있기 때문에 화이트 박스 테스트를 단순화합니다.


Bcat이 아주 좋은 대답을했다고 생각하지만 그가 언급 한 예외적 인 경우에 대해 설명하고 싶습니다.

현실 세계에서는 상황이 항상 명확하지 않을 수 있으며 단위 테스트 클래스를 테스트하는 클래스의 친구가되는 것이 허용되거나 바람직 할 수 있습니다.

나는 큰 레거시 코드베이스를 가진 회사에서 일하는데, 두 가지 문제가 모두 친구 단위 테스트를 바람직하게 만드는 데 기여합니다.

  • 리팩토링이 필요한 엄청나게 큰 함수와 클래스로 고통 받고 있지만 리팩토링하려면 테스트를하는 것이 도움이됩니다.
  • 우리 코드의 대부분은 다양한 이유로 단위 테스트에 포함되지 않아야하는 데이터베이스 액세스에 의존합니다.

어떤 경우에는 모킹이 후자의 문제를 완화하는 데 유용하지만, 매우 복잡한 디자인 (그렇지 않으면 필요하지 않은 클래스 계층)으로 이어지지 만 다음과 같은 방법으로 코드를 매우 간단하게 리팩토링 할 수 있습니다.

class Foo{
public:
     some_db_accessing_method(){
         // some line(s) of code with db dependance.

         // a bunch of code which is the real meat of the function

         // maybe a little more db access.
     }
}

이제 함수의 핵심이 리팩토링이 필요한 상황이 있으므로 단위 테스트를 원합니다. 공개적으로 노출되어서는 안됩니다. 이제이 상황에서 사용할 수있는 조롱이라는 멋진 기술이 있지만 사실이 경우 모의는 과잉입니다. 불필요한 계층 구조로 디자인의 복잡성을 증가시켜야합니다.

훨씬 더 실용적인 접근 방식은 다음과 같이하는 것입니다.

 class Foo{
 public: 
     some_db_accessing_method(){
         // db code as before
         unit_testable_meat(data_we_got_from_db);
         // maybe more db code.    
 }
 private:
     unit_testable_meat(...);
 }

후자는 내가 고기에서 코드를 리팩토링 할 때 발생하는 오류를 잡을 수있는 귀중한 안전망을 제공하는 것을 포함하여 단위 테스트에서 필요한 모든 이점을 제공합니다. 단위 테스트를 위해서는 UnitTest 클래스와 친구가되어야합니다.하지만 Mock을 사용할 수있게 해주는 쓸모없는 코드 계층 구조보다 훨씬 낫다고 강력하게 주장합니다.

저는 이것이 관용구가되어야한다고 생각하며 단위 테스트의 ROI를 높이는 데 적합하고 실용적인 솔루션이라고 생각합니다.


bcat이 제안한 것처럼 가능한 한 공용 인터페이스 자체를 사용하여 버그를 찾아야합니다. 그러나 개인 변수를 인쇄하고 예상 결과와 비교하는 등 (개발자가 문제를 쉽게 디버그하는 데 도움이 됨) 등을 수행하려면 UnitTest 클래스를 테스트 할 클래스의 친구로 만들 수 있습니다. 그러나 아래와 같은 매크로 아래에 추가해야 할 수도 있습니다.

class Myclass
{
#if defined(UNIT_TEST)
    friend class UnitTest;
#endif
};

Enable flag UNIT_TEST only when Unit testing is required. For other releases, you need to disable this flag.


I don't see anything wrong with using a friend unit testing class in many cases. Yes, decomposing a large class into smaller ones is sometimes a better way to go. I think people are a bit too hasty to dismiss using the friend keyword for something like this - it might not be ideal object oriented design, but I can sacrifice a little idealism for better test coverage if that's what I really need.


Typically you only test the public interface so that you are free to redesign and refactor the implementation. Adding test cases for private members defines a requirement and restriction on the implementation of your class.


Make the functions you want to test protected. Now in your unit test file, create a derived class. Create public wrapper functions that call your the class-under-test protected functions.

ReferenceURL : https://stackoverflow.com/questions/4171310/what-is-wrong-with-making-a-unit-test-a-friend-of-the-class-it-is-testing

반응형