단위 테스트는 얼마나 깊습니까?


88

내가 TDD에 대해 알게 된 것은 테스트를 설정하는 데 시간이 걸리고 자연스럽게 게으르다는 것입니다. 저는 항상 가능한 한 적은 코드를 작성하고 싶습니다. 내가하는 첫 번째 일은 내 생성자가 모든 속성을 설정했지만 이것이 과잉입니까?

내 질문은 단위 테스트를 어떤 수준으로 작성합니까?

.. 너무 많은 테스트 사례가 있나요?

답변:


221

나는 테스트가 아니라 작동하는 코드에 대해 돈을 받는다. 그래서 나의 철학은 주어진 신뢰 수준에 도달하기 위해 가능한 한 적은 테스트를하는 것이다. . 일반적으로 생성자에 잘못된 변수를 설정하는 것과 같은 실수를하지 않으면 테스트하지 않습니다. 나는 테스트 오류를 ​​이해하는 경향이 있으므로 복잡한 조건이있는 논리가있을 때 특히주의합니다. 팀에서 코딩 할 때, 저는 우리가 전체적으로 잘못되는 경향이있는 코드를 신중하게 테스트하기 위해 전략을 수정합니다.

다른 사람들은이 철학에 기반한 다른 테스트 전략을 가지고있을 것이지만, 테스트가 코딩의 내부 루프에 가장 잘 맞을 수있는 방법에 대한 미성숙 한 이해 상태를 감안할 때 나에게는 합리적으로 보입니다. 이제 10 년 또는 20 년 후에 우리는 어떤 테스트를 작성하고, 어떤 테스트를 작성하지 않으며, 차이점을 어떻게 구분하는지에 대한보다 보편적 인 이론을 갖게 될 것입니다. 그 동안 실험은 순서대로 보인다.


40
세상은 Kent Beck이 이것을 말할 것이라고 생각하지 않습니다! Kent Beck이 할 것이라고 생각하기 때문에 100 % 커버리지를 충실히 추구하는 수많은 개발자들이 있습니다! 나는 당신이 XP 책에서 항상 Test First를 종교적으로 고수하지는 않는다고 말했습니다. 하지만 저도 놀랐습니다.
Charlie Flowers

6
개발자가 생성하는 코드는 자신의 것이 아니기 때문에 실제로 동의하지 않습니다. 다음 스프린트는 다른 사람이 코드를 변경하고 "알지 못하는"오류를 범할 것입니다. 또한 TDD는 먼저 테스트에 대해 생각합니다. 따라서 코드의 일부를 테스트한다고 가정하여 TDD를 수행하면 잘못된 것입니다
Ricardo Rodrigues

2
보험에 관심이 없습니다. 저는 Beck이 테스트 실패에 대한 응답으로 작성되지 않은 코드를 얼마나 자주 커밋하는지에 매우 관심이 있습니다.
sheldonh 2011

1
@RicardoRodrigues, 다른 사람들이 나중에 작성할 코드를 다루는 테스트를 작성할 수 없습니다. 그것이 그들의 책임입니다.
Kief

2
그것은 내가 쓴 것이 아닙니다.주의 깊게 읽으십시오. 나는 당신이 자신의 코드 부분만을 다루기 위해 테스트를 작성하고, "당신이 실수하지 않는다는 것을 알고있는"부분을 드러내지 않고 그 부분이 변경되고 적절한 테스트가 없으면 바로 거기에 문제가 있다고 썼습니다. 그리고 그것은 TDD가 아닙니다.
Ricardo Rodrigues

20

깨질 것으로 예상되는 항목과 엣지 케이스에 대한 단위 테스트를 작성하십시오. 그 후에 버그에 대한 수정을 작성하기 전에 버그 보고서가 들어 오면 테스트 케이스를 추가해야합니다. 개발자는 다음을 확신 할 수 있습니다.

  1. 버그가 수정되었습니다.
  2. 버그가 다시 나타나지 않습니다.

첨부 된 주석에 따라-시간이 지남에 따라 주어진 클래스에서 많은 버그가 발견되면 단위 테스트 작성에 대한 이러한 접근 방식이 문제를 일으킬 수 있다고 생각합니다. 이것은 아마도 재량권이 도움이 될 것입니다.-다시 발생할 가능성이있는 버그에 대해서만 단위 테스트를 추가하거나 그 재발이 심각한 문제를 일으킬 수있는 곳입니다. 단위 테스트에서 통합 테스트의 척도가 이러한 시나리오에서 도움이 될 수 있음을 발견했습니다. 상위 코드 경로를 테스트하면 하위 코드 경로를 포함 할 수 있습니다.


내가 쓴 버그의 양으로 이것은 안티 패턴이 될 수 있습니다. 문제가 발생한 코드에 대한 수백 개의 테스트를 사용하면 테스트를 읽을 수 없게되며 이러한 테스트를 다시 작성할 때 오버 헤드가 될 수 있습니다.
Johnno Nolan

@JohnNolan : 테스트 가독성이 그토록 중요합니까? IMHO는 적어도 이러한 버그 별 회귀 테스트에서는 그렇지 않습니다. 테스트를 자주 다시 작성하는 경우 너무 낮은 수준에서 테스트하고있을 수 있습니다. 이상적으로는 구현이 변경 되더라도 인터페이스가 비교적 안정적으로 유지되어야하며 인터페이스 수준에서 테스트해야합니다 (실제 세계가 종종 그렇지 않다는 것을 알고 있지만 t like that ... :-/) 만약 당신의 인터페이스가 크게 변한다면, 나는 그것들을 재 작성하는 것보다 이러한 버그 특정 테스트의 대부분 또는 전부를 폐기하는 것을 선호합니다.
j_random_hacker

@j_random_hacker 예, 물론 가독성이 중요합니다. 테스트는 문서의 한 형태이며 프로덕션 코드만큼 중요합니다. 나는 주요 변경 사항에 대한 스크랩 테스트가 좋은 것이며 (tm) 인터페이스 수준에서 테스트를 수행해야한다는 데 동의합니다.
Johnno Nolan 2012

19

모든 것이 가능한 한 단순해야하지만 더 단순해서는 안됩니다. -A. 아인슈타인

TDD에 대해 가장 오해받는 것 중 하나는 그 안에있는 첫 번째 단어입니다. 테스트. 이것이 BDD가 등장한 이유입니다. 사람들은 첫 번째 D가 중요한 것, 즉 Driven이라는 것을 실제로 이해하지 못했기 때문입니다. 우리 모두는 테스트에 대해 조금 또는 많이 생각하고 디자인 추진에 대해 조금 또는 조금 생각하는 경향이 있습니다. 그리고 이것은 귀하의 질문에 대한 모호한 대답이라고 생각합니다. 그러나 실제로 테스트하는 대신 코드를 구동하는 방법을 고려해야합니다. 그것은 Coverage-tool이 당신을 도울 수있는 것입니다. 디자인은 훨씬 더 크고 문제가되는 문제입니다.


네, 모호합니다 ... 이것은 생성자가 일부 동작이 아니기 때문에 우리가 그것을 테스트해서는 안된다는 것을 의미합니까? 하지만 MyClass.DoSomething ()을 테스트해야합니까?
Johnno Nolan

글쎄요, : P에 의존합니다 ... 시공 테스트는 종종 레거시 코드를 테스트하려고 할 때 좋은 시작입니다. 그러나 나는 (대부분의 경우) 무언가를 처음부터 디자인하기 시작할 때 건설 테스트를 떠날 것입니다.
kitofr

그것은 주도적 인 디자인이 아니라 주도적 인 개발입니다. 즉, 작업 기준을 확보하고, 기능을 확인하기위한 테스트를 작성하고, 개발을 진행합니다. 거의 항상 처음으로 코드를 고려하기 직전에 테스트를 작성합니다.
Evan Plaice

마지막 D, 디자인은 사람들이 잊어 버리고 집중을 잃는 단어라고 말하고 싶습니다. 테스트 기반 설계에서는 테스트 실패에 대한 응답으로 코드를 작성합니다. 테스트 중심 설계를 수행하는 경우 테스트되지 않은 코드가 얼마나 남았습니까?
sheldonh

15

"모든 것"테스트를 제안하는 사람들에게 : "완전히 테스트"하는 방법 int square(int x)은 공통 언어 및 일반적인 환경에서 약 40 억 개의 테스트 케이스가 필요 하다는 것을 인식하십시오 .

사실, 그보다 훨씬 더 나쁜 : 방법은 void setX(int newX)또한 의무가 없습니다 이외의 다른 멤버의 값을 변경하는 x- 당신이를 테스트하고 obj.y, obj.z모든 호출 한 후 변경되지 않은 상태로 유지 등 obj.setX(42);?

"모든 것"의 일부만 테스트하는 것이 실용적입니다. 이것을 받아 들인 후에는 믿을 수 없을 정도로 기본적인 행동을 테스트하지 않는 것을 고려하는 것이 더 맛있어집니다. 모든 프로그래머는 버그 위치 의 확률 분포 를 가지고 있습니다. 현명한 접근 방식은 버그 확률이 ​​높을 것으로 예상되는 테스트 지역에 에너지를 집중하는 것입니다.


9

고전적인 대답은 "파손될 수있는 모든 것을 테스트"하는 것입니다. set 또는 get 외에는 아무것도하지 않는 setter와 getter를 테스트하는 것이 아마도 너무 많은 테스트이므로 시간이 걸리지 않는다는 의미로 해석합니다. IDE가 당신을 위해 작성하지 않는 한, 당신도 그렇게 할 수 있습니다.

생성자 속성을 설정 하지 않으면 나중에 오류가 발생할 수있는 경우 해당 속성이 설정되었는지 테스트하는 것은 과도하지 않습니다.


넵 그리고 이것은 많은 속성과 많은 생성자를 가진 클래스에 대한 바인딩입니다.
Johnno Nolan

문제가 사소할수록 (멤버를 0으로 초기화하는 것을 잊는 것과 같은) 디버그하는 데 더 많은 시간이 걸립니다.
Lev

5

내가 작성할 클래스의 가정을 다루기 위해 테스트를 작성합니다. 테스트는 요구 사항을 적용합니다. 본질적으로, 예를 들어 x가 절대 3 일 수없는 경우 해당 요구 사항을 충족하는 테스트가 있는지 확인하겠습니다.

변함없이 조건을 다루는 테스트를 작성하지 않으면 나중에 "인간"테스트 중에 나타납니다. 나는 확실히 그때 하나를 쓸 것이지만, 나는 그들을 일찍 잡을 것이다. 요점은 테스트가 지루하지만 (아마도) 필요하다는 것입니다. 나는 완료하기에 충분한 테스트를 작성하지만 그 이상은 아닙니다.


5

지금 간단한 테스트를 건너 뛰는 문제의 일부는 향후 리팩토링으로 인해 많은 논리로 인해 간단한 속성이 매우 복잡해질 수 있다는 것입니다. 가장 좋은 아이디어는 테스트를 사용하여 모듈의 요구 사항을 확인할 수 있다는 것입니다. X를 통과했을 때 Y를 되 찾아야한다면 그것이 테스트하고 싶은 것입니다. 그런 다음 나중에 코드를 변경할 때 X가 Y를 제공하는지 확인하고 나중에 해당 요구 사항이 추가 될 때 A에 대한 테스트를 추가하여 B를 제공 할 수 있습니다.

초기 개발 테스트를 작성하는 동안 보낸 시간이 첫 번째 또는 두 번째 버그 수정에서 효과가 있다는 것을 발견했습니다. 3 개월 동안 살펴 보지 않은 코드를 선택하고 수정 사항이 모든 경우를 다루고 "아마도"어떤 것도 손상시키지 않는지 합리적으로 확신 할 수있는 능력은 매우 중요합니다. 또한 단위 테스트는 스택 추적을 넘어서는 버그를 분류하는 데 도움이된다는 것을 알게 될 것입니다. 앱의 개별 부분이 작동하고 실패하는 방식을 확인하면 전체적으로 작동하거나 실패하는 이유에 대한 큰 통찰력을 얻을 수 있습니다.


4

대부분의 경우에 논리가 있다면 테스트 해보자. 여기에는 생성자 및 속성이 포함되며, 특히 속성에 둘 이상의 항목이 설정되는 경우 더욱 그렇습니다.

너무 많은 테스트와 관련하여 논쟁의 여지가 있습니다. 어떤 사람들은 모든 것이 견고성을 위해 테스트되어야한다고 말하고, 다른 사람들은 효율적인 테스트를 위해 깨질 수있는 것 (즉, 로직) 만 테스트해야한다고 말합니다.

나는 개인적인 경험에서 두 번째 캠프에 더 의지하고 싶지만 누군가가 모든 것을 테스트하기로 결정했다면 그것이 너무 많았다 고 말하지 않을 것입니다.

그래서, 아니오 – 일반적인 의미에서 "너무 많은"테스트는 개인만을위한 것이 아니라고 말하고 싶습니다.


3

Test Driven Development는 모든 테스트가 통과되면 코딩을 중지하는 것을 의미합니다.

속성에 대한 테스트가없는 경우이를 구현해야하는 이유는 무엇입니까? "불법"할당의 경우 예상되는 동작을 테스트 / 정의하지 않으면 속성은 어떻게해야합니까?

그러므로 나는 전적으로 클래스가 보여야 할 모든 행동을 테스트하는 것입니다. "기본"속성을 포함합니다.

이 테스트를 더 쉽게하기 위해 TestFixture값을 설정 / 가져 오기위한 확장 점을 제공하고 유효하고 잘못된 값 목록을 가져오고 속성이 올바르게 작동하는지 확인하는 단일 테스트가있는 간단한 NUnit 을 만들었습니다 . 단일 속성 테스트는 다음과 같습니다.

[TestFixture]
public class Test_MyObject_SomeProperty : PropertyTest<int>
{

    private MyObject obj = null;

    public override void SetUp() { obj = new MyObject(); }
    public override void TearDown() { obj = null; }

    public override int Get() { return obj.SomeProperty; }
    public override Set(int value) { obj.SomeProperty = value; }

    public override IEnumerable<int> SomeValidValues() { return new List() { 1,3,5,7 }; }
    public override IEnumerable<int> SomeInvalidValues() { return new List() { 2,4,6 }; }

}

람다와 속성을 사용하면 더 간결하게 작성할 수도 있습니다. MBUnit은 이와 같은 것들을 기본적으로 지원합니다. 요점은 위의 코드가 속성의 의도를 포착한다는 것입니다.

추신 : 아마도 PropertyTest 에는 객체의 다른 속성이 변경되지 않았는지 확인하는 방법이 있어야합니다 . 흠 .. 다시 드로잉 보드로.


mbUnit에 대한 프레젠테이션에갔습니다. 멋지네요.
Johnno Nolan

하지만 데이비드, 물어 볼게요 : 위의 켄트 벡의 대답에 놀랐나요? 그의 대답은 당신이 당신의 접근 방식을 다시 생각해야하는지 궁금하게 만드는가? 물론 누구든지 "높은 곳에서 대답"을했기 때문이 아닙니다. 그러나 Kent는 테스트 우선의 핵심 지지자 중 하나로 간주됩니다. 당신의 생각을위한 페니!
Charlie Flowers

@Charly : Kent의 반응은 매우 실용적입니다. 저는 다양한 소스의 코드를 통합 할 프로젝트에서 "그냥"작업하고 있으며 매우 높은 수준의 자신감 을 제공하고 싶습니다 .
David Schmitt

즉, 테스트 된 코드보다 더 간단한 테스트를 위해 노력하고 있으며 이러한 세부 수준은 모든 생성기, 모듈, 비즈니스 규칙 및 유효성 검사기가 함께 모이는 통합 테스트에서만 가치가있을 수 있습니다.
David Schmitt

1

가능한 최대 범위에 도달하기 위해 단위 테스트를합니다. 일부 코드에 도달 할 수 없으면 적용 범위가 최대한 꽉 찰 때까지 리팩터링합니다.

블라인드 쓰기 테스트를 마친 후 보통 각 버그를 재현하는 테스트 케이스를 하나씩 작성합니다.

저는 코드 테스트와 통합 테스트를 구분하는 데 익숙합니다. 통합 테스트 중에 (단위 테스트이지만 구성 요소 그룹에 대한 것이기 때문에 단위 테스트의 대상이 아님) 올바르게 구현 될 요구 사항을 테스트합니다.


1

따라서 테스트를 작성하여 프로그래밍을 많이할수록 테스트의 세분성 수준에 대한 걱정이 줄어 듭니다. 되돌아 보면 내가 행동 을 검증한다는 목표를 달성하기 위해 가능한 가장 간단한 일을하고있는 것 같습니다 . 이것은 내 코드가 내가 요청한 작업을 수행하고 있다는 확신을 생성하고 있음을 의미하지만 내 코드가 버그가 없다는 절대적인 보장으로 간주되지는 않습니다. 올바른 균형은 표준 동작을 테스트하고 아마도 한두 개의 경우를 테스트 한 다음 내 디자인의 다음 부분으로 이동하는 것이라고 생각합니다.

나는 이것이 모든 버그를 다루지 않을 것이며이를 포착하기 위해 다른 전통적인 테스트 방법을 사용한다는 것을 인정합니다.


0

일반적으로 나는 작게 시작하고 내가 알아야 할 입력과 출력으로 시작합니다. 그런 다음 버그를 수정하면서 수정 한 사항이 테스트되었는지 확인하기 위해 더 많은 테스트를 추가합니다. 그것은 유기적이고 저에게 잘 작동합니다.

너무 많이 테스트 할 수 있습니까? 아마도 당신의 애플리케이션이 얼마나 미션 크리티컬한지에 따라 다르 겠지만, 일반적으로주의를 기울이는 편이 더 낫습니다.


0

비즈니스 로직의 "핵심"에있는 모든 것을 테스트해야한다고 생각합니다. Getter 및 Setter도 허용하지 않을 수있는 음수 또는 null 값을 허용 할 수 있기 때문입니다. 시간이 있다면 (항상 상사에게 의존) 다른 비즈니스 로직과 이러한 개체를 호출하는 모든 컨트롤러를 테스트하는 것이 좋습니다 (단위 테스트에서 통합 테스트로 천천히 이동).


0

나는 부작용이없는 간단한 setter / getter 메서드를 단위 테스트하지 않습니다. 그러나 나는 다른 모든 공개 방법을 단위 테스트합니다. 알고리즘의 모든 경계 조건에 대한 테스트를 만들고 단위 테스트의 범위를 확인하려고합니다.

많은 일이지만 그만한 가치가 있다고 생각합니다. 디버거에서 코드를 단계별로 실행하는 것보다 코드 (심지어 테스트 코드 포함)를 작성하는 것이 좋습니다. 코드-빌드-배포-디버그주기는 매우 시간이 많이 걸리고 빌드에 통합 한 단위 테스트가 더 철저할수록 코드-빌드-배포-디버그주기를 거치는 시간이 줄어 듭니다.

왜 아키텍처를 코딩하고 있는지 말하지 않았습니다. 그러나 Java의 경우 Maven 2 , JUnit , DbUnit , CoberturaEasyMock을 사용 합니다.


나는 상당히 언어에 구애받지 않는 질문으로 말하지 않았습니다.
Johnno Nolan

TDD의 단위 테스트는 코드를 작성할 때뿐만 아니라 코드를 상속 한 사람으로부터 보호하고 getter 내에서 값을 형식화하는 것이 합리적이라고 생각합니다!
Paxic

0

더 많이 읽을수록 일부 단위 테스트는 패턴과 비슷하다고 생각합니다. 언어가 부족한 냄새입니다.

사소한 게터가 실제로 올바른 값을 반환하는지 테스트해야 할 때, 이는 게터 이름과 멤버 변수 이름을 혼용 할 수 있기 때문입니다. 루비의 'attr_reader : name'을 입력하면 더 이상 일어날 수 없습니다. 자바에서는 불가능합니다.

게터가 사소하지 않은 경우 에도 테스트를 추가 할 수 있습니다.


게터 테스트가 간단하다는 데 동의합니다. 그러나 나는 생성자 내에서 설정하는 것을 잊을 정도로 어리 석을 수 있습니다. 따라서 테스트가 필요합니다. 질문 한 이후로 생각이 달라졌습니다. 내 답변 참조 stackoverflow.com/questions/153234/how-deep-are-your-unit-tests/…
Johnno Nolan

1
사실, 나는 어떤면에서 전체적으로 단위 테스트가 언어 문제의 냄새라고 주장하고 싶습니다. Eiffel과 같이 계약 (메소드에 대한 사전 / 사후 조건)을 지원하는 언어는 여전히 일부 단위 테스트가 필요하지만 덜 필요합니다. 실제로 간단한 계약이라도 버그를 쉽게 찾을 수 있습니다. 메서드의 계약이 깨지면 버그는 일반적으로 해당 메서드에 있습니다.
Damien Pollet

@Damien : 아마도 단위 테스트와 계약은 변장에서 정말 똑같은 것일까 요? 내 말은, 계약을 "지원하는"언어는 기본적으로 다른 코드 조각 전후에 (선택적으로) 실행되는 코드 조각 (테스트)을 쉽게 작성할 수 있도록한다는 것입니다. 맞습니까? 문법이 충분히 간단하다면 기본적으로 계약을 지원하지 않는 언어는 전처리기를 작성하여 쉽게 확장 할 수 있습니다. 맞습니까? 아니면 하나의 접근 방식 (계약 또는 단위 테스트)이 할 수 있지만 다른 접근 방식으로는 할 수없는 일이 있습니까?
j_random_hacker

0

걱정하게 만드는 소스 코드를 테스트하십시오.

실수를하지 않는 한 매우 자신이있는 코드 부분을 테스트하는 데 유용하지 않습니다.

버그 수정을 테스트하여 버그를 처음 수정하고 마지막으로 수정합니다.

모호한 코드 부분에 대한 확신을 얻으려면 테스트하여 지식을 만드십시오.

헤비 및 중간 리팩토링 전에 테스트하여 기존 기능을 손상시키지 마십시오.


0

이 답변은 중요도 / 중요성으로 인해 단위 테스트를 원하는 주어진 방법에 대해 사용할 단위 테스트 수를 파악하는 데 더 적합합니다. McCabe의 Basis Path Testing 기술을 사용 하면 다음을 수행하여 단순한 "문장 범위"또는 "분기 범위"보다 코드 범위 신뢰도를 양적으로 높일 수 있습니다.

  1. 단위 테스트 할 메서드의 Cyclomatic Complexity 값을 결정합니다 (예를 들어 Visual Studio 2010 Ultimate는 정적 분석 도구를 사용하여이를 계산할 수 있습니다. calpoly.edu/~jdalbey/206/Lectures/BasisPathTutorial/index.html )
  2. 방법을 통해 흐르는 독립 경로의 기본 세트를 나열하십시오. 플로우 그래프 예제는 위의 링크를 참조하십시오.
  3. 2 단계에서 결정된 각 독립 기반 경로에 대한 단위 테스트 준비

모든 방법에 대해이 작업을 수행 하시겠습니까? 진심?
Kristopher Johnson
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.