64비트로 빌드하면 유닛테스트 플랫폼을 x64로 맞추어라고 에러가 나올 수가 있는데 

이런 경우 아래처럼 하면 된다.



저작자 표시
신고
by 흥배 2014.10.15 08:00

출처: http://www.naturalsoftware.jp/blog/4271 http://www.naturalsoftware.jp/blog/4611

번역: 최흥배

 

Visual Studio 2012에서는 아래의 3개의 유닛테스트 프로젝트가 추가되었다.

  • C++/CLI(.NET) 유닛테스트 프로젝트
  • C++/CX(Windows 스토어 ) 유닛테스트 프로젝트
  • C++(Native) 유닛테스트 프로젝트


여기서는 C++ 유닛테스트를 소개한다.

1. 테스트 대상 프로젝트를 만든다. 여기서는「Win32 콘솔 애플리케이션」으로「NativeLibrary」라는 프로젝트 이름으로 한다.

2. 애플리케이션 종류를 '스테틱 라이브러리' 하고 완료 한다.

 

3. 다음으로 같은 솔류션에 '기본 유닛테스트 프로젝트' 만든다.

4. 솔루션에 테스트 대상 라이브러리 NativeLibrary」와 테스트 프로젝트 NativeLibraryTest2개가 만들어졌다.

 

5. 상태에서 한번 빌드 해본다. 테스트 탐색기에 ' 실행 테스트' 추가되었다. 테스트 탐색기가 표시되지 않은 경우는 메뉴의 '테스트 | 윈도우 | 테스트 탐색기' 표시한다.

6. 이번에는 '테스트 탐색기' 왼쪽 위에 있는 버튼을 눌러서 '리빌드' 해본다. 빌드 종료와 같이 테스트가 실행된다.

7. 테스트 이름을 바꾸어본다. 테스트 이름은 한국어를 사용해본다. 빌드하면 테스트 탐색기쪽에 메소드 이름이 바뀐다.

8. 실제 테스트 대상으로 하는 클래스를 만들어본다. NativeLibrary 프로젝트의 '추가' 클래스를 추가한다.

 

9. Calc 이라는 이름을 붙인 클래스를 만든다.

 

10. 더하기 계산 멤버 함수를 만든다. 먼저 구현은 하지 않는다.

 

11. NativeLibraryTest 프로젝트 속성을 열고, C++ 「추가 포함 디렉토리」에 $(SolutionDir」을 추가한다.

12. 계속해서 「공통 속석」의 「새로운 참조 추가」를 누른다.

 

13. 테스트 대상 NativeLibrary 프로젝트가 표시 되므로 체크를 넣어서 OK」를 누른다.

14. 참조에 NativeLibrary」가 추가된 것을 확인한다.

 

15. 이것으로 테스트 준비가 끝났으므로 NativeLibraryTest 프로젝트에 테스트 코드를 쓴다. 테스트 대상을 구현하지 않았으므로 테스트는 실패한다.

16. 테스트 대상을 구현한다. 테스트는 성공.

 

이것으로 네이티브 C++ 에서 단위 테스트는 끝이다. C# 같이 메소드나 클래스의 동적 생성, 리팩토링과 같은 기능은 없으므로 조금 시간이 걸린다.

 

 

 

 

 

 

 

 

Visual Studio 2012 네이티브 C++ 유닛테스트에서 아래와 비슷한 클래스간의 테스트를 만든 경우 Asert::AreEqual 에서 다음과 같은 에러가 나온다.

에러 1 error C2676: 이항연산자 '==' : 'const Person' 、이 연산자 또는 정의된 연산자에 적절한 형으로의 변환을 정의하지 않았다.

이것은 Asert::AreEqual == 연산자를 사용하고 있지 않기 때문으로 Person == 연산자를 구현한다.

그러면 이번에는 다른 에러가 나온다.

에러 1 error C2338: Test writer must define specialization of ToString<const Q& q> for your class class std::basic_string<wchar_t,struct std::char_traits<wchar_t>,class std::allocator<wchar_t> > __cdecl Microsoft::VisualStudio::CppUnitTestFramework::ToString<class Person>(const class Person &).

모르므로 에러를 더블클릭 해보면 다음의 파일로 이동한다.

에러를 출력하기 위한 함수 템플릿 ToString」이 필요해서 기본적인 형에 대해서는 특별화 되어 있지만 유저 정의현 클래스 등은 특별화 되어 있지 않으므로 static_assert 호출되어 ToString」가 호출되고 있는 같다.

이런 이유로 ToString」을 구현하면 좋을 같다. ToString」는 이름 공간「namespace Microsoft{ namespace VisualStudio {namespace CppUnitTestFramework {」에 구현되어 있으므로 이것에 맞춘다. (Person std::string std::wstring 변환하였다).

 

이것으로 테스트를 실행할 있고 Assert 실패한 경우네는 에러 내용이 표시되도록 되었다.

 

신고
by 흥배 2014.09.15 08:00

출처:

http://blog.naver.com/empty_wagon/20151928869

http://blog.naver.com/empty_wagon/20151986599

 

 

사용하는가?

  • Mock 라이브러리는 단위 테스트를 쉽게 하기 위해 존재합니다.
  • 예를 들어서, 클래스 테스트에 데이터베이스를 활용해야 하는 경우가 있다고 가정을 합시다. 경우 직접 데이터베이스에 정보를 넣었다 뺐다 하면 상당히 부담이 됩니다.
  • 이런 부담을 주지 않고, 데이터베이스의 행동을 흉내냄으로써 테스트를 더욱 용이하게 합니다.
  • 하지만 이런 용이함을 얻기 위해서는 인터페이스들에 의해서만 프로그램을 조작하는 설계를 해야 하기는 합니다. (이런 설계가 처음에는 많이 어색하지만, 나중에는 이런 설계가 나은 설계임을 깨닫게 됩니다.)
  • 간단히 이야기해서 Mock 라이브러리는 복잡하고 시간이 걸리는 테스트의 시간을 줄여주기 위해서 존재한다고 있습니다.

 

설치

 

예제

 

public class Book

{

    public string Name { get; set; }

    public int Price { get; set; }

    public string ISBN { get; set; }

}

   

public interface IBookRepository

{

    List<Book> GetBooks();

}

   

public class PriceProcessor

{

    public PriceProcessor(IBookRepository bookRepository)

    {

        bookRepo = bookRepository;

    }

    

    public int GetTotalPrice()

    {

        int result = 0;

               

        foreach (Book b in bookRepo.GetBooks())

        {

            result += b.Price;

        }

    

        return result;

    }

    

    private IBookRepository bookRepo;

}

   

   

[TestFixture]

public class PriceProcessorTests

{

    [Test]

    public void GetTotalPrice_NoDiscount_ReturnCorrectPriceSum()

    {

        List<Book> bookList = new List<Book>()

        {

            new Book() {ISBN="1234", Price=10000, Name="C언어 입문"},

            new Book() {ISBN="1235", Price=20000, Name="C++ 입문"},

            new Book() {ISBN="1236", Price=30000, Name="ASP.NET MVC3 완성"},

        };

    

        Mock<IBookRepository> bookRepoMock = new Mock<IBookRepository>();

        bookRepoMock.Setup(b => b.GetBooks()).Returns(bookList);

    

        PriceProcessor priceProcessor = new PriceProcessor(bookRepoMock.Object);

    

        Assert.That(priceProcessor.GetTotalPrice(), Is.EqualTo(60000));

    }

}

  • bookRepoMock.Setup(b => b.GetBooks()).Returns(bookList);

  • GetBooks함수가 불리면, bookList 반환하라고 명령을 내리고 있습니다. 그리고 완성이 bookRepoMock Object 속성을 이용해서 priceProcessor 넘겨 주고 있습니다

 

함수가 불려졌는지 확인

 

 

public class BestSellers

{

    public BestSellers(IBookRepository bookRepository)

    {

        bookRepo = bookRepository;

    }

    

    public Result AddBook(Book book)

    {

        if (book.Name.Contains("<") || book.Name.Contains(">"))

        {

            return Result.XssAlert;

        }

               

        bookRepo.AddBook(book);

        return Result.Success;

    }

    

    IBookRepository bookRepo;

}

   

public interface IBookRepository

{

    List<Book> GetBooks();

    bool AddBook(Book book);

}

   

   

[Test]

public void AddBook_ContainsTag_IBookRepositoryAddBookIsNotCalled()

{

    Book b = new Book() { Name="<iframe></iframe>"};

    

    Mock<IBookRepository> mock = new Mock<IBookRepository>();

    mock.Setup(r => r.AddBook(b)).Verifiable();

    

    BestSellers bestSellers = new BestSellers(mock.Object);

    

    bestSellers.AddBook(b);

    

    mock.Verify(r => r.AddBook(b), Times.Never());

}

 

Mock 객체에서 생성된 함수들을 매개 변수에 따라 동작을 조절

 

public class Book

{

    public string Name { get; set; }

    public int Price { get; set; }

    public string ISBN { get; set; }

}

   

public void ReducePrice(float reductionRatio)

{

    foreach (Book b in bookRepo.GetBooks())

    {

        b.Price = (int)(reductionRatio * b.Price);

        bookRepo.UpdateBook(b);

    }

}

   

   

[Test]

public void ReducePrice_ValueBetween0And1_IBookRepositoryUpdateBookCalled()

{

    List<Book> bookList = new List<Book>()

    {

        new Book() {ISBN="1234", Price=10000, Name="C언어 입문"},

        new Book() {ISBN="1235", Price=20000, Name="C++ 입문"},

        new Book() {ISBN="1236", Price=30000, Name="ASP.NET MVC3 완성"},

    };

    

    Mock<IBookRepository> bookRepoMock = new Mock<IBookRepository>();

    bookRepoMock.Setup(b => b.GetBooks()).Returns(bookList);

    

    PriceProcessor priceProcessor = new PriceProcessor(bookRepoMock.Object);

    

    priceProcessor.ReducePrice(0.1f);

    

    bookRepoMock.Verify(r => r.UpdateBook(It.IsAny<Book>()),

Times.Exactly(bookList.Count));

}

 

Returs, Callback, Throws

 

public Result AddBook(Book book)

{

    if (book.Name.Contains("<") || book.Name.Contains(">"))

    {

        return Result.XssAlert;

    }

               

    bool added = bookRepo.AddBook(book);

    if (added == false)

    {

        return Result.AlreadyExist;

    }

    return Result.Success;

}

   

   

[Test]

public void AddBook_AddSameBookAgain_ReturnsAlreadyExist()

{

    Book b = new Book() { ISBN = "1234", Name = "abc" };

    

    int count = 0;

    Mock<IBookRepository> mock = new Mock<IBookRepository>();

    mock.Setup(r => r.AddBook(b)).Callback(() => count++).Returns(() => count <= 1);

    

    BestSellers bestSellers = new BestSellers(mock.Object);

    

    Result result1 = bestSellers.AddBook(b);

    Result result2 = bestSellers.AddBook(b);

    

    Assert.That(result1, Is.EqualTo(Result.Success));

    Assert.That(result2, Is.EqualTo(Result.AlreadyExist));

}

 

참고

  • Moq活用して.NETでモックを使ったテストを

 

 

 

 

신고
by 흥배 2014.01.08 08:00

유닛테스트 준비

  • 보통 프로그램이나 라이브러리 프로젝트에 추가 프로젝트로 유닛테스트 프레임워크를 사용하는 프로젝트를 만든다.
    • 이렇게 하면 유닛테스트에 영향을 받지 않으면서 기존처럼 개발을 있다.

  • 유닛테스트 프로젝트에 테스트 메소드를 만들면 테스트 탐색기에 리스트로 나온다.

 

유닛테스트 초기화와 실행 순서

 

지정한 유닛테스트만 실행

  • 유닛테스트 리스트에서 특정 테스트만 실행하고 싶은 경우 항목만 선택 테스트를 실행한다

 

카테고리

  • 유닛테스트를 비슷한 항목끼리 카테고리로 묶어서 카테고리 별로 테스트 있다.

 

유닛테스트 예제

 

 

[TestCategory("ScoreCalculator")]

[TestMethod]

public void Test주고_받는_점수_계산하기()

{

    PrivateType TestObject = new PrivateType(typeof(YakuLibNET.ScoreCalculator));

   

    bool Is부모가_이긴 = true;

    int 이긴_플레이어_점수 = 0;

    object Result;

    YakuLibNET.KUK_END_SCORE ResultScore;

   

   

    // [테스트-부모가 이긴 경우]

    Is부모가_이긴 = true;

    이긴_플레이어_점수 = 3900;

   

    Result = TestObject.InvokeStatic("주고_받는_점수_계산하기", Is부모가_이긴, 이긴_플레이어_점수);

    ResultScore = (YakuLibNET.KUK_END_SCORE)Result;

    Assert.AreEqual(3900, ResultScore.이긴_플레이어가_받는_점수);

    Assert.AreEqual(1300, ResultScore.부모윈_진_자식이_내는_점수);

   

   

    Is부모가_이긴 = true;

    이긴_플레이어_점수 = 2000;

   

    Result = TestObject.InvokeStatic("주고_받는_점수_계산하기", Is부모가_이긴, 이긴_플레이어_점수);

    ResultScore = (YakuLibNET.KUK_END_SCORE)Result;

    Assert.AreEqual(이긴_플레이어_점수, ResultScore.이긴_플레이어가_받는_점수);

    Assert.AreEqual(700, ResultScore.부모윈_진_자식이_내는_점수);

   

   

    Is부모가_이긴 = true;

    이긴_플레이어_점수 = 7700;

   

    Result = TestObject.InvokeStatic("주고_받는_점수_계산하기", Is부모가_이긴, 이긴_플레이어_점수);

    ResultScore = (YakuLibNET.KUK_END_SCORE)Result;

    Assert.AreEqual(이긴_플레이어_점수, ResultScore.이긴_플레이어가_받는_점수);

    Assert.AreEqual(2600, ResultScore.부모윈_진_자식이_내는_점수);

   

   

    Is부모가_이긴 = true;

    이긴_플레이어_점수 = 11600;

   

    Result = TestObject.InvokeStatic("주고_받는_점수_계산하기", Is부모가_이긴, 이긴_플레이어_점수);

    ResultScore = (YakuLibNET.KUK_END_SCORE)Result;

    Assert.AreEqual(이긴_플레이어_점수, ResultScore.이긴_플레이어가_받는_점수);

    Assert.AreEqual(3900, ResultScore.부모윈_진_자식이_내는_점수);

   

   

    Is부모가_이긴 = true;

    이긴_플레이어_점수 = 10600;

   

    Result = TestObject.InvokeStatic("주고_받는_점수_계산하기", Is부모가_이긴, 이긴_플레이어_점수);

    ResultScore = (YakuLibNET.KUK_END_SCORE)Result;

    Assert.AreEqual(이긴_플레이어_점수, ResultScore.이긴_플레이어가_받는_점수);

    Assert.AreEqual(3600, ResultScore.부모윈_진_자식이_내는_점수);

   

   

    // [자신이 이긴 경우]

    Is부모가_이긴 = false;

    이긴_플레이어_점수 = 1300;

   

    Result = TestObject.InvokeStatic("주고_받는_점수_계산하기", Is부모가_이긴, 이긴_플레이어_점수);

    ResultScore = (YakuLibNET.KUK_END_SCORE)Result;

    Assert.AreEqual(이긴_플레이어_점수, ResultScore.이긴_플레이어가_받는_점수);

    Assert.AreEqual(700, ResultScore.자식윈_진_부모가_내는_점수);

    Assert.AreEqual(400, ResultScore.자식윈_진_자식이_내는_점수);

   

   

    Is부모가_이긴 = false;

    이긴_플레이어_점수 = 5200;

   

    Result = TestObject.InvokeStatic("주고_받는_점수_계산하기", Is부모가_이긴, 이긴_플레이어_점수);

    ResultScore = (YakuLibNET.KUK_END_SCORE)Result;

    Assert.AreEqual(이긴_플레이어_점수, ResultScore.이긴_플레이어가_받는_점수);

    Assert.AreEqual(2600, ResultScore.자식윈_진_부모가_내는_점수);

    Assert.AreEqual(1300, ResultScore.자식윈_진_자식이_내는_점수);

   

   

    Is부모가_이긴 = false;

    이긴_플레이어_점수 = 7100;

   

    Result = TestObject.InvokeStatic("주고_받는_점수_계산하기", Is부모가_이긴, 이긴_플레이어_점수);

    ResultScore = (YakuLibNET.KUK_END_SCORE)Result;

    Assert.AreEqual(이긴_플레이어_점수, ResultScore.이긴_플레이어가_받는_점수);

    Assert.AreEqual(3600, ResultScore.자식윈_진_부모가_내는_점수);

    Assert.AreEqual(1800, ResultScore.자식윈_진_자식이_내는_점수);

}

 

테스트 함수 리스트

 

public 아닌 메소드 테스트 하기

 

 

[TestMethod]

public void Test주고_받는_점수_계산하기()

{

        // ScoreCalculator는 private 멤버 함수이다.

    PrivateType TestObject = new PrivateType(typeof(YakuLibNET.ScoreCalculator));

       

    // [테스트-부모가 이긴 경우]

    bool Is부모가_이긴 = true;

    int 이긴_플레이어_점수 = 3900;

   

    object Result = TestObject.InvokeStatic("주고_받는_점수_계산하기", Is부모가_이긴, 이긴_플레이어_점수);

    YakuLibNET.KUK_END_SCORE ResultScore = (YakuLibNET.KUK_END_SCORE)Result;

    Assert.AreEqual(3900, ResultScore.이긴_플레이어가_받는_점수);

    Assert.AreEqual(1300, ResultScore.부모윈_진_자식이_내는_점수);

       

}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

public class PrivateAccess

{

    private static string NameStatic

    {

        get { return typeof(PrivateAccess).Name; }

    }

       

        private string Name { get; set; }

   

    public PrivateAccess(string name)

    {

        this.Name = name;

    }

       

        private int Add(int lhs, int rhs)

    {

        return lhs + rhs;

    }

   

    private static int AddStatic(int lhs, int rhs)

    {

        return lhs + rhs;

    }

    private int Subtract(int lhs, int rhs)

    {

        return lhs - rhs;

    }

}

   

   

[TestMethod()]

public void PrivateAccessConstructorTest()

{

    // Arange

    PrivateObject po = new PrivateObject(typeof(PrivateAccess), new object[] { "こんにちわ" });

    string name = "こんにちわ";

    // Act

    // PrivateAccess target = new PrivateAccess(name);

    // Assert

    Assert.AreEqual(name, po.GetProperty("Name"));  

}

   

public void AddTest()

{

    PrivateObject po = new PrivateObject(typeof(PrivateAccess), new object[] { "こんにちは" });

       

    //PrivateAccess_Accessor target = new PrivateAccess_Accessor(param0);

    int lhs = 2;

    int rhs = 3;

    int expected = 5;

    int actual;

    actual = (int) po.Invoke("Add", new object[]{lhs, rhs});

    Assert.AreEqual(expected, actual);

}

   

public void AddStaticTest()

{

    PrivateType pt = new PrivateType(typeof(PrivateAccess));

    int lhs = 1;

    int rhs = 2;

    int expected = 3;

    int actual;

    actual = (int) pt.InvokeStatic("AddStatic", new object[] { lhs, rhs });

    Assert.AreEqual(expected, actual);

}

   

[TestMethod()]

[DeploymentItem("SampleLibrary.dll")]

public void NameTest()

{

    PrivateObject po = new PrivateObject(new PrivateAccess("hello"));

       

    // PrivateAccess_Accessor target = new PrivateAccess_Accessor(param0);

    string expected = "hello";

    string actual = po.GetProperty("Name") as string;

    Assert.AreEqual(expected, actual);

}

   

[TestMethod()]

public void NameStaticTest()

{

    PrivateType pt = new PrivateType(typeof(PrivateAccess));

    string actual;

    actual = pt.GetStaticProperty("NameStatic") as string;

    Assert.AreEqual("PrivateAccess", actual);

}

  • static 클래스의 private 필드 설정하기

 

 

 

 

 

 

 

 

 

 

 

 

public class UniqueNumberRepository

{

    const int MaxStockCount = 200000;

    const int MinStockCount = 50000;

    static Int64 seqNumber = 0;

    static Int64 lastNumber = 0;

}

   

   

PrivateType TestObject = new PrivateType(typeof(UniqueNumberRepository));

TestObject.SetStaticField("seqNumber", 1);

TestObject.SetStaticField("lastNumber", 10);

 

System.Transactions 사용한 DB 유닛테스트

  • 다중 커밋을 지원.
  • 유닛테스트 코드에서 트랜잭션을 시작하여 제품 코드를 호출하고, 제품 코드측에서 데이터베이스에 변경을 커밋하여도 뒤의 유닛테스트 측에서 롤백하여 데이터베이스에 변경을 남기지 않는다.

 

예외 발생 여부 테스트

// InvalidOperationException 이 발생하면 테스트 성공

[TestMethod()]

[ExpectedException(typeof(InvalidOperationException))]

public void TestMethod() 

{

 // 여기서 InvalidOperationException 예외가 발생하는지 테스트

}





참고


신고
by 흥배 2014.01.07 08:00
| 1 |