단일 단위 테스트에서 여러 개의 어설 션을 갖는 것이 괜찮습니까?


397

이 위대한 게시물에 대한 의견 에서 Roy Osherove 는 각 테스트를 단일 테스트로 실행하도록 설계된 OAPT 프로젝트를 언급했습니다 .

다음은 프로젝트 홈페이지에 작성되었습니다.

적절한 단위 테스트는 정확히 한 가지 이유로 실패 해야하므로 단위 테스트 당 하나의 어설 션을 사용해야합니다.

또한 Roy는 다음과 같이 논평했습니다.

내 지침은 일반적으로 테스트 당 하나의 논리적 개념을 테스트하는 것입니다. 동일한 객체 에 대해 여러 개의 어설 션을 가질 수 있습니다 . 그것들은 보통 테스트되는 것과 같은 개념 일 것입니다.

나는 여러 가지 주장이 필요한 경우가 있다고 생각 하지만 (예 : Guard Assertion ) 일반적으로 이것을 피하려고합니다. 당신의 의견 것입니다? 여러 개의 어설 션이 실제로 필요한 실제 예제를 제공하십시오 .


2
여러 번의 주장없이 어떻게 조롱을합니까? 모의에 대한 각 기대는 당신이 부과하는 모든 호출 순서를 포함하여 그 자체로 주장입니다.
Christopher Creutzig 2016 년

15
나는 과거에 한 가지 방법으로 만 주장되는 철학을 보았습니다. 오래된 동료가 펑키 상속 메커니즘을 사용하여이를 가능하게했습니다. 많은 하위 클래스 (분기당 하나)와 다른 설정을 확인하기 위해 동일한 설정 / 해체 프로세스를 수행 한 많은 테스트로 이어졌습니다. 느리고 읽기가 어려웠으며 유지 보수에 심각한 문제가있었습니다. 나는 그가 더 고전적인 접근 방식으로 다시 전환하라고 확신하지 않았다. Gerard Meszaros 책 은이 주제에 대해 자세히 이야기합니다.
트래비스 파크

3
일반적으로 테스트 당 어설 션 수를 최소화하려고 노력해야한다고 생각합니다. 그러나 테스트가 문제를 코드의 특정 위치로 충분히 좁히는 한 유용한 테스트입니다.
ConditionRacer

2
다양한 엣지 케이스 동작을 테스트하기 위해 RowTest(MbUnit) / TestCase(NUnit) 대신 여러 어설 션이 사용되는 경우를 보았습니다 . 작업에 적합한 도구를 사용하십시오! (불행히도 MSTest는 아직 행 테스트 기능이없는 것 같습니다.)
GalacticCowboy

@GalacticCowboy 테스트 데이터 소스RowTest 와 유사한 기능을 TestCase사용하고 테스트 데이터 소스를 사용할 수 있습니다 . 성공적인 CSV 파일을 사용하고 있습니다.
julealgon

답변:


236

필자는 이것이 반드시 나쁜 것이라고 생각하지는 않지만 테스트에서 단 한 번의 주장 만하도록 노력해야 한다고 생각 합니다. 즉, 더 많은 테스트를 작성하면 테스트에서 한 번에 한 가지만 테스트하게됩니다.

내가 말했듯이, 내 테스트의 절반은 실제로 단 하나의 주장 만 가지고 있다고 말할 것입니다. 나는 그것이 단지하게 생각 (? 시험) 코드 냄새 는 약 5 개 이상이 테스트에서 주장이있을 때.

여러 개의 어설 션을 어떻게 해결합니까?


9
이 대답처럼-예, 괜찮지 만 일반적인 경우에는 좋지 않습니다 (-:
Murph

5
응? 왜 그렇게 하시겠습니까? 메소드 실행이 정확히 동일합니까?
jgauffin

197
단위 테스트 당 한 번의 어설 션은 독자의 위아래 스크롤 기능을 테스트하는 좋은 방법입니다.
Tom

3
이 뒤에 추론이 있습니까? 있는 그대로,이 현재의 대답은 그것이 무엇이되어야하는지, 왜 그런지는 명시하지 않습니다.
Steven Jeuris

37
강하게 동의. 대답에는 단일 주장이 있다는 장점이 없으며, 명백한 단점은 인터넷에 대한 대답이 그렇게 말하기 때문에 테스트를 복사하여 붙여 넣어야한다는 것입니다. 결과의 여러 필드 또는 단일 작업의 여러 결과를 테스트해야하는 경우 큰 블로 블 어설트에서 테스트하는 것보다 훨씬 유용한 정보를 제공하므로 모든 필드를 독립적 인 어설 션으로 어설 션해야합니다. 좋은 테스트에서는 단일 작업 결과가 아니라 단일 작업을 테스트합니다.
Peter

296

테스트가 한 가지 이유로 만 실패해야한다고해서 항상 하나의 Assert명령문 만 있어야한다는 것은 아닙니다 . IMHO " 배열, 행위, 주장 "패턴 을 유지하는 것이 더 중요합니다 .

핵심은 작업이 하나 뿐인 다음 어설 션을 사용하여 해당 작업의 결과를 검사하는 것입니다. 그러나 "배열, 행위, 주장, 시험 종료 "입니다. 다른 조치를 수행하여 테스트를 계속하려는 경향이 있고 나중에 더 많은 주장을한다면 대신 별도의 테스트를 수행하십시오.

동일한 동작을 테스트하는 부분을 구성하는 여러 가지 주장을 보게되어 기쁩니다. 예 :

[Test]
public void ValueIsInRange()
{
  int value = GetValueToTest();

  Assert.That(value, Is.GreaterThan(10), "value is too small");
  Assert.That(value, Is.LessThan(100), "value is too large");
} 

또는

[Test]
public void ListContainsOneValue()
{
  var list = GetListOf(1);

  Assert.That(list, Is.Not.Null, "List is null");
  Assert.That(list.Count, Is.EqualTo(1), "Should have one item in list");
  Assert.That(list[0], Is.Not.Null, "Item is null");
} 

당신은 일 어설으로 이러한 결합,하지만 당신이 주장과는 다른 일이 해야 또는 필수 . 그것들을 결합하여 개선이 없습니다.

예를 들어 첫 번째 있을

Assert.IsTrue((10 < value) && (value < 100), "Value out of range"); 

그러나 이것은 더 좋지 않습니다. 오류 메시지는 덜 구체적이며 다른 장점은 없습니다. 두 개 또는 세 개 이상의 어설 션을 하나의 큰 부울 조건으로 결합하면 읽기가 어려워지고 변경하기가 어려워지고 실패한 이유를 해결하기가 더 어려워지는 다른 예제를 생각할 수 있습니다. 왜 규칙을 위해서만 하는가?

NB : 여기서 작성하는 코드는 NUnit의 C #이지만 원칙은 다른 언어 및 프레임 워크와 함께 유지됩니다. 구문도 매우 유사 할 수 있습니다.


32
핵심은 작업이 하나 뿐인 다음 어설 션을 사용하여 해당 작업의 결과를 검사하는 것입니다.
Amitābha

1
Arrange가 시간이 많이 걸리면 Assert를 하나 더 갖는 것도 흥미 롭습니다.
Rekshino

1
@Rekshino, 배열이 시간이 오래 걸리면 예를 들어 배열 코드를 테스트 초기화 루틴에 넣음으로써 배열 코드를 공유 할 수 있습니다.
Shaun Luttin

2
따라서 Jaco 답변과 비교하면 "단 하나의 단언"이 "단 하나의 단언 그룹"이되어 더 의미가 있습니다.
Walfrat

2
이것은 좋은 대답이지만 단일 주장이 더 좋지 않다는 것에 동의하지 않습니다. 잘못된 어설 션 예제가 더 좋지는 않지만, 이것이 제대로 수행되면 단일 어설 션이 더 좋지 않다는 것을 의미하지는 않습니다. 많은 라이브러리가 사용자 지정 어설트 / 매칭자를 허용하므로 아직 존재하지 않는 경우 무언가를 만들 수 있습니다. 예를 들어 , 내 의견으로는 Assert.IsBetween(10, 100, value)인쇄물 Expected 8 to be between 10 and 100 두 개의 개별 주장보다 낫습니다. 반드시 필요하지는 않다고 주장 할 수 있지만, 일반적으로 전체 세트를 만들기 전에 단일 주장으로 쉽게 축소 할 수 있는지 고려해 볼 가치가 있습니다.
Thor84no

85

나는 하나 이상의 주장이 나쁜 것이라고 생각한 적이 없다.

나는 항상 그것을한다 :

public void ToPredicateTest()
{
    ResultField rf = new ResultField(ResultFieldType.Measurement, "name", 100);
    Predicate<ResultField> p = (new ConditionBuilder()).LessThanConst(400)
                                                       .Or()
                                                       .OpenParenthesis()
                                                       .GreaterThanConst(500)
                                                       .And()
                                                       .LessThanConst(1000)
                                                       .And().Not()
                                                       .EqualsConst(666)
                                                       .CloseParenthesis()
                                                       .ToPredicate();
    Assert.IsTrue(p(ResultField.FillResult(rf, 399)));
    Assert.IsTrue(p(ResultField.FillResult(rf, 567)));
    Assert.IsFalse(p(ResultField.FillResult(rf, 400)));
    Assert.IsFalse(p(ResultField.FillResult(rf, 666)));
    Assert.IsFalse(p(ResultField.FillResult(rf, 1001)));

    Predicate<ResultField> p2 = (new ConditionBuilder()).EqualsConst(true).ToPredicate();

    Assert.IsTrue(p2(new ResultField(ResultFieldType.Confirmation, "Is True", true)));
    Assert.IsFalse(p2(new ResultField(ResultFieldType.Confirmation, "Is False", false)));
}

여기서는 여러 조건을 사용하여 복잡한 조건을 예상 조건 자로 바꿀 수 있습니다.

나는 한 단위 ( ToPredicate방법) 만 테스트 하고 있지만 테스트에서 생각할 수있는 모든 것을 다루고 있습니다.


46
오류 감지로 인해 여러 어설 션이 잘못되었습니다. 첫 번째 Assert.IsTrue에 실패한 경우 다른 어설 션이 실행되지 않으며 정보가 제공되지 않습니다. 반면에 5 번의 주장으로 1 번 대신 5 번의 테스트를한다면 유용한 정보를 얻을 수 있습니다
Sly

6
모든 주장이 동일한 종류의 기능을 테스트해도 여전히 나쁜 것으로 생각하십니까? 위와 같이이 예에서는 조건을 테스트하므로이 중 하나라도 실패하면 수정해야합니다. 당신이 이전에 실패한 경우 마지막 두 가지 주장을 놓칠 수 있다는 것이 중요합니까?
cringe

106
한 번에 하나씩 문제를 해결합니다. 따라서 테스트가 두 번 이상 실패 할 수 있다는 사실은 나를 귀찮게하지 않습니다. 그것들을 나누면 같은 오류가 발생하지만 한 번에 모두 발생합니다. 한 번에 한 단계 씩 문제를 해결하는 것이 더 쉽다는 것을 알게되었습니다. 나는이 경우 마지막 두 가지 주장이 아마도 자신의 테스트로 리팩토링 될 수 있음을 인정한다.
Matt Ellen

19
- 귀하의 경우는 NUnit를 추가 속성의 TestCase를 가지고 그 이유는 매우 대표 nunit.org/?p=testCase&r=2.5
Restuta

10
Google C ++ 테스트 프레임 워크에는 ASSERT () 및 EXPECT ()가 있습니다. EXPECT ()가 계속되는 동안 ASSERT ()는 실패시 중지됩니다. 테스트에서 둘 이상의 것을 검증하려고 할 때 매우 편리합니다.
ratkok 2016 년

21

높은 수준의 동작을 확인하기 위해 단위 테스트를 사용하는 경우 단일 테스트에 여러 어설 션을 절대적으로 넣습니다. 비상 알림 코드에 실제로 사용하는 테스트가 있습니다. 테스트 전에 실행되는 코드는 시스템을 메인 프로세서가 실행되면 알람이 전송되는 상태로 만듭니다.

@Test
public void testAlarmSent() {
    assertAllUnitsAvailable();
    assertNewAlarmMessages(0);

    pulseMainProcessor();

    assertAllUnitsAlerting();
    assertAllNotificationsSent();
    assertAllNotificationsUnclosed();
    assertNewAlarmMessages(1);
}

그것은 코드가 내가 기대하는 방식으로 작동하고 있음을 확신하기 위해 프로세스의 모든 단계에서 존재 해야하는 조건을 나타냅니다. 단일 어설 션이 실패하면 나머지 어설 션도 실행되지 않을 것입니다. 시스템의 상태가 더 이상 유효하기 때문에, 그 이후의 주장은. 나에게 소중한 아무것도 말하지 않을 경우 * assertAllUnitsAlerting()실패, 그때의 만들기 위해 무엇을할지 모르겠다 assertAllNotificationSent()'의 성공 또는 실패 나는 이전에 오류를 일으키는 것을 결정까지를 그것을 수정했습니다.

(*-문제를 디버깅하는 데 유용 할 수 있지만 테스트에 실패한 가장 중요한 정보는 이미 수신되었습니다.)


그렇게하면 종속 테스트와 함께 테스트 프레임 워크를 더 잘 사용해야합니다 (예 : testng가이 기능을 지원함)
Kemoda

8
코드가 수행하는 작업과 상태 변경을 확신 할 수 있도록 이와 같은 테스트를 작성합니다. 이것은 단위 테스트가 아니라 통합 테스트라고 생각합니다.
mdma

assertAlarmStatus (int numberOfAlarmMessages);로 리팩토링하는 것에 대한 당신의 의견은 무엇입니까?
Borjab

1
당신의 주장은 아주 좋은 시험 이름을 만들 것입니다.
Bjorn Tipling

유효하지 않은 입력에서도 테스트를 실행하는 것이 좋습니다. 그것은 당신에게 그런 식으로 더 많은 정보를 제공합니다 (특히 기대하지 않았을 때 여전히 지나가는 경우).
CurtainDog

8

내가 생각하는 또 다른 이유는 한 가지 방법으로 여러 가지 주장이 나쁜 것이 아니라는 것입니다. 다음 코드에서 설명합니다.

class Service {
    Result process();
}

class Result {
    Inner inner;
}

class Inner {
    int number;
}

내 테스트에서 단순히 클래스 인스턴스 service.process()에서 올바른 숫자 를 반환 하는지 테스트하고 싶습니다 Inner.

테스트 대신 ...

@Test
public void test() {
    Result res = service.process();
    if ( res != null && res.getInner() != null ) Assert.assertEquals( ..., res.getInner() );
}

나는 일을 해요

@Test
public void test() {
    Result res = service.process();
    Assert.notNull(res);
    Assert.notNull(res.getInner());
    Assert.assertEquals( ..., res.getInner() );
}

2
그리고 그것은 좋은 일입니다, 당신은 당신의 테스트에 조건부 논리가 없어야합니다. 테스트가 더 복잡하고 읽기 어려워집니다. 그리고 Roy는 자신의 블로그 게시물에서 한 객체에 대해 여러 개의 주장을하는 것이 대부분의 경우라고 설명했습니다. 그래서 당신이 가진 사람들은 단지 가드 주장이며 그것들을 갖는 것은 괜찮습니다.
Restuta

2
귀하 Assert.notNull가 널 (null) 인 경우의 중복 있으며, 테스트는 NPE와 함께 실패합니다.
sara

1
또한 첫 번째 예제 ( if)는 resis isnull
sara

1
@kai notNull은 중복 적이지만 동의하지만 예외 대신 주장을하는 것이 더 깨끗하다고 ​​생각합니다 (그리고 적절한 메시지로 게으르지 않은 경우).
Betlista

1
글쎄 실패한 주장도 예외를 던지고 두 경우 모두 스택 추적과 함께 던진 정확한 행에 대한 직접 링크를 얻으므로 개인적으로 어쨌든 검사 할 전제 조건으로 테스트를 어지럽히 지 않을 것입니다. Assert.assertEquals(..., service.process().getInner());라인이 "너무 길면"추출 된 변수로 가능한 oneliner a la를 선호합니다
sara

6

한 가지 이유로 테스트가 실패해야한다는 규칙 내에서 여러 개의 어설 션을 쓰는 것이 유효한 경우가 많이 있다고 생각합니다.

예를 들어, 날짜 문자열을 구문 분석하는 함수를 상상해보십시오.

function testParseValidDateYMD() {
    var date = Date.parse("2016-01-02");

    Assert.That(date.Year).Equals(2016);
    Assert.That(date.Month).Equals(1);
    Assert.That(date.Day).Equals(0);
}

테스트가 실패하면 한 가지 이유 때문이므로 구문 분석이 올바르지 않습니다. 이 테스트가 세 가지 이유로 실패 할 수 있다고 주장 할 경우 IMHO는 "한 가지 이유"에 대한 정의에서 너무 세분화 될 수 있습니다.


3

[Test] 메서드 자체 내에 여러 개의 어설 션이있는 것이 좋은 상황에 대해서는 모르겠습니다. 사람들이 여러 가지 어설 션을 사용하는 주된 이유는 테스트중인 각 클래스에 대해 하나의 [TestFixture] 클래스를 사용하려고하기 때문입니다. 대신 테스트를 더 많은 [TestFixture] 클래스로 나눌 수 있습니다. 이를 통해 첫 번째 어설 션이 실패한 방식 대신 예상 한 방식으로 코드가 반응하지 않은 여러 가지 방법을 볼 수 있습니다. 이를 달성하는 방법은 많은 [TestFixture] 클래스를 테스트하여 클래스 당 하나 이상의 디렉토리를 테스트하는 것입니다. 각 [TestFixture] 클래스는 테스트 할 개체의 특정 상태에 따라 이름이 지정됩니다. [SetUp] 메서드는 객체를 클래스 이름으로 설명 된 상태로 만듭니다. 그런 다음 객체의 현재 상태를 고려할 때 각각 서로 다른 사실을 주장하는 여러 개의 [Test] 메서드가 있습니다. 각 [Test] 메소드는 코드를 영어로 읽는 대신 개념의 이름을 따서 명명 한 경우를 제외하고는 주장하는 이름을 따서 명명됩니다. 그런 다음 각 [Test] 메소드 구현에는 무언가를 주장하는 단일 코드 행만 필요합니다. 이 접근 방식의 또 다른 장점은 테스트 대상과 클래스 및 메서드 이름을보고 예상 한 내용이 명확 해짐에 따라 테스트를 읽기 쉽게 만드는 것입니다. 테스트하려는 모든 작은 경우를 실현하기 시작하고 버그를 발견하면 확장 성이 향상됩니다. 아마도 코드를 영어로 읽는 대신 개념의 이름을 따서 명명했을 수도 있습니다. 그런 다음 각 [Test] 메소드 구현에는 무언가를 주장하는 단일 코드 행만 필요합니다. 이 접근 방식의 또 다른 장점은 테스트 대상과 클래스 및 메서드 이름을보고 예상 한 내용이 명확 해짐에 따라 테스트를 읽기 쉽게 만드는 것입니다. 테스트하려는 모든 작은 경우를 실현하기 시작하고 버그를 발견하면 확장 성이 향상됩니다. 아마도 코드를 영어로 읽는 대신 개념의 이름을 따서 명명했을 수도 있습니다. 그런 다음 각 [Test] 메소드 구현에는 무언가를 주장하는 단일 코드 행만 필요합니다. 이 접근 방식의 또 다른 장점은 테스트 대상과 클래스 및 메서드 이름을보고 예상 한 내용이 명확 해짐에 따라 테스트를 읽기 쉽게 만드는 것입니다. 테스트하려는 모든 작은 경우를 실현하기 시작하고 버그를 발견하면 확장 성이 향상됩니다. 클래스와 메소드 이름을보고 기대하는 것. 테스트하려는 모든 작은 경우를 실현하기 시작하고 버그를 발견하면 확장 성이 향상됩니다. 클래스와 메소드 이름을보고 기대하는 것. 테스트하려는 모든 작은 경우를 실현하기 시작하고 버그를 발견하면 확장 성이 향상됩니다.

일반적으로 이는 [SetUp] 메서드 내의 마지막 코드 줄이 [TestFixture]의 전용 인스턴스 변수에 속성 ​​값 또는 반환 값을 저장해야 함을 의미합니다. 그런 다음 다른 [Test] 메소드에서이 인스턴스 변수에 대해 여러 가지 다른 것을 주장 할 수 있습니다. 또한 테스트중인 오브젝트의 다른 특성이 원하는 상태로 설정되어 있는지에 대한 어설 션을 작성할 수도 있습니다.

때로는 객체를 원하는 상태로 만들기 전에 엉망이되지 않도록 테스트중인 객체를 원하는 상태로 가져 오는 동안 어설 션을 작성해야합니다. 이 경우 해당 추가 어설 션이 [SetUp] 메서드 안에 나타납니다. [SetUp] 방법으로 문제가 발생하면 테스트하려는 대상 상태에 도달하기 전에 테스트에 문제가있는 것이 분명합니다.

발생할 수있는 또 다른 문제는 발생했을 것으로 예상되는 예외를 테스트하고 있다는 것입니다. 위의 모델을 따르지 않도록 유혹 할 수 있습니다. 그러나 [SetUp] 메서드 내에서 예외를 잡아서 인스턴스 변수에 저장하면 여전히 달성 할 수 있습니다. 이를 통해 예외에 대해 각각 고유 한 [Test] 방법으로 여러 가지를 주장 할 수 있습니다. 그런 다음 테스트중인 오브젝트에 대해 다른 사항을 주장하여 예외가 발생하여 의도하지 않은 부작용이 없는지 확인할 수 있습니다.

예 (여러 파일로 나 into) :

namespace Tests.AcctTests
{
    [TestFixture]
    public class no_events
    {
        private Acct _acct;

        [SetUp]
        public void SetUp() {
            _acct = new Acct();
        }

        [Test]
        public void balance_0() {
            Assert.That(_acct.Balance, Is.EqualTo(0m));
        }
    }

    [TestFixture]
    public class try_withdraw_0
    {
        private Acct _acct;
        private List<string> _problems;

        [SetUp]
        public void SetUp() {
            _acct = new Acct();
            Assert.That(_acct.Balance, Is.EqualTo(0));
            _problems = _acct.Withdraw(0m);
        }

        [Test]
        public void has_problem() {
            Assert.That(_problems, Is.EquivalentTo(new string[] { "Withdraw amount must be greater than zero." }));
        }

        [Test]
        public void balance_not_changed() {
            Assert.That(_acct.Balance, Is.EqualTo(0m));
        }
    }

    [TestFixture]
    public class try_withdraw_negative
    {
        private Acct _acct;
        private List<string> _problems;

        [SetUp]
        public void SetUp() {
            _acct = new Acct();
            Assert.That(_acct.Balance, Is.EqualTo(0));
            _problems = _acct.Withdraw(-0.01m);
        }

        [Test]
        public void has_problem() {
            Assert.That(_problems, Is.EquivalentTo(new string[] { "Withdraw amount must be greater than zero." }));
        }

        [Test]
        public void balance_not_changed() {
            Assert.That(_acct.Balance, Is.EqualTo(0m));
        }
    }
}

이 경우 TestCase 입력을 어떻게 처리합니까?
Simon Gillbee 2016

현재 제 조직에서는 20,000 개 이상의 단위 테스트를 보여 드리고 있습니다. 이것은 악몽이다. 많은 테스트 설정 코드가 복사 / 붙여 넣기되어 잘못된 테스트 설정과 잘못된 테스트가 발생합니다. 각 [Test]메소드에 대해 해당 클래스가 다시 인스턴스화되고 [SetUp]메소드가 다시 실행됩니다. 이로 인해 .NET 가비지 콜렉터가 종료되고 테스트가 로컬에서 5 분 이상, 빌드 서버에서 20 분 이상과 같이 매우 느리게 실행됩니다. 20K 테스트는 약 2-3 분 안에 실행되어야합니다. 나는 것 하지 특히 큰 틱 테스트 스위트에 대한 모든에서이 테스트 스타일을 추천합니다.
fourpastmidnight

@fourpastmidnight 당신이 말한 것의 대부분은 유효한 비판처럼 보이지만 설정 코드 복사 및 붙여 넣기 및 잘못된 점은 구조의 문제가 아니라 무책임한 프로그래머 (무책임한 관리자 또는 부정적인 환경의 결과 일 수 있음)의 문제입니다 나쁜 프로그래머보다 더). 사람들이 코드를 복사하여 붙여 넣기 만하면 코드가 정확하고 어떤 이유로 든 코드를 이해하기를 원하지 않는다면 어떤 식 으로든이 작업을 수행하지 못하도록하거나 교육을받을 수 없으면 교육을 받아야합니다. 훈련. 그것은 모든 훌륭한 프로그래밍 원칙에 위배됩니다.
still_dreaming_1

그러나 일반적으로 동의합니다. 이것은 모든 종류의 문제로 이어질 많은 팽창 / 수하물 / 복제를 초래할 미친 과잉입니다. 나는 미쳤고 이런 것들을 추천했다. 그것이 전날 나 자신에 대해 매일 말할 수 있기를 희망하는 것입니다. 왜냐하면 나는 더 좋은 방법을 찾는 것을 멈추지 않기 때문입니다.
still_dreaming_1

@ still_dreaming_1 wrt "무책임한 프로그래머":이 행동이 큰 문제라는 데 동의합니다. 그러나 이런 종류의 테스트 구조는 실제로 이러한 종류의 동작을 더 좋게 또는 더 나쁘게 초대합니다. 나쁜 개발 관행을 제외하고,이 양식에 대한 나의 반대 의견은 테스트 성능을 실제로 떨어 뜨린다는 것입니다. 느리게 실행되는 테스트 스위트보다 나쁜 것은 없습니다. 느리게 실행되는 테스트 스위트는 사람들이 로컬에서 테스트를 실행하지 않으며 중간 빌드에서 사람들이 테스트를 건너 뛰려는 유혹을 받기도합니다. 와.
fourpastmidnight

2

동일한 테스트에서 여러 어설 션이있는 것은 테스트가 실패한 경우에만 문제가됩니다. 그런 다음 테스트를 디버그하거나 예외를 분석하여 실패한 어설 션을 찾아야 할 수 있습니다. 각 테스트마다 한 번의 주장으로 일반적으로 무엇이 잘못되었는지 파악하는 것이 더 쉽습니다.

동일한 어설 션에서 항상 여러 조건으로 다시 작성할 수 있으므로 여러 어설 션이 실제로 필요한 시나리오는 생각할 수 없습니다 . 그러나 예를 들어 입력이 잘못되어 이후 단계가 중단 될 위험이있는 대신 단계 사이의 중간 데이터를 확인하는 여러 단계가있는 것이 바람직 할 수 있습니다.


1
여러 조건을 단일 어설 션으로 결합하는 경우 실패 할 때 하나만 실패하면됩니다. 여러 가지 어설 션을 사용하면 그 중 일부 (실패까지 포함)에 대해 구체적으로 알 수 있습니다. 반환 된 배열에 단일 값이 포함되어 있는지 확인하십시오. null이 아닌지 확인한 다음 정확히 하나의 요소를 갖고 해당 요소의 값을가집니다. (플랫폼에 따라) 값을 즉시 확인하면 null 역 참조가 발생하고 (널 어설 션 실패보다 도움이되지 않음) 배열 길이를 확인하지 않습니다.
Richard

@Richard : 결과를 얻은 다음 그 결과에서 무언가를 추출하는 것은 여러 단계의 과정이 될 것이므로 답의 두 번째 단락에서 다뤘습니다.
구파

2
경험 법칙 : 테스트에서 여러 개의 어설 션이있는 경우 각각 다른 메시지가 있어야합니다. 그런 다음이 문제가 없습니다.
Anthony

그리고 NCrunch와 같은 품질 테스트 러너를 사용하면 테스트 코드와 테스트중인 코드 모두에서 테스트가 실패한 라인을 정확하게 보여줍니다.
fourpastmidnight

2

테스트에 실패하면 다음 어설 션도 중단되는지 알 수 없습니다. 종종 문제의 원인을 파악하기 위해 귀중한 정보가 누락 될 수 있습니다. 내 해결책은 하나의 assert를 사용하지만 여러 값을 사용하는 것입니다.

String actual = "val1="+val1+"\nval2="+val2;
assertEquals(
    "val1=5\n" +
    "val2=hello"
    , actual
);

그러면 한 번에 모든 실패한 어설 션을 볼 수 있습니다. 대부분의 IDE가 비교 대화 상자에서 문자열 차이를 나란히 표시하기 때문에 여러 줄을 사용합니다.


2

단일 테스트 기능에 여러 개의 어설 션이있는 경우 수행중인 테스트와 직접 관련이있을 것으로 예상됩니다. 예를 들어

@Test
test_Is_Date_segments_correct {

   // It is okay if you have multiple asserts checking dd, mm, yyyy, hh, mm, ss, etc. 
   // But you would not have any assert statement checking if it is string or number,
   // that is a different test and may be with multiple or single assert statement.
}

많은 테스트를하는 것 (아마도 과잉이라고 생각 될 때도)은 나쁘지 않습니다. 가장 중요하고 필수적인 테스트를받는 것이 더 중요하다고 주장 할 수 있습니다. 따라서 어설 션 할 때 여러 어설 션에 대해 너무 걱정하지 말고 어설 션을 올바르게 배치하십시오. 둘 이상이 필요한 경우 둘 이상을 사용하십시오.


1

단위 테스트의 목표는 실패한 부분에 대해 가능한 많은 정보를 제공하는 동시에 가장 근본적인 문제를 정확하게 정확히 찾아내는 것입니다. 다른 어설 션이 실패했거나 다른 말로 테스트간에 종속성 관계가있는 경우 한 어설 션이 실패한다는 것을 논리적으로 알면 단일 테스트 내에서 여러 어설 션으로 롤링하는 것이 좋습니다. 이는 단일 테스트 내에서 첫 번째 어설 션에 대해 실패한 경우 제거 할 수있는 명백한 실패로 테스트 결과를 버리지 않는 이점이 있습니다. 이 관계가 존재하지 않는 경우에는 이러한 어설 션을 개별 테스트로 분리하는 것이 기본적으로 선호됩니다. 그렇지 않으면 이러한 실패를 찾으려면 모든 문제를 해결하기 위해 여러 번의 테스트 실행을 반복해야하기 때문입니다.

그런 다음 지나치게 복잡한 테스트를 작성해야하는 방식으로 단위 / 클래스를 디자인하면 테스트 중에 부담이 줄어들고 더 나은 디자인을 촉진 할 수 있습니다.


1

네, 여러 주장을 가지고 괜찮 만큼 실패하는 테스트가 실패를 진단 할 수 있도록 당신에게 충분한 정보를 제공합니다. 이것은 테스트 대상과 실패 모드에 따라 다릅니다.

적절한 단위 테스트는 정확히 한 가지 이유로 실패해야하므로 단위 테스트 당 하나의 어설 션을 사용해야합니다.

나는 그러한 공식이 도움이되는 것을 결코 발견하지 못했습니다. 두 문자열이 같다는 주장을 고려하십시오. 이는 두 문자열의 길이가 동일하고 해당 색인의 각 문자가 같다는 것을 의미하는 것과 의미가 같습니다.

다중 어설 션 시스템을 단일 어설 션으로 다시 작성할 수 있고 단일 어설 션을 더 작은 어설 션 세트로 분해 할 수 있다고 일반화하고 말할 수 있습니다.

따라서 코드의 명확성과 테스트 결과의 명확성에 중점을 두어 그 반대가 아니라 사용하는 어설 션 수를 안내하십시오.


0

답은 매우 간단합니다. 둘 이상의 속성, 동일한 객체 또는 두 개의 다른 객체를 변경하는 함수를 테스트하고 함수의 정확성이 모든 변경 결과에 따라 결정되면 주장하고 싶습니다. 이러한 모든 변경 사항이 올바르게 수행되었습니다!

나는 논리적 인 개념에 대한 아이디어를 얻었지만 그 반대의 결론은 함수가 둘 이상의 객체를 변경해서는 안된다는 것을 말할 것입니다. 그러나 그것은 내 경험으로 모든 경우에 구현하는 것이 불가능합니다.

은행 거래의 논리적 개념을 취하십시오. 대부분의 경우 한 은행 계좌에서 금액을 인출하는 것이 해당 금액을 다른 계좌에 추가하는 것을 포함해야합니다. 이 두 가지를 분리하고 싶지 않으며 원자 단위를 형성합니다. 두 가지 기능 (withdraw / addMoney)을 만들어 두 가지 다른 단위 테스트를 작성할 수도 있습니다. 그러나이 두 가지 조치는 한 트랜잭션 내에서 발생해야하며 트랜잭션이 작동하는지 확인하려고합니다. 이 경우 개별 단계를 성공적으로 수행하는 것만으로는 충분하지 않습니다. 테스트에서 두 은행 계좌를 모두 확인해야합니다.

단위 테스트에서 테스트하지 않고 통합 또는 수락 테스트에서 테스트하는 더 복잡한 예제가있을 수 있습니다. 그러나 그 경계는 유창합니다. IMHO! 결정하기가 쉽지 않고 상황과 개인적 취향의 문제입니다. 하나에서 돈을 인출하고 다른 계정에 추가하는 것은 여전히 ​​매우 간단한 기능이며 확실히 단위 테스트 후보입니다.


-1

이 질문은 스파게티와 라자냐 코드 문제 간의 균형을 맞추는 고전적인 문제와 관련이 있습니다.

여러 가지 어설 션을 사용하면 테스트에 대한 아이디어가없는 스파게티 문제에 쉽게 도달 할 수 있지만 테스트 당 단일 어설 션을 사용하면 큰 라자냐에서 여러 테스트를 수행하여 테스트를 읽을 수 없게 만들 수 있습니다. .

몇 가지 예외가 있지만이 경우 진자를 중간에 유지하는 것이 답입니다.


-3

나는 일반적으로 "한 가지 이유로 만 실패"에 동의하지 않습니다. 더 중요한 것은 테스트가 짧고 명확하게 읽는다는 것입니다.

이것은 항상 달성 가능한 것은 아니며 테스트가 복잡 할 때 (긴) 설명적인 이름과 적은 수의 테스트는 더 의미가 있습니다.

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