Visual C# 코드 유닛 테스트Unit testing Visual C# code

이 항목에서는 UWP 앱에서 Visual C# 클래스에 대한 단위 테스트를 만드는 한 가지 방법에 대해 설명합니다.This topic describes one way to create unit tests for a Visual C# class in a UWP app. Rooter 클래스는 지정된 숫자의 제곱근 예상 값을 계산하는 함수를 구현하여 미적분법의 극한 이론을 보여 줍니다.The Rooter class demonstrates vague memories of limit theory from calculus by implementing a function that calculates an estimate of the square root of a given number. Maths 응용 프로그램은 이 함수를 사용하여 수학으로 할 수 있는 재미있는 작업을 사용자에게 보여 줄 수 있습니다.The Maths app can then use this function to show a user the fun things that can be done with math.

이 항목에서는 개발의 첫 단계로 단위 테스트를 사용하는 방법을 보여 줍니다.This topic demonstrates 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.

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

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

  2. 새 프로젝트 대화 상자에서 설치됨 > Visual C# 을 확장하고 Windows 유니버설을 선택합니다.In the New Project dialog box, expand Installed > Visual C# and choose Windows Universal. 그런 다음 프로젝트 템플릿 목록에서 새 응용 프로그램을 선택합니다.Then choose Blank App from the list of project templates.

  3. 프로젝트의 이름을 Maths로 지정하고 솔루션용 디렉터리 만들기가 선택되어 있는지 확인합니다.Name the project Maths and make sure Create directory for solution is selected.

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

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

  6. Visual Studio 편집기에서 UnitTest1.cs를 엽니다.Open UnitTest1.cs in the Visual Studio editor.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using Microsoft.VisualStudio.TestTools.UnitTesting;
    using Maths;
    
    namespace RooterTests
    {
        [TestClass]
        public class UnitTest1
    
            [TestMethod]
            public void TestMethod1()
            {
    
            }
    

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

    • 각 테스트는 TestMethodAttribute 특성을 사용하여 정의됩니다.Each test is defined by using the TestMethodAttribute attribute. 테스트 메서드는 void를 반환해야 하고 매개 변수를 사용할 수 없습니다.A test method must return void and can't have any parameters.

    • 테스트 메서드는 TestClassAttribute 특성으로 데코레이팅된 클래스에 있어야 합니다.Test methods must be in a class decorated with the TestClassAttribute attribute.

      테스트가 실행될 때 각 테스트 클래스의 인스턴스가 만들어집니다.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. 자세한 내용은 단위 테스트에서 MSTest 프레임워크 사용을 참조하세요.For more information, see Using the MSTest framework in unit tests.

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

  1. UnitTest1.cs 파일의 TestMethod1에 테스트 코드를 삽입합니다.Insert some test code in TestMethod1 in the UnitTest1.cs file:

    [TestMethod]
    public void TestMethod1()
    {
        Assert.AreEqual(0, 0);
    }
    

    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.

    테스트 탐색기

Maths 프로젝트에 Rooter 클래스 추가Add the Rooter class to the Maths project

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

  2. 클래스 파일의 이름을 Rooter.cs로 지정합니다.Name the class file Rooter.cs.

  3. 다음 코드를 Rooter 클래스 Rooter.cs 파일에 추가합니다.Add the following code to the Rooter class Rooter.cs file:

    public Rooter()
    {
    }
    
    // estimate the square root of a number
    public double SquareRoot(double x)
    {
        return 0.0;
    }
    

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

  4. SquareRoot 메서드는 테스트 설정의 기본 구조를 테스트할 정도의 최소 구현일 뿐입니다.The SquareRoot method is only a minimal implementation, just enough to test the basic structure of the testing setup.

응용 프로그램 프로젝트에 테스트 프로젝트 연결Couple the test project to the app project

  1. Maths 응용 프로그램에 대한 참조를 RooterTests 프로젝트에 추가합니다.Add a reference to the Maths app to the RooterTests project.

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

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

      Maths 프로젝트에 참조 추가

  2. UnitTest1.cs 파일에 using 문을 추가합니다.Add a using statement to the UnitTest1.cs file:

    1. UnitTest1.cs를 엽니다.Open UnitTest1.cs.

    2. using Microsoft.VisualStudio.TestTools.UnitTesting; 줄 아래에 다음 코드를 추가합니다.Add this code below the using Microsoft.VisualStudio.TestTools.UnitTesting; line:

      using Maths;
      
  3. Rooter 함수를 사용하는 테스트를 추가합니다.Add a test that uses the Rooter function. UnitTest1.cs에 다음 코드를 추가합니다.Add the following code to UnitTest1.cs:

    [TestMethod]
    public void BasicTest()
    {
        Maths.Rooter rooter = new Rooter();
        double expected = 0.0;
        double actual = rooter.SquareRoot(expected * expected);
        double tolerance = .001;
        Assert.AreEqual(expected, actual, tolerance);
    }
    
  4. 솔루션을 빌드합니다.Build the solution.

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

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

    기본 테스트 통과

테스트 및 코드 프로젝트를 설정하고 코드 프로젝트에서 함수를 실행하는 테스트를 실행할 수 있는지 확인했습니다.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:

    [TestMethod]
    public void RangeTest()
    {
        Rooter rooter = new Rooter();
        for (double v = 1e-6; v < 1e6; v = v * 3.2)
        {
            double expected = v;
            double actual = rooter.SquareRoot(v*v);
            double tolerance = ToleranceHelper(expected);
            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 실패

    테스트를 작성한 후 즉시 각 테스트가 실패하는지 확인합니다.Immediately after you have written it, verify that each test fails. 이렇게 하면 결코 실패하지 않는 테스트를 작성하게 되는 간단한 실수를 방지하는 데 도움이 됩니다.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. Rooter.cs에서 SquareRoot 함수를 다음과 같이 변경합니다.Change the SquareRoot function in Rooter.cs to this:

    public double SquareRoot(double x)
    {
        double estimate = x;
        double diff = x;
        while (diff > estimate / 1000)
        {
            double previousEstimate = estimate;
            estimate = estimate - (estimate * estimate - x) / (2 * estimate);
            diff = Math.Abs(previousEstimate - estimate);
        }
        return estimate;
    }
    
  5. 솔루션을 빌드한 다음, 테스트 탐색기에서 모두 실행을 선택합니다.Build the solution and then in Test Explorer, choose Run All.

    이제 세 테스트가 모두 통과합니다.All three tests now 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.csAdd another test to UnitTest1.cs:

    // Verify that negative inputs throw an exception.
    [TestMethod]
    public void NegativeRangeTest()
    {
        string message;
        Rooter rooter = new Rooter();
        for (double v = -0.1; v > -3.0; v = v - 0.5)
        {
            try
            {
                // Should raise an exception:
                double actual = rooter.SquareRoot(v);
    
                message = String.Format("No exception for input {0}", v);
                Assert.Fail(message);
            }
            catch (ArgumentOutOfRangeException ex)
            {
                continue; // Correct exception.
            }
            catch (Exception e)
            {
                message = String.Format("Incorrect exception for {0}", v);
                Assert.Fail(message);
            }
        }
    }
    
  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.

    NegativeRangeTests 실패

  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하기 위해 코드를 Rooter 메서드에 추가합니다.Add code to the Rooter method to catch the exception:

      public double SquareRoot(double x)
      {
          if (x < 0.0)
          {
              throw new ArgumentOutOfRangeException();
      }
      
  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.

모든 테스트 통과

코드 리팩터링Refactor the code

SquareRoot 함수에서 중앙 계산을 간소화합니다.Simplify the central calculation in the SquareRoot function.

  1. 결과 구현을 변경합니다.Change the result implementation

    // 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.

테스트 코드를 리팩터링하여 중복 코드를 제거합니다.Refactor the test code to eliminate duplicated code.

RangeTest 메서드는 Assert 메서드에 전달되는 tolerance 변수의 분모를 하드 코드합니다.Note that the RangeTest method hard codes the denominator of the tolerance variable that is passed to the Assert method. 동일한 허용 오차 계산을 사용하는 추가 테스트를 추가할 계획인 경우 여러 위치에서 하드 코드된 값을 사용하면 오류가 발생할 수 있습니다.If you plan to add additional tests that use the same tolerance calculation, the use of a hard-coded value in multiple locations could lead to errors.

  1. 허용 오차 값을 계산하기 위해 전용 메서드를 Unit1Test 클래스에 추가한 다음, 이 메서드를 대신 호출합니다.Add a private method to the Unit1Test class to calculate the tolerance value, and then call that method instead.

    private double ToleranceHelper(double expected)
    {
        return expected / 1000;
    }
    
    ...
    
    [TestMethod]
    public void RangeTest()
    {
        ...
        // old code
        // double tolerance = expected/1000;
        // new code
        double tolerance = ToleranceHelper(expected);
        Assert.AreEqual(expected, actual, tolerance);
    }
    ...
    
  2. 모두 실행을 선택하여 리팩터링된 메서드를 테스트하고 오류가 없는지 확인합니다.Choose Run All to test the refactored method and make sure that you haven't introduced an error.

참고

테스트 탐색기에 표시하지 않으려는 도우미 메서드를 테스트 클래스에 추가하는 경우, 이 메서드에 TestMethodAttribute 특성을 추가하지 마세요.If you add a helper method to a test class that you don't want to appear in Test Explorer, do not add the TestMethodAttribute attribute to the method.