Visual C++ DLL 테스트 방법How to test a Visual C++ DLL

이 문서에서는 C++용 Microsoft 테스트 프레임워크를 사용하여 UWP(유니버설 Windows 플랫폼) 앱용 C++ DLL에 대한 단위 테스트를 만드는 한 가지 방법을 설명합니다.This topic describes one way to create unit tests for a C++ DLL for Universal Windows Platform (UWP) apps with the Microsoft Test Framework for C++. RooterLib DLL은 지정된 숫자의 제곱근 예상 값을 계산하는 함수를 구현하여 미적분법의 한계 이론에 대한 희미한 기억을 보여 줍니다.The RooterLib DLL demonstrates vague memories of limit theory from the calculus by implementing a function that calculates an estimate of the square root of a given number. DLL은 UWP 앱에 포함하여 사용자에게 수학으로 할 수 있는 재밌는 것을 보여줄 수 있습니다.The DLL might then be included in a UWP app that shows a user the fun things that can be done with math.

이 항목에서는 개발의 첫 단계로 단위 테스트를 사용하는 방법을 보여 줍니다.This topic shows you how to use unit testing as the first step in development. 이 방법에서는 먼저 테스트하고 있는 시스템에서 특정 동작을 확인하는 테스트 메서드를 작성한 다음 테스트를 통과하는 코드를 작성합니다.In this approach, you first write a test method that verifies a specific behavior in the system that you are testing and then you write the code that passes the test. 다음 절차의 순서를 변경함으로써 이 전략을 반대로 적용하여 먼저 테스트할 코드를 작성한 다음 단위 테스트를 작성할 수 있습니다.By making changes in the order of the following procedures, you can reverse this strategy to first write the code that you want to test and then write the unit tests.

또한 이 항목에서는 단일 Visual Studio 솔루션과 테스트할 DLL 및 단위 테스트에 대한 별도의 프로젝트를 만듭니다.This topic also creates a single Visual Studio solution and separate projects for the unit tests and the DLL that you want to test. DLL 프로젝트에 직접 단위 테스트를 포함하거나 단위 테스트 및 .DLL에 대한 별도의 솔루션을 만들 수도 있습니다.You can also include the unit tests directly in the DLL project, or you can create separate solutions for the unit tests and the .DLL. 사용할 구조에 대한 팁은 기존 C++ 응용 프로그램에 단위 테스트 추가를 참조하세요.See Adding unit tests to existing C++ applications for tips on which structure to use.

솔루션 및 단위 테스트 프로젝트 만들기Create the solution and the unit test project

  1. 파일 메뉴에서 새로 만들기 > 새 프로젝트...를 선택합니다.On the File menu, choose New > New Project....

  2. 새 프로젝트 대화 상자에서 설치됨 > Visual C++를 확장하고 Windows 유니버설을 선택합니다.In the New Project dialog, expand Installed > Visual C++ and choose Windows Universal. 그런 다음 프로젝트 템플릿 목록에서 유닛 테스트 앱(유니버설 Windows)을 선택합니다.Then choose Unit Test App (Universal Windows) from the list of project templates.

  3. 프로젝트 이름을 RooterLibTests로 지정하고 위치를 지정합니다. 솔루션 이름을 RooterLib로 지정하고 솔루션용 디렉터리 만들기가 선택되어 있는지 확인합니다.Name the project RooterLibTests; specify the location; name the solution RooterLib; and make sure Create directory for solution is checked.

    솔루션 및 프로젝트 이름과 위치 지정Specify the solution and project name and location

  4. 새 프로젝트에서 unittest1.cpp를 엽니다.In the new project, open unittest1.cpp.

    unittest1.cppunittest1.cpp

    다음 사항에 유의합니다.Note that:

    • 각 테스트는 TEST_METHOD(YourTestName){...}를 사용하여 정의됩니다.Each test is defined by using TEST_METHOD(YourTestName){...}.

      기존의 함수 시그니처는 작성할 필요가 없습니다.You do not have to write a conventional function signature. 시그니처는 TEST_METHOD 매크로에 의해 생성됩니다.The signature is created by the macro TEST_METHOD. 이 매크로는 void를 반환하는 인스턴스 함수를 생성합니다.The macro generates an instance function that returns void. 또한 테스트 메서드에 대한 정보를 반환하는 정적 함수를 생성합니다.It also generates a static function that returns information about the test method. 이 정보를 통해 테스트 탐색기에서 메서드를 찾을 수 있습니다.This information allows the test explorer to find the method.

    • 테스트 메서드는 TEST_CLASS(YourClassName){...}를 사용해서 클래스로 그룹화됩니다.Test methods are grouped into classes by using TEST_CLASS(YourClassName){...}.

      테스트를 실행하면 각 테스트 클래스의 인스턴스가 생성됩니다.When the tests are run, an instance of each test class is created. 테스트 메서드는 지정되지 않은 순서로 호출됩니다.The test methods are called in an unspecified order. 각 모듈, 클래스 또는 메서드의 전/후에 호출되는 특별한 메서드를 정의할 수 있습니다.You can define special methods that are invoked before and after each module, class, or method. 자세한 내용은 MSDN 라이브러리의 Microsoft.VisualStudio.TestTools.CppUnitTestFramework 사용을 참조하세요.For more information, see Using Microsoft.VisualStudio.TestTools.CppUnitTestFramework in the MSDN Library.

테스트 탐색기에서 테스트가 실행되는지 확인Verify that the tests run in Test Explorer

  1. 일부 테스트 코드를 삽입합니다.Insert some test code:

    TEST_METHOD(TestMethod1)  
    {  
        Assert::AreEqual(1,1);  
    }  
    

    Assert 클래스는 테스트 메서드의 결과를 확인하는 데 사용할 수 있는 몇 가지 정적 메서드를 제공합니다.Notice that the Assert class provides several static methods that you can use to verify results in test methods.

  2. 테스트 메뉴에서 실행을 선택하고 모두 실행을 선택합니다.On the Test menu, choose Run and then choose Run All.

    테스트 프로젝트가 빌드되고 실행됩니다.The test project builds and runs. 테스트 탐색기 창이 나타나고 테스트가 통과한 테스트 아래에 나열됩니다.The Test Explorer window appears, and the test is listed under Passed Tests. 창의 아래쪽에 있는 요약 창은 선택된 테스트에 대한 추가 정보를 제공합니다.The Summary pane at the bottom of the window provides additional details about the selected test.

    테스트 탐색기Test Explorer

솔루션에 DLL 프로젝트 추가Add the DLL project to the solution

  1. 솔루션 탐색기에서 솔루션 이름을 선택합니다.In Solution Explorer, choose the solution name. 바로 가기 메뉴에서 추가를 선택한 다음 새 프로젝트 추가를 선택합니다.From the shortcut menu, choose Add, and then Add New Project.

    RooterLib 프로젝트 만들기Create the RooterLib project

  2. 새 프로젝트 추가 대화 상자에서 DLL(UWP 앱)을 선택합니다.In the Add New Project dialog box, choose DLL (UWP apps).

  3. RooterLib.h 파일에 다음 코드를 추가합니다.Add the following code to the RooterLib.h file:

    // The following ifdef block is the standard way of creating macros which make exporting   
    // from a DLL simpler. All files within this DLL are compiled with the ROOTERLIB_EXPORTS  
    // symbol defined on the command line. This symbol should not be defined on any project  
    // that uses this DLL. This way any other project whose source files include this file see   
    // ROOTERLIB_API functions as being imported from a DLL, whereas this DLL sees symbols  
    // defined with this macro as being exported.  
    #ifdef ROOTERLIB_EXPORTS  
    #define ROOTERLIB_API  __declspec(dllexport)  
    #else  
    #define ROOTERLIB_API __declspec(dllimport)  
    #endif //ROOTERLIB_EXPORTS  
    
    class ROOTERLIB_API CRooterLib {  
    public:  
        CRooterLib(void);  
        double SquareRoot(double v);  
    };  
    

    주석에서는 dll의 개발자뿐만 아니라 해당 프로젝트에서 DLL을 참조하는 모든 사용자에게 ifdef 블록을 설명합니다.The comments explain the ifdef block not only to the developer of the dll, but to anyone who references the DLL in their project. DLL의 프로젝트 속성을 사용하여 명령줄에 ROOTERLIB_EXPORTS 기호를 추가할 수 있습니다.You can add the ROOTERLIB_EXPORTS symbol to the command line by using the project properties of the DLL.

    CRooterLib 클래스는 생성자와 SqareRoot 평가자 메서드를 선언합니다.The CRooterLib class declares a constructor and the SqareRoot estimator method.

  4. 명령줄에 ROOTERLIB_EXPORTS 기호를 추가합니다.Add the ROOTERLIB_EXPORTS symbol to the command line.

    1. 솔루션 탐색기에서 RooterLib 프로젝트를 선택한 다음 바로 가기 메뉴에서 속성을 선택합니다.In Solution Explorer, choose the RooterLib project, and then choose Properties from the shortcut menu.

      전처리기 기호 정의 추가Add a preprocessor symbol definition

    2. RooterLib 속성 페이지 대화 상자에서 구성 속성, C++를 차례로 확장하고 전처리기를 선택합니다.In the RooterLib Property Page dialog box, expand Configuration Properties, expand C++ and choose Preprocessor.

    3. 전처리기 정의 목록에서 <편집...>을 선택하고 전처리기 정의 대화 상자에서 ROOTERLIB_EXPORTS를 추가합니다.Choose <Edit...> from the Preprocessor Definitions list, and then add ROOTERLIB_EXPORTS in the Preprocessor Definitions dialog box.

  5. 선언된 함수의 최소 구현을 추가합니다.Add minimal implementations of the declared functions. RooterLib.cpp를 열고 다음 코드를 추가합니다.Open RooterLib.cpp and add the following code:

    // constructor  
    CRooterLib::CRooterLib()  
    {  
    }  
    
    // Find the square root of a number.  
    double CRooterLib::SquareRoot(double v)  
    {  
        return 0.0;  
    }  
    

dll 함수가 테스트 코드에 표시되도록 설정Make the dll functions visible to the test code

  1. RooterLibTests 프로젝트에 RooterLib를 추가합니다.Add RooterLib to the RooterLibTests project.

    1. 솔루션 탐색기에서 RooterLibTests 프로젝트를 선택한 다음 바로 가기 메뉴에서 참조...를 선택합니다.In Solution Explorer, choose the RooterLibTests project and then choose References... on the shortcut menu.

    2. RooterLib 프로젝트 속성 대화 상자에서 공용 속성을 확장하고 프레임워크 및 참조를 선택합니다.On the RooterLib Project Properties dialog box, expand Common Properties and choose Framework and References.

    3. 새 참조 추가...를 선택합니다.Choose Add New Reference....

    4. 참조 추가 대화 상자에서 솔루션을 확장한 다음 프로젝트를 선택합니다.In the Add Reference dialog box, expand Solution and then choose Projects. 그런 다음 RouterLib 항목을 선택합니다.Then select the RouterLib item.

  2. unittest1.cpp에 RooterLib 헤더 파일을 포함합니다.Include the RooterLib header file in unittest1.cpp.

    1. unittest1.cpp를 엽니다.Open unittest1.cpp.

    2. 다음 코드를 #include "CppUnitTest.h" 줄 아래에 추가합니다.Add this code to below the #include "CppUnitTest.h" line:

      #include "..\RooterLib\RooterLib.h"  
      
  3. 가져온 함수를 사용하는 테스트를 추가합니다.Add a test that uses the imported function. unittest1.cpp에 다음 코드를 추가합니다.Add the following code to unittest1.cpp:

    TEST_METHOD(BasicTest)  
    {  
        CRooterLib rooter;  
        Assert::AreEqual(  
            // Expected value:  
            0.0,   
            // Actual value:  
            rooter.SquareRoot(0.0),   
            // Tolerance:  
            0.01,  
            // Message:  
            L"Basic test failed",  
            // Line number - used if there is no PDB file:  
            LINE_INFO());  
    }  
    
  4. 솔루션을 빌드합니다.Build the solution.

    새 테스트가 테스트 탐색기의 실행하지 않은 테스트 노드에 표시됩니다.The new test appears in Test Explorer in the Not Run Tests node.

  5. 테스트 탐색기에서 모두 실행을 선택합니다.In Test Explorer, choose Run All.

    기본 테스트 통과Basic Test passed

    테스트 및 코드 프로젝트를 설정하고 코드 프로젝트에서 함수를 실행하는 테스트를 실행할 수 있는지 확인했습니다.You have set up the test and the code projects, and verified that you can run tests that run functions in the code project. 이제 실제 테스트 및 코드 작성을 시작할 수 있습니다.Now you can begin to write real tests and code.

반복적으로 테스트를 확장하고 통과하도록 만들기Iteratively augment the tests and make them pass

  1. 새 테스트 추가:Add a new test:

    TEST_METHOD(RangeTest)  
    {  
        CRooterLib rooter;  
        for (double v = 1e-6; v < 1e6; v = v * 3.2)  
        {  
            double expected = v;  
            double actual = rooter.SquareRoot(v*v);  
            double tolerance = expected/1000;  
            Assert::AreEqual(expected, actual, tolerance);  
        }  
    };  
    

    통과된 테스트는 변경하지 않는 것이 좋습니다.We recommend that you do not change tests that have passed. 대신, 새 테스트를 추가하고, 테스트가 통과하도록 코드를 업데이트하고, 다시 다른 테스트를 추가하는 방식을 반복합니다.Instead, add a new test, update the code so that the test passes, and then add another test, and so on.

    사용자가 요구 사항을 변경할 경우, 더 이상 올바르지 않은 테스트는 비활성화합니다.When your users change their requirements, disable the tests that are no longer correct. 새 테스트를 작성하고, 동일한 증분 방식으로 한 번에 하나씩 작동합니다.Write new tests and make them work one at a time, in the same incremental manner.

  2. 테스트 탐색기에서 모두 실행을 선택합니다.In Test Explorer, choose Run All.

  3. 테스트가 실패합니다.The test fails.

    RangeTest 실패The RangeTest fails

    테스트 작성 후 즉시 각 테스트가 실패하는지 확인합니다.Verify that each test fails immediately after you have written it. 이렇게 하면 결코 실패하지 않는 테스트를 작성하게 되는 간단한 실수를 방지하는 데 도움이 됩니다.This helps you avoid the easy mistake of writing a test that never fails.

  4. 새 테스트가 통과하도록 테스트 중인 코드를 개선합니다.Enhance the code under test so that the new test passes. RooterLib.cpp에 다음을 추가합니다.Add the following to RooterLib.cpp:

    #include <math.h>  
    ...  
    // Find the square root of a number.  
    double CRooterLib::SquareRoot(double v)  
    {  
        double result = v;  
        double diff = v;  
        while (diff > result/1000)  
        {  
            double oldResult = result;  
            result = result - (result*result - v)/(2*result);  
            diff = abs (oldResult - result);  
        }  
        return result;  
    }  
    
  5. 솔루션을 빌드한 다음 테스트 탐색기에서 모두 실행을 선택합니다.Build the solution and then in Test Explorer, choose Run All.

    두 테스트가 모두 통과합니다.Both tests pass.

한 번에 하나씩 테스트를 추가하여 코드를 개발합니다.Develop code by adding tests one at a time. 각 반복 후 모든 테스트가 통과하는지 확인합니다.Make sure that all the tests pass after each iteration.

실패한 테스트 디버그Debug a failing test

  1. unittest1.cpp에 다른 테스트를 추가합니다.Add another test to unittest1.cpp:

    // Verify that negative inputs throw an exception.  
     TEST_METHOD(NegativeRangeTest)  
     {  
       wchar_t message[200];  
       CRooterLib rooter;  
       for (double v = -0.1; v > -3.0; v = v - 0.5)  
       {  
         try   
         {  
           // Should raise an exception:  
           double result = rooter.SquareRoot(v);  
    
           swprintf_s(message, L"No exception for input %g", v);  
           Assert::Fail(message, LINE_INFO());  
         }  
         catch (std::out_of_range ex)  
         {  
           continue; // Correct exception.  
         }  
         catch (...)  
         {  
           swprintf_s(message, L"Incorrect exception for %g", v);  
           Assert::Fail(message, LINE_INFO());  
         }  
       }  
    };  
    
  2. 테스트 탐색기에서 모두 실행을 선택합니다.In Test Explorer, choose Run All.

    테스트가 실패합니다.The test fails. 테스트 탐색기에서 테스트 이름을 선택합니다.Choose the test name in Test Explorer. 실패한 어설션이 강조 표시됩니다.The failed assertion is highlighted. 오류 메시지는 테스트 탐색기의 세부 정보 창에 표시됩니다.The failure message is visible in the detail pane of Test Explorer.

    NegativeRangeTest 실패NegativeRangeTests failed

  3. 테스트가 실패한 이유를 확인하려면 함수를 단계별로 실행합니다.To see why the test fails, step through the function:

    1. SquareRoot 함수의 시작 부분에 중단점을 설정합니다.Set a breakpoint at the start of the SquareRoot function.

    2. 실패한 테스트의 바로 가기 메뉴에서 선택한 테스트 디버그를 선택합니다.On the shortcut menu of the failed test, choose Debug Selected Tests.

      중단점에서 실행이 중지되면 코드를 단계별로 실행합니다.When the run stops at the breakpoint, step through the code.

    3. 예외를 catch하는 코드를 RooterLib.cpp에 추가합니다.Add code to RooterLib.cpp to catch the exception:

      #include <stdexcept>  
      ...  
      double CRooterLib::SquareRoot(double v)  
      {  
          //Validate the input parameter:  
          if (v < 0.0)   
          {  
            throw std::out_of_range("Can't do square roots of negatives");  
          }  
      ...  
      
    4. 테스트 탐색기에서 모두 실행을 선택하여 수정된 메서드를 테스트하고 실패가 재발하지 않는지 확인합니다.In Test Explorer, choose Run All to test the corrected method and make sure that you haven't introduced a regression.

    이제 모든 테스트가 통과합니다.All tests now pass.

    모든 테스트 통과All tests pass

테스트를 변경하지 않고 코드 리팩터링Refactor the code without changing tests

  1. SquareRoot 함수에서 중앙 계산을 단순화합니다.Simplify the central calculation in the SquareRoot function:

    // old code  
    //result = result - (result*result - v)/(2*result);  
    // new code  
    result = (result + v/result) / 2.0;  
    
  2. 모두 실행을 선택하여 리팩터링된 메서드를 테스트하고 실패가 재발하지 않는지 확인합니다.Choose Run All to test the refactored method and make sure that you haven't introduced a regression.

    훌륭한 단위 테스트의 안정적인 집합은 코드를 변경할 때 버그를 만들지 않았다는 확신을 줍니다.A stable set of good unit tests gives confidence that you have not introduced bugs when you change the code.

    리팩터링은 다른 변경 사항과 구분해서 관리합니다.Keep refactoring separate from other changes.