단일 클래스의 단위 테스트를위한 단일 또는 다중 파일?


20

조직에 대한 지침을 모으는 데 도움이되는 단위 테스트 모범 사례를 조사 할 때 테스트 픽스처 (테스트 클래스)를 분리하거나 단일 클래스에 대한 모든 테스트를 하나의 파일로 유지하는 것이 더 나은지 또는 유용한 지에 대한 질문을했습니다.

Fwiw, 나는 단일 단위, 테스트 당 하나의 주장, 모의 모든 의존성 등을 대상으로하는 화이트 박스 테스트라는 순수한 의미에서 "단위 테스트"를 언급하고 있습니다.

예제 시나리오는 CheckIn 및 CheckOut의 두 가지 메소드가있는 클래스 (문서라고 함)입니다. 각 방법은 동작을 제어하는 ​​다양한 규칙 등을 구현합니다. 테스트 당 하나의 어설 션 규칙에 따라 각 방법에 대해 여러 테스트를 수행합니다. DocumentTests이름이 CheckInShouldThrowExceptionWhenUserIsUnauthorizedand 인 단일 클래스 에 모든 테스트를 배치 할 수 있습니다 CheckOutShouldThrowExceptionWhenUserIsUnauthorized.

또는, 나는 두 개의 별도의 테스트 클래스를 가질 수있다 : CheckInShouldCheckOutShould. 이 경우 테스트 이름이 짧아 지지만 특정 동작 (방법)에 대한 모든 테스트가 함께 구성되도록 테스트 이름이 구성됩니다.

나는 접근 할 수있는 찬반 양론이 있다고 확신하고 누군가가 여러 파일로 경로를 갔는지 궁금하다면 왜 그렇습니까? 또는 단일 파일 접근 방식을 선택한 경우 왜 더 나은 느낌이 들까 요?


2
테스트 방법 이름이 너무 깁니다. 테스트 방법에 여러 개의 어설 션이 포함되어 있음을 의미하더라도이를 단순화하십시오.
Bernard

12
@Bernard : 메소드 당 여러 개의 어설 션을 갖는 것은 나쁜 습관으로 간주되지만 긴 테스트 메소드 이름은 나쁜 습관으로 간주되지 않습니다. 우리는 일반적으로 이름 자체에서 방법이 테스트하는 것을 문서화합니다. 예 : constructor_nullSomeList (), setUserName_UserNameIsNotValid () 등 ...
c_maker

4
@ Bernard : 나는 긴 테스트 이름을 사용합니다 (그리고 거기 만!). 나는 그들을 사랑하고, 그것이 작동하지 않는 것이 너무 분명하기 때문에 (당신의 이름이 좋은 선택이라면;)).
Sebastian Bauer

여러 어설 션이 모두 동일한 결과를 테스트하는 경우 나쁜 습관이 아닙니다. 예 : 당신은 않을 것이다 testResponseContainsSuccessTrue(), testResponseContainsMyData()하고 testResponseStatusCodeIsOk(). 당신은 하나에서 그들을 것 testResponse()세 가지가 주장했습니다 assertEquals(200, response.status), assertEquals({"data": "mydata"}, response.data)그리고assertEquals(true, response.success)
주하 Untinen

답변:


18

드문 일이지만 때로는 테스트중인 특정 클래스에 대해 여러 개의 테스트 클래스를 갖는 것이 좋습니다. 일반적으로 다른 설정이 필요하고 테스트의 하위 집합에서 공유 할 때이 작업을 수행합니다.


3
그 접근법이 처음에 나에게 제시된 좋은 예입니다. 예를 들어 CheckIn 메서드를 테스트하려는 경우 항상 테스트중인 개체를 한 가지 방법으로 설정하려고하지만 CheckOut 메서드를 테스트 할 때는 다른 설정이 필요합니다. 좋은 지적.
SonOfPirate

14

단일 클래스에 대한 테스트를 여러 테스트 클래스로 분할해야하는 강력한 이유를 실제로 알 수 없습니다. 주도적 인 아이디어는 클래스 수준에서 응집력을 유지하는 것이므로 테스트 수준에서도 노력해야합니다. 몇 가지 임의의 이유 :

  1. 설정 코드를 복제 (및 여러 버전의 유지 보수) 할 필요가 없습니다.
  2. 하나의 테스트 클래스로 그룹화 된 경우 IDE에서 클래스에 대한 모든 테스트를보다 쉽게 ​​실행할 수 있습니다.
  3. 테스트 클래스와 클래스 간의 일대일 매핑으로보다 쉬운 문제 해결

좋은 지적입니다. 테스트중인 클래스가 끔찍한 kludge라면 몇 가지 테스트 클래스를 배제하지 않을 것입니다. 여기에는 클래스가 도우미 로직을 포함합니다. 나는 매우 엄격한 규칙을 좋아하지 않습니다. 그 외에는 좋은 점이 있습니다.
Job

1
단일 클래스에 대해 작동하는 테스트 픽스처에 대한 공통 기본 클래스를 사용하여 중복 설정 코드를 제거했습니다. 다른 두 가지 사항에 전혀 동의하지 않습니다.
SonOfPirate

JUnit에서 예를 들어 다른 러너를 사용하여 테스트하여 다른 클래스가 필요할 수 있습니다.
Joachim Nilsson

복잡한 수학을 가진 많은 수의 메소드가있는 클래스를 작성할 때 85 개의 테스트가 있으며 지금까지 24 %의 메소드에 대해서만 테스트를 작성했습니다. 어떻게 든이 수 많은 테스트를 별도의 범주로 포크하여 하위 집합을 실행할 수 있는지 궁금해했기 때문에 여기에서 나 자신을 찾습니다.
Mooing Duck

7

여러 파일에서 클래스에 대한 단위 테스트를 강제로 수행해야하는 경우 클래스 자체가 제대로 설계되지 않았 음을 나타낼 수 있습니다. 단일 책임 원칙 및 기타 프로그래밍 모범 사례를 합리적으로 준수하는 클래스에 대한 단위 테스트를 분할하는 것이 더 유리한 시나리오는 생각할 수 없습니다.

또한 단위 테스트에서 더 긴 메소드 이름을 갖는 것이 허용되지만, 방해가되는 경우 항상 단위 테스트 이름 지정 규칙을 재고하여 이름을 줄이십시오.


아아, 실제 세계에서 모든 클래스가 SRP 등을 준수하는 것은 아니며 레거시 코드를 단위 테스트 할 때 문제가됩니다.
Péter Török

3
SRP가 어떻게 관련되어 있는지 잘 모르겠습니다. 수업에 여러 개의 메소드가 있으며 동작을 유발하는 모든 다른 요구 사항에 대한 단위 테스트를 작성해야합니다. 테스트중인 클래스의 특정 메소드와 관련된 50 개의 테스트 메소드가있는 하나의 테스트 클래스 또는 10 개의 테스트가있는 5 개의 테스트 클래스를 갖는 것이 더 낫습니까?
SonOfPirate

2
@SonOfPirate : 너무 많은 테스트가 필요한 경우 메서드가 너무 많은 것을 의미 할 수 있습니다. SRP는 여기서 매우 관련이 있습니다. 메소드뿐만 아니라 클래스에도 SRP를 적용하면 테스트하기 쉬운 많은 작은 클래스와 메소드가 있으므로 테스트 메소드가 많이 필요하지 않습니다. (하지만
레테르 토르크 (

내가 제공 할 수있는 가장 좋은 예는 체크 아웃 여부와 같은 객체를 체크인 할 수있는 적절한 상태에있는 경우 누가 조치를 수행 할 수 있는지 (권한 부여) 결정하는 비즈니스 규칙이있는 CheckIn 메소드입니다. 만들어졌다. 객체가 체크 아웃되지 않거나 변경되지 않은 상태에서 CheckIn이 호출 될 때 메소드가 올바르게 동작하는지 검증하기위한 테스트뿐만 아니라 권한 부여 규칙이 적용되는지를 테스트하는 단위 테스트가있을 것입니다. 테스트. 이것이 잘못되었다고 제안하고 있습니까?
SonOfPirate

나는이 주제에 대해 어렵고 빠르며 옳고 그름의 대답이 없다고 생각합니다. 당신이하고있는 일이 당신을 위해 일하고 있다면, 좋습니다. 이것은 일반적인 지침에 대한 나의 관점입니다.
FishBasketGordo

2

테스트를 여러 클래스로 분리하는 것에 대한 나의 주장 중 하나는 팀의 다른 개발자 (특히 테스트에 정통하지 않은 사람들)가 기존 테스트를 찾는 것이 더 어려워진다는 것 입니다 (이미 이미 이것에 대한 테스트가 있는지 궁금합니다. 어디 있는지 궁금합니다. ) 및 새로운 테스트를 넣을 위치 ( 이 클래스 에서이 메소드에 대한 테스트를 작성하려고하지만 새 파일 또는 기존 파일에 넣을지 확실하지 않습니다. 하나? )

테스트마다 다른 설정이 필요한 경우, TDD 실무자들이 "고정"또는 "설정"을 다른 클래스 / 파일에 넣는 것을 보았지만 테스트 자체는 아닙니다.


1

내가 채택하기로 선택한 기술이 처음 입증 된 링크를 기억 / 찾을 수 있기를 바랍니다. 기본적으로 테스트중인 각 멤버에 대해 중첩 된 테스트 픽스처 (클래스)가 포함 된 테스트중인 각 클래스에 대해 단일 추상 클래스를 작성합니다. 이는 원래 원하는 분리를 제공하지만 모든 테스트는 각 위치에 대해 동일한 파일에 유지됩니다. 또한 테스트 러너에서 쉽게 그룹화하고 정렬 할 수있는 클래스 이름을 생성합니다.

이 방법이 원래 시나리오에 적용되는 예는 다음과 같습니다.

public abstract class DocumentTests : TestBase
{
    [TestClass()]
    public sealed class CheckInShould : DocumentTests
    {
        [TestMethod()]
        public void ThrowExceptionWhenUserIsNotAuthorized()
        {
        }
    }

    [TestClass()]
    public sealed class CheckOutShould : DocumentTests
    {
        [TestMethod()]
        public void ThrowExceptionWhenUserIsNotAuthorized()
        {
        }
    }
}

테스트 목록에 다음과 같은 테스트가 나타납니다.

DocumentTests+CheckInShould.ThrowExceptionWhenUserIsNotAuthorized
DocumentTests+CheckOutShould.ThrowExceptionWhenUserIsNotAuthorized

더 많은 테스트가 추가되면 클래스 이름별로 쉽게 그룹화 및 정렬되어 테스트중인 클래스에 대한 모든 테스트가 함께 나열됩니다.

이 접근법의 또 다른 장점은이 구조의 객체 지향적 특성으로 인해 모든 중첩 클래스와 각 클래스 내부에서 공유되는 DocumentTests 기본 클래스의 시작 및 정리 메소드 내부에서 코드를 정의 할 수 있습니다. 포함 된 테스트의 중첩 클래스


1

잠시 다른 방향으로 생각하십시오.

왜 수업 당 하나의 테스트 클래스 만 가지고 있습니까? 수업 시험이나 단위 시험을하고 있습니까? 당신은 수업에 의존 합니까?

단위 테스트는 특정 상황에서 특정 동작을 테스트해야합니다. 수업에 이미 수업이 있다는 사실 CheckIn은 먼저 행동이 필요하다는 것을 의미합니다.

이 의사 코드에 대해 어떻게 생각하십니까?

// check_in_test.file

class CheckInTest extends TestCase {
        /** @test */
        public function unauthorized_users_cannot_check_in() {
                $this->expectException();

                $document = new Document($unauthorizedUser);

                $document->checkIn();
        }
}

이제 checkIn메소드를 직접 테스트하지 않습니다 . 대신 동작을 테스트하고 있습니다 (다행히;);) 리팩토링 Document하고 다른 클래스로 분할하거나 다른 클래스를 병합 해야하는 경우 테스트 파일은 일관성이 있습니다. 리팩토링 할 때 로직 만 변경하지 않고 구조 만 변경합니다.

하나의 클래스에 대한 하나의 테스트 파일은 필요할 때마다 리팩토링하기가 더 어려워지고 코드 자체의 도메인 / 논리 측면에서도 의미가 떨어집니다.


0

일반적으로 테스트보다는 메소드가 아닌 클래스의 동작을 테스트하는 것이 좋습니다. 즉, 일부 테스트는 예상되는 동작을 테스트하기 위해 클래스에서 두 가지 메소드를 모두 호출해야 할 수도 있습니다.

일반적으로 프로덕션 클래스 당 하나의 단위 테스트 클래스로 시작하지만 테스트하는 동작에 따라 단위 테스트 클래스를 여러 테스트 클래스로 분할 할 수 있습니다. 다시 말해 테스트 클래스를 CheckInShould와 CheckOutShould로 나누지 말고 테스트중인 장치의 동작에 따라 나누지 않는 것이 좋습니다.


0

1. 잘못 될 가능성을 고려하십시오

초기 개발 과정에서 수행중인 작업을 상당히 잘 알고 있으며 두 솔루션 모두 제대로 작동 할 것입니다.

훨씬 나중에 변경 후 테스트가 실패하면 더 흥미로워집니다. 두 가지 가능성이 있습니다.

  1. 귀하는 심각하게 고장났습니다 CheckIn(또는 CheckOut). 이 경우에도 단일 파일 솔루션과 두 파일 솔루션 모두 정상입니다.
  2. 당신은 모두 수정 한 CheckIn하고 CheckOut있지만 그들 모두 함께, 그들 각각에 맞는 방식으로 (그리고 아마도 자신의 테스트)를. 쌍의 일관성을 깨뜨 렸습니다. 이 경우 테스트를 두 파일로 나누면 문제를 이해하기가 더 어려워집니다.

2. 어떤 테스트에 사용되는지 고려하십시오

테스트는 두 가지 주요 목적을 제공합니다.

  1. 프로그램이 여전히 올바르게 작동하는지 자동으로 확인하십시오. 테스트가 하나의 파일에 있는지 여러 파일에 있는지 여부는이 목적에 중요하지 않습니다.
  2. 인간 독자가 프로그램을 이해하도록 도와주십시오. 밀접하게 관련된 기능에 대한 테스트를 함께 유지하면 일관성을 이해하는 것이 빠른 길이 기 때문에 훨씬 쉬울 것입니다.

그래서?

이 두 가지 관점 모두 테스트를 함께 유지하는 것이 도움이 될 수 있지만 아프지는 않습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.