단위 테스트 예상 결과를 하드 코딩해야합니까?


29

단위 테스트의 예상 결과가 하드 코딩되어야합니까, 아니면 초기화 된 변수에 의존 할 수 있습니까? 하드 코딩되거나 계산 된 결과가 단위 테스트에서 오류가 발생할 위험을 증가 시킵니까? 고려하지 않은 다른 요소가 있습니까?

예를 들어,이 두 가지 중 더 안정적인 형식은 무엇입니까?

[TestMethod]
public void GetPath_Hardcoded()
{
    MyClass target = new MyClass("fields", "that later", "determine", "a folder");
    string expected = "C:\\Output Folder\\fields\\that later\\determine\\a folder";
    string actual = target.GetPath();
    Assert.AreEqual(expected, actual,
        "GetPath should return a full directory path based on its fields.");
}

[TestMethod]
public void GetPath_Softcoded()
{
    MyClass target = new MyClass("fields", "that later", "determine", "a folder");
    string expected = "C:\\Output Folder\\" + string.Join("\\", target.Field1, target.Field2, target.Field3, target.Field4);
    string actual = target.GetPath();
    Assert.AreEqual(expected, actual,
        "GetPath should return a full directory path based on its fields.");
}

편집 1 : DXM의 답변에 따라 옵션 3이 선호되는 솔루션입니까?

[TestMethod]
public void GetPath_Option3()
{
    string field1 = "fields";
    string field2 = "that later";
    string field3 = "determine";
    string field4 = "a folder";
    MyClass target = new MyClass(field1, field2, field3, field4);
    string expected = "C:\\Output Folder\\" + string.Join("\\", field1, field2, field3, field4);
    string actual = target.GetPath();
    Assert.AreEqual(expected, actual,
        "GetPath should return a full directory path based on its fields.");
}

2
둘 다하세요. 진심으로. 테스트는 겹칠 수 있으며 겹쳐 야합니다. 또한 하드 코딩 된 값을 다루는 경우 일종의 데이터 기반 테스트를 살펴보십시오.
Job

세 번째 옵션은 내가 사용하고 싶은 것에 동의합니다. 컴파일시 조작을 제거하므로 옵션 1이 손상 될 것이라고 생각하지 않습니다.
kwelch

두 옵션 모두 하드 코딩을 사용하며 테스트가 C : \\
Qwertie

답변:


27

계산 된 기대 값은보다 강력하고 유연한 테스트 사례를 초래한다고 생각합니다. 또한 예상 결과를 계산하는 식에 좋은 변수 이름을 사용하면 예상 결과가 처음부터 어디에서 왔는지 훨씬 더 명확 해집니다.

그러나 특정 예제에서 SUT (테스트 대상 시스템)를 계산의 입력으로 사용하기 때문에 "소프트 코드 된"방법을 신뢰하지 않습니다. MyClass에 필드가 제대로 저장되지 않은 버그가있는 경우 예상 값 계산이 target.GetPath ()와 같은 잘못된 문자열을 사용하기 때문에 실제로 테스트가 통과됩니다.

내 제안은 의미가있는 곳에서 예상 값을 계산하는 것이지만 계산은 SUT 자체의 코드에 의존하지 않아야합니다.

OP의 응답 업데이트에 대한 응답으로 :

그렇습니다. 내 지식은 있지만 TDD 수행에 다소 제한된 경험을 바탕으로 옵션 # 3을 선택합니다.


1
좋은 지적! 테스트에서 확인되지 않은 개체에 의존하지 마십시오.
Hand-E-Food

SUT 코드가 중복되지 않습니까?
Abyx

1
어떤 식 으로든 SUT이 작동하는지 확인하는 방법입니다. 우리가 동일한 코드를 사용하고 파열되면 알 수 없습니다. 물론 계산을 수행하기 위해 많은 SUT를 복제 해야하는 경우 옵션 # 1이 더 좋아질 수 있습니다. 값을 하드 코딩하면됩니다.
DXM

16

코드가 다음과 같으면 어떻게됩니까?

MyTarget() // constructor
{
   Field1 = Field2 = Field3 = Field4 = "";
}

두 번째 예제는 버그를 잡을 수 없지만 첫 번째 예제는 버그를 잡을 수 없습니다.

일반적으로 소프트 코딩은 버그를 숨길 수 있으므로 사용하지 않는 것이 좋습니다. 예를 들면 다음과 같습니다.

string expected = "C:\\Output Folder" + string.Join("\\", target.Field1, target.Field2, target.Field3, target.Field4);

문제를 발견 할 수 있습니까? 하드 코딩 된 버전에서는 같은 실수를하지 않을 것입니다. 하드 코딩 된 값보다 정확한 계산을 얻는 것이 더 어렵습니다. 그래서 소프트 코딩 된 값보다 하드 코딩 된 값으로 작업하는 것을 선호합니다.

그러나 예외가 있습니다. 코드를 Windows 및 Linux에서 실행해야한다면 어떻게해야합니까? 경로가 달라야 할뿐만 아니라 다른 경로 구분 기호를 사용해야합니다! 차이점을 추상화하는 함수를 사용하여 경로를 계산하면 해당 컨텍스트에서 의미가있을 수 있습니다.


나는 당신이하는 말을 듣고 그것이 고려할만한 가치를줍니다. 소프트 코딩은 다른 테스트 사례 (예 : ConstructorShouldCorrectlyInitialiseFields) 통과에 의존합니다. 설명하는 실패는 다른 단위 테스트 실패에 의해 상호 참조됩니다.
Hand-E-Food

@ Hand-E-Food, 객체의 개별 방법에 대한 테스트를 작성하는 것처럼 들립니다. 하지마 개별 방법이 아니라 전체 객체의 정확성을 검사하는 테스트를 작성해야합니다. 그렇지 않으면 개체 내부의 변화와 관련하여 테스트가 취약합니다.
Winston Ewert

잘 모르겠습니다. 내가 준 예제는 순전히 가설 적이며 이해하기 쉬운 시나리오였습니다. 클래스와 객체의 공개 멤버를 테스트하기 위해 단위 테스트를 작성하고 있습니다. 그것이 올바른 사용법입니까?
Hand-E-Food

@ Hand-E-Food, 올바르게 이해하면 테스트 ConstructShouldCorrectlyInitialiseFields가 생성자를 호출 한 다음 필드가 올바르게 설정되었는지 확인합니다. 하지만 그렇게해서는 안됩니다. 내부 필드가 무엇을하고 있는지는 신경 쓰지 않아야합니다. 객체의 외부 동작이 올바른지 확인해야합니다. 그렇지 않으면 내부 구현을 교체해야 할 날이 올 수 있습니다. 내부 상태에 대한 주장을 한 경우 모든 테스트가 중단됩니다. 그러나 외부 행동에 대한 주장 만했다면 모든 것이 여전히 작동합니다.
Winston Ewert

@ Winston-- 실제로 xUnit Test Patterns 책을 통해 쟁기질을하고 있으며 그 전에는 The Unit of Unit Testing이 끝났습니다. 나는 내가 무슨 말을하고 있는지 아는 척하지 않을 것이지만, 나는 그 책에서 무언가를 집어 들었다고 생각합니다. 두 책 모두 각 테스트 방법이 절대 최소값을 테스트해야하며 전체 개체를 테스트하기위한 많은 테스트 사례가 있어야합니다. 이렇게하면 인터페이스 나 기능이 변경 될 때 대부분의 테스트 방법이 아닌 몇 가지 테스트 방법 만 수정하면됩니다. 또한 크기가 작기 때문에 변경이 더 쉬워야합니다.
DXM

4

제 생각에는 두 가지 제안 모두 이상적이지 않습니다. 가장 이상적인 방법은 다음과 같습니다.

[TestMethod]
public void GetPath_Hardcoded()
{
    const string f1 = "fields"; const string f2 = "that later"; 
    const string f3 = "determine"; const string f4 = "a folder";

    MyClass target = new MyClass( f1, f2, f3, f4 );
    string expected = "C:\\Output Folder\\" + string.Join("\\", f1, f2, f3, f4);
    string actual = target.GetPath();
    Assert.AreEqual(expected, actual,
        "GetPath should return a full directory path based on its fields.");
}

즉, 테스트는 개체의 내부 상태가 아닌 개체의 입력 및 출력을 기반으로 독점적으로 작동해야합니다. 물체는 블랙 박스로 취급해야합니다. (나는 단지 예제이기 때문에 Path.Combine 대신 string.Join을 사용하는 부적절 성과 같은 다른 문제는 무시합니다.)


1
모든 방법이 작동하는 것은 아닙니다. 많은 개체가 일부 개체의 상태를 변경하는 부작용을 올바르게 가지고 있습니다. 부작용이있는 방법에 대한 단위 테스트는 아마도 방법에 의해 영향을받는 물체의 상태를 평가해야 할 것입니다.
Matthew Flynn

그런 다음 해당 상태는 메소드의 출력으로 간주됩니다. 이 샘플 테스트의 목적은 MyClass의 생성자가 아닌 GetPath () 메서드를 확인하는 것입니다. @DXM의 답변을 읽으면 블랙 박스 접근 방식을 취하는 데 충분한 이유가 있습니다.
Mike Nakis

@ MatthewFlynn, 그 상태에 영향을받는 방법을 테스트해야합니다. 정확한 내부 상태는 구현 세부 사항이며 테스트 비즈니스는 없습니다.
Winston Ewert

@MatthewFlynn은 명확히하기 위해 표시된 예와 관련이 있거나 다른 단위 테스트를 위해 고려해야 할 다른 것입니까? 나는 target.Dispose(); Assert.IsTrue(target.IsDisposed);(매우 간단한 예) 와 같은 것이 중요하다는 것을 알 수있었습니다 .
Hand-E-Food

이 경우에도 IsDisposed 속성은 구현 세부 정보가 아니라 클래스의 공용 인터페이스에 없어서는 안될 부분입니다. (IDispose 인터페이스는 그러한 속성을 제공하지는 않지만 불행한 일입니다.)
Mike Nakis

2

토론에는 두 가지 측면이 있습니다.

1. 테스트 케이스에 대상 자체 사용
첫 번째 질문은 클래스 자체 를 사용하여 테스트 스텁에서 수행 한 작업에 의존하고 참여해야합니까? -대답은 아니요 일반적으로 테스트하는 코드에 대해 가정해서는 입니다. 이것이 제대로 수행되지 않으면 시간이 지남에 따라 버그가 일부 단위 테스트에 영향을받지 않습니다.

2. 하드 코딩
해야 당신이 하드 코드 ? 다시 대답은 아니오 입니다. 다른 소프트웨어와 마찬가지로 정보의 하드 코딩은 상황이 발전 할 때 어려워집니다. 예를 들어, 위의 경로를 다시 수정하려면 추가 단위를 쓰거나 계속 수정해야합니다. 더 좋은 방법은 입력 및 평가 날짜를 쉽게 조정할 수있는 별도의 구성에서 파생시키는 것입니다.

예를 들어 여기 테스트 스텁을 올바르게 수행하는 방법이 있습니다.

[TestMethod]
public void GetPath_Tested(int CaseId)
{
    testParams = GetTestConfig(caseID,"testConfig.txt"); // some wrapper that does read line and chops the field. 
    MyClass target = new MyClass(testParams.field1, testParams.field2);
    string expected = testParams.field5;
    string actual = target.GetPath();
    Assert.AreEqual(expected, actual,
        "GetPath should return a full directory path based on its fields.");
}

0

가능한 많은 개념이 있으며, 차이점을보기 위해 몇 가지 예를 만들었습니다.

[TestMethod]
public void GetPath_Softcoded()
{
    //Hardcoded since you want to see what you expect is most simple and clear
    string expected = "C:\\Output Folder\\fields\\that later\\determine\\a folder";

    //If this test should also use a mocked filesystem it might be that you want to use
    //some base directory, which you could set in the setUp of your test class
    //that is usefull if you you need to run the same test on different environments
    string expected = this.outputPath + "fields\\that later\\determine\\a folder";


    //another readable way could be interesting if you have difficult variables needed to test
    string fields = "fields";
    string thatLater = "that later";
    string determine = "determine";
    string aFolder = "a folder";
    string expected = this.outputPath + fields + "\\" + thatLater + "\\" + determine + "\\" + aFolder;
    MyClass target = new MyClass(fields, thatLater, determine, aFolder);

    //in general testing with real words is not needed, so code could be shorter on that
    //for testing difficult folder names you write a separate test anyway
    string f1 = "f1";
    string f2 = "f2";
    string f3 = "f3";
    string f4 = "f4";
    string expected = this.outputPath + f1 + "\\" + f2 + "\\" + f3 + "\\" + f4;
    MyClass target = new MyClass(f1, f2, f3, f4);

    //so here we start to see a structure, it looks more like an array of fields
    //so what would make testing more interesting with lots of variables is the use of a data provider
    //the data provider will re-use your test with many different kinds of inputs. That will reduce the amount of duplication of code for testing
    //http://msdn.microsoft.com/en-us/library/ms182527.aspx


    The part where you compare already seems correct
    MyClass target = new MyClass(fields, thatLater, determine, aFolder);

    string actual = target.GetPath();
    Assert.AreEqual(expected, actual,
        "GetPath should return a full directory path based on its fields.");
}

요약 : 일반적으로 첫 번째 하드 코딩 된 테스트는 간단하고 포인트 등으로 간단하기 때문에 나에게 가장 의미가 있습니다. 경로를 너무 여러 번 시작하면 설정 방법에 넣으십시오.

향후의 구조적 테스트를 위해 데이터 소스를 확인하여 테스트 상황이 더 필요한 경우 더 많은 데이터 행을 추가 할 수 있습니다.


0

최신 테스트 프레임 워크를 사용하면 메소드에 매개 변수를 제공 할 수 있습니다. 나는 그것들을 활용할 것입니다 :

[TestCase("fields", "that later", "determine", "a folder", @"C:\Output Folder\fields\that later\determine\a folder")]
public void GetPathShouldReturnFullDirectoryPathBasedOnItsFields(
    string field1, string field2, string field3, string field,
    string expected)
{
    MyClass target = new MyClass(field1, field2, field3, field4);
    string actual = target.GetPath();
    Assert.AreEqual(expected, actual,
        "GetPath should return a full directory path based on its fields.");
}

내 견해로는 이것에 몇 가지 장점이 있습니다.

  1. 개발자들은 종종 단순한 코드 부분을 SUT에서 단위 테스트로 복사하려고합니다. Winston이 지적했듯이 , 여전히 까다로운 버그가 숨겨져있을 수 있습니다. 예상 결과를 "하드 코딩"하면 원래 코드가 잘못된 것과 같은 이유로 테스트 코드가 잘못된 상황을 피할 수 있습니다. 그러나 요구 사항이 변경되면 수십 가지 테스트 방법에 포함 된 하드 코딩 된 문자열을 추적해야한다면 성 가실 수 있습니다. 테스트 로직 외부에서 모든 하드 코딩 된 값을 한 곳에두면 두 가지 이점을 모두 누릴 수 있습니다.
  2. 한 줄의 코드로 다른 입력 및 예상 출력에 대한 테스트를 추가 할 수 있습니다. 이렇게하면 테스트 코드를 DRY로 유지하고 쉽게 유지하면서 더 많은 테스트를 작성할 수 있습니다. 테스트를 추가하는 것이 너무 저렴하기 때문에 전혀 새로운 테스트 방법을 작성해야한다고 생각하지 않았던 새로운 테스트 사례에 마음이 열렸습니다. 예를 들어, 입력 중 하나에 점이있는 경우 어떤 동작이 예상됩니까? 백 슬래시? 비어 있다면 어떨까요? 아니면 공백? 아니면 공백으로 시작하거나 끝났습니까?
  3. 테스트 프레임 워크는 각 TestCase를 자체 테스트로 취급하며 제공된 입력 및 출력을 테스트 이름에 넣습니다. 모든 TestCase가 하나만 통과하면 어느 쪽이 고장 났으며 다른 쪽과 어떻게 다른지 쉽게 알 수 있습니다.
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.