"Arrange-Assert-Act-Assert"여야합니까?


94

Arrange-Act-Assert 의 고전적인 테스트 패턴과 관련하여 저는 종종 Act 이전에 반대 주장을 추가하는 것을 발견합니다. 이렇게하면 전달 된 주장이 실제로 동작의 결과로 전달되고 있음을 알 수 있습니다.

Red-green-refactor의 빨간색과 유사하다고 생각합니다. 테스트 과정에서 빨간색 막대를 본 경우에만 초록색 막대가 차이를 만드는 코드를 작성했음을 의미합니다. 통과하는 테스트를 작성하면 모든 코드가 만족할 것입니다. 마찬가지로, Arrange-Assert-Act-Assert와 관련하여 첫 번째 주장이 실패하면 어떤 Act가 최종 Assert를 통과했을 것임을 알고 있습니다. 따라서 실제로 Act에 대한 어떤 것도 확인하지 않았습니다.

테스트가이 패턴을 따르나요? 그 이유는 무엇?

업데이트 설명 : 초기 주장은 본질적으로 최종 주장과 반대입니다. Arrange가 작동했다는 주장이 아닙니다. Act가 아직 작동하지 않았다는 주장입니다.

답변:


121

이것은 가장 일반적인 일은 아니지만 자체 이름을 가질만큼 충분히 일반적입니다. 이 기술을 Guard Assertion 이라고 합니다. 훌륭한 책 xUnit Test Patterns 에서 490 페이지에 대한 자세한 설명을 찾을 수 있습니다.Gerard Meszaros (강력 권장) .

일반적으로이 패턴을 직접 사용하지 않습니다. 보장해야한다고 느끼는 전제 조건을 검증하는 특정 테스트를 작성하는 것이 더 정확하다는 것을 알기 때문입니다. 이러한 테스트는 전제 조건이 실패하면 항상 실패해야하며 이는 다른 모든 테스트에 포함 할 필요가 없음을 의미합니다. 하나의 테스트 케이스가 한 가지만 확인하므로 우려 사항을 더 잘 격리 할 수 ​​있습니다.

주어진 테스트 케이스에 대해 충족해야하는 전제 조건이 많을 수 있으므로 Guard Assertion이 두 개 이상 필요할 수 있습니다. 모든 테스트에서이를 반복하는 대신 각 전제 조건에 대해 하나의 테스트를 수행하면 테스트 코드를보다 쉽게 ​​관리 할 수 ​​있습니다. 그렇게하면 반복 횟수가 줄어들 기 때문입니다.


+1, 아주 좋은 답변입니다. 마지막 부분은 별도의 단위 테스트로 사물을 보호 할 수 있음을 보여주기 때문에 특히 중요합니다.
murrekatt 2011 년

3
나는 일반적으로 이런 방식으로도 수행했지만 전제 조건을 보장하기 위해 별도의 테스트를 수행하는 데 문제가 있습니다 (특히 요구 사항이 변경되는 대규모 코드베이스)-전제 조건 테스트는 시간이 지남에 따라 수정되고 '메인'과 동기화되지 않습니다. 이러한 전제 조건을 전제하는 테스트. 따라서 전제 조건은 모두 양호하고 녹색 일 수 있지만 이러한 전제 조건은 기본 테스트에서 충족되지 않으며 이제 항상 녹색과 양호 함을 표시합니다. 그러나 전제 조건이 주 테스트에 있었다면 실패했을 것입니다. 이 문제를 발견하고 이에 대한 좋은 해결책을 찾았습니까?
nchaud

2
테스트를 많이 변경하면 다른 문제가 발생할 수 있습니다. 테스트를 덜 신뢰할 수있게 만드는 경향이 있기 때문입니다. 요구 사항이 변경되는 경우에도 추가 전용 방식으로 코드를 디자인하는 것이 좋습니다.
Mark Seemann 2014 년

@MarkSeemann 맞습니다. 반복을 최소화해야한다는 것은 맞습니다.하지만 다른 측면 에는 Arrange 자체 테스트가 통과하더라도 특정 테스트에 대한 Arrange에 영향을 미칠 수있는 많은 것들이있을 수 있습니다. 예를 들어 Arrange 테스트 또는 다른 테스트 후 정리가 잘못되었으며 Arrange가 Arrange 테스트와 동일하지 않습니다.
Rekshino


8

여기에 예가 있습니다.

public void testEncompass() throws Exception {
    Range range = new Range(0, 5);
    assertFalse(range.includes(7));
    range.encompass(7);
    assertTrue(range.includes(7));
}

Range.includes()단순히 진실을 반환하기 위해 쓴 것일 수 있습니다 . 나는하지 않았지만 내가 가질 수 있다고 상상할 수 있습니다. 아니면 다른 여러 가지 방법으로 잘못 썼을 수도 있습니다. 나는 TDD를 사용하여 실제로 제대로 includes()작동하기를 희망하고 기대합니다 . 따라서 첫 번째 주장은 두 번째 주장이 실제로 의미가 있는지 확인하기위한 온 전성 검사입니다.

읽기 자체 assertTrue(range.includes(7));는 "수정 된 범위에 7이 포함됨"이라고 말합니다. 첫 번째 주장의 맥락에서 읽어보십시오. "envelope ()호출 하면 7이 포함 되도록 주장합니다 . 그리고 include는 우리가 테스트하는 단위이기 때문에 (작은) 값이라고 생각합니다.

나는 내 대답을 받아들이고있다. 다른 많은 사람들이 내 질문을 설정 테스트에 관한 것으로 오해했습니다. 약간 다른 것 같아요.


예를 들어 주셔서 감사합니다, Carl. 음, TDD주기의 빨간색 부분에서, envelope ()가 실제로 뭔가를 할 때까지; 첫 번째 주장은 무의미하며 두 번째 주장의 중복 일뿐입니다. 녹색에서 유용하기 시작합니다. 리팩토링 중에 의미가 있습니다. 이 작업을 자동으로 수행하는 UT 프레임 워크가 있으면 좋을 것입니다.
philant

Range 클래스를 TDD라고 가정하면 Range ctor를 테스트하는 또 다른 실패한 테스트가 없을까요?
philant

1
@philippe : 질문을 이해했는지 잘 모르겠습니다. Range 생성자와 includes ()에는 자체 단위 테스트가 있습니다. 자세히 설명해 주시겠습니까?
Carl Manaster

첫 번째 assertFalse (range.includes (7)) 어설 션이 실패하려면 범위 생성자에 결함이 있어야합니다. 그래서 Range 생성자에 대한 테스트가 그 주장과 동시에 중단되지 않는지 물어 보려고했습니다. 그리고 다른 값에 대한 Act 이후 에 주장하는 것은 어떨까요 : 예를 들어 assertFalse (range.includes (6))?
philant

1
내 생각에 범위 구성은 includes ()와 같은 함수보다 우선합니다. 그래서 동의하지만, 잘못된 생성자 (또는 잘못된 includes ())만이 첫 번째 주장을 실패하게 만들 것이고 생성자의 테스트는 includes ()에 대한 호출을 포함하지 않을 것입니다. 예, 첫 번째 주장까지 모든 기능이 이미 테스트되었습니다. 그러나이 초기의 부정적인 주장은 무언가를 전달하는 것이며, 제 생각에는 유용한 무언가를 전달하는 것입니다. 그러한 주장이 처음 작성 될 때 통과하더라도.
Carl Manaster

7

Arrange-Assert-Act-Assert테스트는 항상 두 개의 테스트로 리팩토링 할 수있다 :

1. Arrange-Assert

2. Arrange-Act-Assert

첫 번째 테스트는 Arrange 단계에서 설정된 것에 대해서만 주장하고 두 번째 테스트는 Act 단계에서 발생한 것에 대해서만 주장합니다.

이것은 실패한 것이 Arrange 또는 Act 단계인지에 대해 더 정확한 피드백을 제공하는 이점이 있습니다. 반면 원본에서는 Arrange-Assert-Act-Assert이러한 단계가 결합되어 있으므로 더 깊이 파고 들어서 주장이 실패한 이유와 실패한 이유를 확인하기 위해 정확히 조사해야합니다. 실패한 것은 Arrange 또는 Act였습니다.

또한 테스트를 더 작은 독립 단위로 분리하므로 단위 테스트의 의도를 더 잘 충족시킵니다.

마지막으로, 다른 테스트에서 유사한 Arrange 섹션을 볼 때마다이를 공유 도우미 메서드로 가져와야합니다. 그래야 테스트가 더 건조 해지고 향후 유지 관리 가 쉬워 집니다.


3

나는 지금 이것을하고있다. 다른 종류의 AAAA

Arrange - setup
Act - what is being tested
Assemble - what is optionally needed to perform the assert
Assert - the actual assertions

업데이트 테스트의 예 :

Arrange: 
    New object as NewObject
    Set properties of NewObject
    Save the NewObject
    Read the object as ReadObject

Act: 
    Change the ReadObject
    Save the ReadObject

Assemble: 
    Read the object as ReadUpdated

Assert: 
    Compare ReadUpdated with ReadObject properties

그 이유는 ACT가 ReadUpdated의 읽기를 포함하지 않는 이유는 그것이 행위의 일부가 아니기 때문입니다. 행위는 단지 변화하고 저장하는 것입니다. 실제로 어설 션을 위해 ARRANGE ReadUpdated를 사용하고 어설 션을 위해 ASSEMBLE을 호출합니다. 이것은 ARRANGE 섹션의 혼동을 방지하기위한 것입니다.

ASSERT는 어설 션 만 포함해야합니다. 어설 션을 설정하는 ACT와 ASSERT 사이에 ASSEMBLE이 남습니다.

마지막으로 Arrange에서 실패한 경우 이러한 사소한 버그 를 방지 / 찾을 수있는 다른 테스트가 있어야하므로 테스트가 올바르지 않습니다 . 내가 제시 한 시나리오의 경우 READ 및 CREATE를 테스트하는 다른 테스트가 이미 있어야합니다. "Guard Assertion"을 만들면 DRY를 깨고 유지 관리를 만들 수 있습니다.


1

테스트중인 작업을 수행하기 전에 상태를 확인하기 위해 "건전성 검사"어설 션을 던지는 것은 오래된 기술입니다. 나는 보통 테스트 스캐 폴딩으로 작성하여 테스트가 내가 기대하는 바를 수행한다는 것을 스스로 증명하고 나중에 테스트 스캐 폴딩으로 복잡한 테스트를 피하기 위해 제거합니다. 때로는 비계를 그대로두면 테스트가 내러티브 역할을하는 데 도움이됩니다.


1

나는 이미이 기술에 대해 읽었다 – 아마도 당신에게서 – 그러나 나는 그것을 사용하지 않는다; 주로 단위 테스트를 위해 트리플 A 양식에 익숙하기 때문입니다.

이제 호기심이 생기고 몇 가지 질문이 있습니다. 테스트를 어떻게 작성합니까,이 주장이 실패하도록 만들었습니까? 빨강-녹색-빨강-녹색-리 팩터주기를 따르거나 나중에 추가합니까?

코드를 리팩토링 한 후 가끔 실패합니까? 이것은 당신에게 무엇을 말합니까? 도움이 된 사례를 공유 할 수있을 것입니다. 감사.


나는 일반적으로 초기 어설 션이 실패하도록 강요하지 않습니다. 결국 TDD 어설 션이 방법이 작성되기 전에 실패해서는 안됩니다. 내가 내가 그것을 쓸 때, 그것을 쓰기 전에 그냥 이후, 테스트를 작성하는 일반적인 과정에서. 솔직히 실패한 것을 기억할 수 없습니다. 아마도 시간 낭비라는 의미 일 것입니다. 예를 들어 보려고하는데 현재는 생각하지 않습니다. 질문 해 주셔서 감사합니다. 도움이됩니다.
Carl Manaster 2009-06-20

1

실패한 테스트를 조사 할 때 전에이 작업을 수행했습니다.

머리를 많이 긁은 후 "정렬"중에 호출 된 메서드가 제대로 작동하지 않는 것이 원인이라고 판단했습니다. 테스트 실패는 잘못된 것입니다. 편곡 후 Assert를 추가했습니다. 이로 인해 실제 문제를 강조한 곳에서 테스트가 실패했습니다.

테스트의 Arrange 부분이 너무 길고 복잡하면 코드 냄새가 나는 것 같습니다.


사소한 요점 : 너무 복잡한 Arrange는 코드 냄새보다 디자인 냄새가 더 많다고 생각합니다. 때로는 복잡한 Arrange만으로 유닛을 테스트 할 수있는 디자인이되기도합니다. 그 상황은 단순한 코드 냄새보다 더 깊은 수정을 원하기 때문에 언급했습니다.
Carl Manaster 2010 년

1

일반적으로 나는 "정렬, 행동, 주장"을 매우 좋아하고 그것을 나의 개인 표준으로 사용합니다. 그러나 내가하도록 상기시키지 못하는 한 가지는 주장이 끝났을 때 내가 배열 한 것을 어긋나게하는 것이다. 대부분의 경우 대부분의 작업은 가비지 수집 등을 통해 자동으로 사라지기 때문에 많은 문제가 발생하지 않습니다. 그러나 외부 리소스에 대한 연결을 설정 한 경우 완료되면 해당 연결을 닫고 싶을 것입니다. 당신의 주장으로 또는 당신은 많은 사람들이 다른 사람에게 줄 수 있어야 할 연결이나 중요한 자원을 보유하고있는 서버 또는 값 비싼 자원을 가지고 있습니다. 이것은 당신이TearDown 또는 TestFixtureTearDown을 사용하지 않는 개발자 중 한 명인하나 이상의 테스트 후 정리합니다. 물론 "Arrange, Act, Assert"는 내가 여는 것을 닫지 못한 것에 대한 책임이 없습니다. 추천 할 "dispose"에 대한 좋은 "A-word"동의어를 아직 찾지 못했기 때문에이 "gotcha"만 언급합니다! 어떤 제안?


1
@carlmanaster, 당신은 실제로 나를 위해 충분히 가깝습니다! 다음 TestFixture에서 크기를 시험해보기 위해 그것을 고수하고 있습니다. 마치 어머니가 가르쳐 주셨어야했던 일을하라는 작은 상기와 같습니다. "열면 닫으세요! 엉망이되면 정리하세요!" 다른 사람이 개선 할 수 있지만 적어도 "a!"로 시작합니다. 제안 해 주셔서 감사합니다!
John Tobler

1
@carlmanaster, 나는 "Annul"을 시도했다. "해체"보다 낫고 일종의 효과가 있지만 여전히 "정렬, 행동, 주장"만큼 완벽하게 내 머릿속에 꽂혀있는 또 다른 "A"단어를 찾고 있습니다. 어쩌면 "절멸?!"
John Tobler

1
이제 "정렬, 가정, 행동, 주장, 소멸"이 있습니다. 흠! 난 너무 복잡해? 그냥 키스하고 "정렬하고, 행동하고, 주장하라!"
John Tobler 2011-08-16

1
재설정에 R을 사용할 수 있습니까? 나는 그것이 A가 아니라는 것을 알고 있지만 해적처럼 들린다 : Aaargh! 및 어설와 운율을 재설정 O를
마르셀 발데스 오 로즈 코

1

Design by Contract 에 대한 Wikipedia의 항목을 살펴보십시오 . Arrange-Act-Assert 거룩한 삼위 일체는 동일한 개념 중 일부를 인코딩하려는 시도이며 프로그램의 정확성을 증명하는 것입니다. 기사에서 :

The notion of a contract extends down to the method/procedure level; the
contract for each method will normally contain the following pieces of
information:

    Acceptable and unacceptable input values or types, and their meanings
    Return values or types, and their meanings
    Error and exception condition values or types that can occur, and their meanings
    Side effects
    Preconditions
    Postconditions
    Invariants
    (more rarely) Performance guarantees, e.g. for time or space used

이를 설정하는 데 소요되는 노력의 양과 추가되는 가치 사이에는 상충 관계가 있습니다. AAA는 필요한 최소 단계에 대한 유용한 알림이지만 다른 사람이 추가 단계를 생성하는 것을 방해해서는 안됩니다.


0

테스트 환경 / 언어에 따라 다르지만 일반적으로 Arrange 파트의 무언가가 실패하면 예외가 발생하고 테스트는 Act 파트를 시작하는 대신이를 표시하지 못합니다. 그래서 저는 보통 두 번째 Assert 부분을 사용하지 않습니다.

또한 Arrange 부분이 매우 복잡하고 항상 예외를 throw하지 않는 경우 일부 메서드 내부에 래핑하고 자체 테스트를 작성하는 것을 고려할 수 있으므로 실패하지 않는지 확인할 수 있습니다. 예외 발생).


0

나는 다음과 같은 것을 생각하기 때문에 그 패턴을 사용하지 않습니다.

Arrange
Assert-Not
Act
Assert

Arrange 파트가 올바르게 작동한다는 것을 알고 있기 때문에 의미가 없을 수 있습니다. 즉, Arrange 파트에있는 모든 항목을 테스트해야하거나 테스트가 필요하지 않을만큼 간단해야합니다.

답변의 예를 사용하여 :

public void testEncompass() throws Exception {
    Range range = new Range(0, 5);
    assertFalse(range.includes(7)); // <-- Pointless and against DRY if there 
                                    // are unit tests for Range(int, int)
    range.encompass(7);
    assertTrue(range.includes(7));
}

내 질문을 정말로 이해하지 못하는 것 같습니다. 초기 주장은 Arrange 테스트에 관한 것이 아닙니다. 그것은 단순히 법안이 마지막에 주장되는 국가를 초래하는 것을 보장하는 것입니다.
Carl Manaster

그리고 내 요점은 Assert-Not 부분에 넣은 것이 무엇이든 Arrange 부분에 이미 함축되어 있다는 것입니다. 왜냐하면 Arrange 부분의 코드는 철저히 테스트되었고 이미 그것이 무엇을하는지 알고 있기 때문입니다.
Marcel Valdez Orozco 2012 년

그러나 나는 Assert-Not 부분에 가치가 있다고 믿습니다. 왜냐하면 당신이 말하고 있기 때문입니다 : Arrange 부분이 '이 상태'에서 '세계'를 떠나는 것을 감안할 때 내 '행동'은이 '새로운 상태'에서 '세계'를 떠날 것입니다. ; 그리고 Arrange 부분이 의존하는 코드의 구현이 변경되면 테스트도 중단됩니다. 그러나 다시 말하지만 Arrange 부분에 의존하는 모든 코드에 대한 테스트가 있어야하기 때문에 DRY에 반대 할 수 있습니다.
Marcel Valdez Orozco 2012 년

같은 프로젝트에서 여러 팀 (또는 큰 팀)이 작업하는 프로젝트에서 이러한 조항은 매우 유용 할 것입니다. 그렇지 않으면 불필요하고 중복됩니다.
Marcel Valdez Orozco 2012 년

아마도 그러한 절은 통합 테스트, 시스템 테스트 또는 수락 테스트에서 더 좋을 것입니다. 여기서 배열 부분은 일반적으로 둘 이상의 구성 요소에 의존하고 '세계'의 초기 상태가 예기치 않게 변경 될 수있는 요인이 더 많습니다. 그러나 나는 단위 테스트에서 그것을위한 장소를 보지 못합니다.
Marcel Valdez Orozco 2012 년

0

예제의 모든 것을 테스트하려면 다음과 같이 더 많은 테스트를 시도하십시오.

public void testIncludes7() throws Exception {
    Range range = new Range(0, 5);
    assertFalse(range.includes(7));
}

public void testIncludes5() throws Exception {
    Range range = new Range(0, 5);
    assertTrue(range.includes(5));
}

public void testIncludes0() throws Exception {
    Range range = new Range(0, 5);
    assertTrue(range.includes(0));
}

public void testEncompassInc7() throws Exception {
    Range range = new Range(0, 5);
    range.encompass(7);
    assertTrue(range.includes(7));
}

public void testEncompassInc5() throws Exception {
    Range range = new Range(0, 5);
    range.encompass(7);
    assertTrue(range.includes(5));
}

public void testEncompassInc0() throws Exception {
    Range range = new Range(0, 5);
    range.encompass(7);
    assertTrue(range.includes(0));
}

그렇지 않으면 오류가 발생할 가능성이 너무 많기 때문입니다. 범위에서 5 개를 포함하려고 시도하는 또 다른 테스트 세트 ... 우리가 기대하는 것은 무엇입니까-포함하는 예외 또는 변경되지 않는 범위?

어쨌든, 요점은 당신이 시험하고 싶은 행위에 어떤 가정이 있다면, 그들 자신의 시험에 넣어야한다는 것입니다.


0

나는 사용한다:

1. Setup
2. Act
3. Assert 
4. Teardown

깨끗한 설정이 매우 중요하기 때문입니다.

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