본문 바로가기

.Net Technology/.NET TDD

(6) NUnit 시작하기 - NUnit의 애트리뷰트들

NUnit에서 지원하는 애트리뷰트들에 대해서 먼저 살펴보도록 하자. 
 
TestFixture
 
바로 앞의 예제에서는 클래스 위에 TestFixture를 이용해서 테스트를 정의 했다. 이 애트리뷰트는 주로 테스트 메서드들을 가지고 있음을 명시해주기 위해서 사용한다. 다시 말해서, 만약 이 애트리뷰트를 클래스에 정의하게 되면 내부적인 테스트 애플리케이션이 내부적인 테스트 메서드들이 존재하는지 스캔하게 된다. 아래 코드는 이 테스트 메서드를 사용하는 방법을 보여준다.
 
using System;
using NUnit.Framework;

namespace UnitTestingExamples
{
    [TestFixture]
    public class SomeTests
    {
    }
}
 
 
Test
 
Test 애트리뷰트는 TestFixture 안에 존재하는 메서드들에서 사용한다. 바로 테스트를 진행해야 되는 메서드라는 것을 알려주는 역할을 하게 된다. 단 여기서 중요한 부분이 이 테스트 메서드는 반드시 public 으로 작성되어야 하고 어떤 파라메터를 받아서는 안 된다. 또한 반환 타입은 항상 void로 작성되어야 한다. 다음 소스코드는 Test 메서드의 예제를 보여주고 있다.
 
namespace UnitTestingExamples
{
    using System;
    using NUnit.Framework;

    [TestFixture]
    public class SomeTests
    {
        [Test]
        public void TestOne()
        {
        }
    }
}
 
 
TestFixtureSetUp 과 TestFixtureTearDown
 
이 애트리뷰트들은 TestFixture로 선언된 클래스 안의 메서드들에 추가할 수 있는 기능인데, TestFixtureSetUp은 테스트를 진행하기 전에 먼저 호출되어 테스트 하는데 있어서 초기화 하거나 정의해야 값을 지정하기 위해서 사용된다. 그리고 TestFixtureTearDown의 경우 모든 테스트들이 마친 뒤에 마지막으로 호출된다. 예를 들어 만약 테스트 뒤에 어떤 일을 수행해야 하거나 테스트 전에 반드시 준비해야 하는 공통 작업이 있다면 이 애트리뷰트들이 유용하게 사용될 것이다.
 
namespace NUnit.Tests
{
    using System;
    using NUnit.Framework;

    [TestFixture]
    public class SuccessTests
    {
        [TestFixtureSetUp]
        public void Init()
        {  }

        [TestFixtureTearDown]
        public void Cleanup()
        {  }

        [Test]
        public void Add1()
        {  }

        [Test]
        public void Add2()
        {  }
    }
}
 
위의 테스트를 실행하게 될 경우에 메서드가 호출되는 순서는 Init()->Add1->Add2->CleanUp 이 될 것이고 실제 테스트는 Add1 메서드와 Add2메서드만 진행하고 결과를 보여주게 된다.
 
 
ExpectedException
 
이 메서드는 테스트를 실행했을 때 이 애트리뷰트에서 정의한 예외가 만들어졌는가를 테스트 하게 된다. 물론 try catch를 이용해서 예외를 잡은 뒤에 실제로 예상한 예외가 만들어졌는지 직접 비교할 수도 있지만 그럴 경우에 테스트 코드가 try catch로 묶이면서 조금 복잡해 질 수 있다. 예제 코드는 아래와 같다. 
 
namespace NUnit.Tests
{
    using System;
    using NUnit.Framework;

    [TestFixture]
    public class SuccessTests
    {
        [Test]
        [ExpectedException(typeof(InvalidOperationException))]
        public void ExpectAnExceptionByType()
        { 
            throw new InvalidOperationException(); 
        }

        [Test]
        [ExpectedException("System.InvalidOperationException")]
        public void ExpectAnExceptionByName()
        { 
        }
    }
}
 
먼저 코드에서 원하는 예외 타입을 typeof 키워드를 이용해서 지정하는 방법이 있으며 아니면 직접 어셈블리 이름을 지정하는 것도 가능하다. 위의 테스트 코드를 살펴보면 첫번째 테스트에서는 throw를 통하여 강제적으로 예외상황을 만들어 발생시켰다. 하지만 아래 메서드에서는 예외를 발생시키지 않았다. 테스트 결과는 예상했던 대로 예외를 발생시킨 하나의 테스트만 통과되는 것을 볼 수 있다.
 
[그림12] ExpectException의 테스트 결과
 
 
Ignore
 
이 애트리뷰트는 실제 테스트로 정의 되어 있어도 테스트를 진행하지 않고 무시하겠다라는 의미로 사용한다. 물론, 실제 [Test] 애트리뷰트를 지운 뒤에 주석으로 달아도 되지만 그럴 경우 테스트 목록에서 없어져 버리고 만다. 하지만 Ignore를 이용할 경우에 테스트 리스트에서 빨간색도 초록색도 아닌 노란색 메시지를 표시하면서 왜 포함이 되지 않았는지에 대한 이유를 작성할 수 있다. 예제 코드를 살펴보자. 
 
namespace NUnit.Tests
{
    using System;
    using NUnit.Framework;

    [TestFixture]
    public class SuccessTests
    {
        [Test]
        public void ExpectAnExceptionByType()
        {
        }

        [Ignore("이번 버전에서는 제외된 기능")]
        [Test]
        public void ExpectAnExceptionByName()
        { 
        }
    }
}
 
이렇게 메시지를 정의하게 될 경우 아래와 같은 테스트 결과와 위에서 정의한 메시지 또한 확인할 수 있다.
 
[그림13] Ignore 메세지
 
 
TestCase
 
이 애트리뷰트는 가장 자주 사용하는 기능이다. 여러 가지 테스트 케이스를 만들어 내고 싶다면 이 기능을 사용해도 된다. 다시 말해서 같은 테스트를 다른 값들을 가지고 반복해서 진행하고 싶다면 두세 번 테스트 메서드를 적용하지 않고도 단순히 값만 할당하는 것으로 테스트를 진행할 수 있다. 예를 들어 1장에서 아래와 같이 계속 반복되는 테스트를 작성했었다.
 
[TestClass]
public class MultipleCalculatorTest
{
    [TestMethod]
    public void WhenValueDivisibleOnlyBy3()
    {
        var mCal = new MultipleCalculator();
        string valueExpected = "Joel";
        string result = mCal.Calculate(3);
        Assert.AreEqual(valueExpected, result);
    }

    … 생략 …

    [TestMethod]
    public void WhenValueDivisionBy3and5()
    {
        var mCal = new MultipleCalculator();
        string valueExpected = "Joel Sarah";
        string result = mCal.Calculate(15);
        Assert.AreEqual(valueExpected, result);
    }
    [TestMethod]
    public void WhenValueIsNotDivisibleBy3Or5()
    {
        var mCal = new MultipleCalculator();
        string valueExpected = "4";
        string result = mCal.Calculate(4);
        Assert.AreEqual(valueExpected, result);
    }
}
 
위의 코드에서는 valueExpected 라는 우리가 테스트에서 예상하고 있는 문자열 값과 mCal.Calculate에 할당하게 되는 숫자 값만 다를 뿐 안에서 진행하는 내용은 모두 같다. 위의 내용을 TestCase를 이용해서 아주 간단하게 작성할 수 있다.
 
namespace NUnit.Tests
{
    using System;
    using NUnit.Framework;
    using HOONS.TDD.Chapter1.SampleExample;

    [TestFixture]
    public class TestCase 
    {
        [TestCase("Joel", 3)]
        [TestCase("Sarah", 5)]
        [TestCase("Noah", 7)]
        [TestCase("4", 4)]
        public void Chapter1_TestAgain(string valueExpected, int number)
        {
            var mCal = new MultipleCalculator();
            string result = mCal.Calculate(number);
            Assert.AreEqual(valueExpected, result);
        }
    }
}
 
이렇게 TestCase를 작성해줌으로써 우리는 아래와 같이 테스트 케이스별 결과를 확인할 수 있다. 
 
[그림14] TestCase 예제
 
 
Timeout
 
이 애트리뷰트는 사용이 간단하다. 만약 테스트가 지정한 밀리세컨드의 시간 안에 수행되지 않을 경우에 에러메세지를 반환하는 것이다. 즉, 성능에 민감한 문제가 있을 경우에 간단하게 정의하여 사용할 수 있다. 
 
namespace NUnit.Tests
{
    using System;
    using NUnit.Framework;

    [TestFixture]
    public class Timeout 
    {
        [Test]
        [Timeout(1)]
        public void ShouldFinishInOneMS()
        {    }
    }
}
 
위의 테스트에서는 1밀리 세컨드를 셋팅 했다. 아무리 메서드 안의 실행 값이 없다고 한들 이 시간 안에 끝나는 것이 무리인지 실패 메시지를 반환하게 된다. 
 
[그림15] Timeout 애트리뷰트
 
위의 값을 10으로 바꿀 경우 해당 PC에 따라 다르겠지만 필자의 PC에서는 7 밀리초를 반환하면서 테스트가 통과하는 것을 볼 수 있었다.
 
이렇게 해서 대부분의 중요한 NUnit의 애트리뷰트들을 살펴보았다. 여기서 소개된 정도만 사용할 수 있으면 충분히 효율적인 테스트 코드를 작성하는 것이 가능할 것이다.