TDD : NUnit 2.5 New Attributes CSharp

NUnit 2.5 에서 새로 등장한 Attribute 에 대해 알아보겠습니다.

CombinatorialAttribute
테스트 함수의 인자들의 조합이 Combination 조합으로 테스트 데이타를 생성해서, 테스트를 수행하도록 합니다.

MaxtimeAttribute 
테스트 수행 시간의 최대값을 지정하여, 테스트 실패여부를 판단합니다. 단 지정된 최대 시간(msec)이 초과되었다고, 테스트를 중단하는 것이 아니라, 테스트 수행은 완료하지만 결과만 Fail로 나타나게 합니다.

PairwiseAttribute 
Parameterized test 에서 설명했듯이, 테스트 함수의 인자들의 조합을 쌍으로 이루어지도록 한다는 뜻입니다.

RangeAttribute, RandomAttribute, ValuesAttribute
Parameterized test시 인자의 값을 정하는 방식입니다. Range는 범위, Random은 무작위, Values 는 특정 값의 array를 지정할 수 있도록 한 것입니다. 테스트 함수의 인자에 대한 attribute이므로, 각각의 인자별로 다른 attribute주어서 작업할 수 있습니다.

RepeatAttribute
반복해서 테스트를 수행하도록 합니다. 단 중간에 실패하면, 더 이상 수행하지 않습니다.

RequiredAddinAttribute
테스트를 위해 특정 assembly가 로딩되어야 함을 명시하는 것입니다. 만약 테스트 시 필요한 어셈블리가 로딩되지 않는다면, 테스트를 수행을 건너뛰고, non-runnable로 표시합니다.

RequiresMTAAttribute, RequiresSTA
테스트 class나 메소드를 테스트 수행 시에 Multi-Threaded apartment / Single-Threaded apartment 환경에서 수행되어야 함을 나타냅니다. 
// An MTA thread will be created and used to run all the tests in the assembly
[assembly:RequiresMTA]
...
// TestFixture 가 독립된 Thread를 필요로합니다.
[TestFixture, RequiresMTA]
public class FixtureRequiresMTA
{
// 새로운 MTA thread가 생성되고, 이 클래스의 모든 메소드가 새로 생성된 thread에서 수행됩니다.
        // 단 메소드에 RequiresMTA가 없다면 말이죠
}

[TestFixture, RequiresMTA]
public class AnotherFixture
{
    [Test, RequiresMTA]
    public void TestRequiresMTA()
    {
          // 이 테스트 함수는 class가 MTA 환경에서 수행되고 있지 않다면, 새로운 thread를 생성합니다.
    }
}

RequiresThread
테스트 메소드가 새로운 독립된 thread에서 수행되어야 함을 나타냅니다.
속성으로 STA, MTA를 지정할 수 있습니다.
이 Attribute와 RepeatAttribute를 사용하면 MbUnit의 ThreadedRepeatAttribute와 같은 기능을 수행합니다.

TestCaseAttribute
MbUnit의 RowTest와 마찮가지로, Parameterized Test를 수행할 때, 인자 값을 지정하여 테스트가 가능합니다.
MbUnit보다 좋은 점은 Result라는 값을 가지고 assertion을 수행할 수 있다는 점입니다.

TheoryAttribute (이론)
일반적인 테스트는 예제 중심으로 작성되고 테스트되어집니다. 그래서 테스트 코드만 보고도, 어떤 API에 대한 사용방법을 익힐 수가 있지요. Theory는 특수한 형태의 테스트 기법인데, 개발 시스템의 일반적인 가설(Theory)을 증명하기 위해 사용합니다.
Parameterized Test와 유사하긴 하지만, Theory는 모든 인자 값에 대해 만족하는 일반적인 가설을 만들고, 그 가설을 증명하는 방식입니다. 
Theory 에 대한 증명을 위해서는 많은 데이타가 필요한데, Parameterized Test와 유사하지만, 다른 방식을 사용합니다. DataPoint, DataPoints 라는 attribute를 사용합니다. Theory가 적용된 테스트 함수에 DataPoint 로 지정된 Data들을 조합하여, 제공하고, 그 함수를 테스트하여, 가설(Theory)을 증명하는 방식입니다. 

우선 Theory에 대한 전제조건은 Assume.That(...) 으로 정의하고, 가설의 증명은 Assert.That(...)으로 수행합니다.
Assume.That(...)은 Assert 와는 달리 전제 조건에 위배되는 조건이라면, 예외를 발생시켜 테스트가 실패했다고 표시하는 것이 아니라, 전제조건이 만족하지 못하므로, 이론 증명을 진행할 수 없음을 나타내고, 끝냅니다.

가설 증명의 모든 테스트 케이스에 대해 다음과 같은 결과를 도출합니다.
 모든 시험종류에 대해 전제조건을 만족하지 못한다면, 가설의 증명은 실패했다로 규정합니다.
 하나의 Assertion이 실패했더라도, Theory 자체가 실패했다고 간주한다.
적어도 하나 이상의 시험이 전제조건을 만족하고, 검증에 성공했다면, 가설은 증명되었다고 간주한다.
다음은 Theory 테스트 방식에 대한 예입니다.

가설(Theory) :
"음수가 아닌 수에 대해, 그 수의 제곱근은 항상 음수가 아니고, 그 제곱근의 제곱은 그 수와 같다."
위의 이론을 증명하는 테스트 함수를 작성하면 다음과 같습니다.
public class SqrtTestFixture
{
[Datapoint]
public double zero = 0;
[Datapoint]
public double positive = 1;
[Datapoint]
public double negative = -1;
[Datapoint]
public double max = double.MaxValue;
[Datapoint]
public double infinity = double.PositiveInfinity;

[Theory]
public void SquareRootDefinition(double num)
{
Assume.That(num >=0.0 && num < double.MaxValue);  // 여기서 negative, infinity는 걸러진다.

double sqrt = Math.Sqrt(num);
Assert.That( sqrt >= 0.0);
Assert.That( sqrt * sqrt, Is.EqualTo(num).Within(1E-5));
}

Theory는 저도 어디에 적용할까 고민중입니다.

아쉬운 점

보통의 테스트 클래스에서, 테스트 메소드 들을 작성할 때, 실행 순서에 상관없이 테스트가 수행되도록 작성해야 합니다.
만약 그렇지 못할 때에는 명확히 순서를 지정해주거나, MbUnit처럼 메소드 위치에 따라 수행되도록 해야 합니다. 근데 NUnit은 그런게 없습니다. 결국 실행순서에 상관없이 테스트 가능하도록 작성하는 방법을 택할 수 밖에 없습니다.

덧글

  • 브루스팍 2010/03/09 10:16 # 삭제

    안녕하세요.. 글 잘보고 있습니다.
    궁금한게 있는데요..
    RequiresMTAAttribute, RequiresSTA
    에서
    RequireMTA만 지정해 준다고 저절로 멀티 쓰레드로 테스트가 수행되는 건 아닌건가요?
    지정해 주고 나서 별도로 쓰레드를 생성시켜서 돌려주어야 하는 건가요?
  • debop 2010/03/15 10:25 #

    게을러서 답변이 늦었습니다. 네 예상했던 대로 그렇게 된다는 얘기입니다. 제가 확인해본 바로 STA로만 돌려야 하는 특정 환경에 대해, 아무런 Attribute를 주지 않았을 때와 RequiresSTA 를 주었을 때, STA 환경에서만 제대로 테스트가 되더군요.
  • 브루스팍 2010/07/29 23:35 # 삭제

    네.. 답변 감사합니다. (조금 늦었죠..;;)
  • debop 2010/07/30 11:13 #

    별말씀을... 이렇게 질문 주시면 저도 좋습니다.
※ 로그인 사용자만 덧글을 남길 수 있습니다.