연습 - Azure Function 단위 테스트

완료됨

단위 테스트는 Agile 방법론의 핵심 부분입니다. Visual Studio는 테스트 프로젝트 템플릿을 제공합니다. 이 템플릿을 사용하여 애플리케이션에 대한 단위 테스트를 만들고, 동일한 기술을 Azure Functions 테스트에 적용할 수 있습니다.

고급 시계 온라인 웹 사이트 시나리오에서 개발 팀은 단위 테스트에서 80% 이상의 코드 검사를 달성하는 정책을 사용하고 있습니다. Azure Functions에도 동일한 정책을 구현하려고 합니다.

여기서는 Visual Studio에서 xUnit 테스트 프레임워크를 사용하여 Azure Functions를 테스트하는 방법을 알아봅니다.

단위 테스트 프로젝트 만들기

첫 번째 단계는 단위 테스트가 포함된 프로젝트를 만들고, 이를 Azure 함수 앱을 보유하는 솔루션에 추가하는 것입니다. 다음 단계를 사용하여 WatchInfo 함수를 테스트하기 위한 단위 테스트 프로젝트를 만듭니다.

  1. Visual Studio의 솔루션 탐색기 창에서 WatchPortalFunction 솔루션을 마우스 오른쪽 단추로 클릭하고 추가를 선택한 다음, 새 프로젝트를 선택합니다.

    솔루션에 새 프로젝트 추가 명령을 보여 주는 솔루션 탐색기의 스크린샷

  2. 새 프로젝트 추가 창에서 아래로 스크롤하여 xUnit 테스트 프로젝트C#+ 아이콘 템플릿을 선택한 후, 다음을 선택합니다.

    새 프로젝트 추가 창의 스크린샷 xUnit 테스트 프로젝트 템플릿이 선택되어 있습니다.

  3. 새 프로젝트 구성 창이 나타납니다. 프로젝트 이름 필드에 WatchFunctionsTests를 입력합니다. 위치 필드 외에, 찾아보기 아이콘을 선택한 다음, WatchPortalFunction 폴더를 선택합니다.

  4. 다음을 선택합니다. 추가 정보 창이 표시됩니다.

  5. 대상 프레임워크에서 .NET 6.0(장기 지원)의 기본값을 사용합니다.

  6. 만들기를 실행합니다.

  7. 프로젝트가 추가되면 솔루션 탐색기 창에서 WatchFunctionTests 프로젝트를 마우스 오른쪽 단추로 클릭하고 NuGet 패키지 관리를 선택합니다.

  8. NuGet: WatchFunctionTests 창에서 찾아보기 탭을 선택합니다. 검색 상자에 Microsoft.AspNetCore.Mvc를 입력합니다. Microsoft.AspNetCore.Mvc 패키지를 선택한 다음, 설치를 선택합니다.

    NuGet 패키지 관리자 창의 스크린샷. 사용자가 Microsoft.AspNetCore.Mvc 패키지를 설치하고 있음

    참고

    테스트 프로젝트에서는 모의 HTTP 환경을 만듭니다. 이 작업을 수행하는 데 필요한 클래스는 Microsoft.AspNetCore.Mvc 패키지에 있습니다.

  9. 패키지가 설치될 때까지 기다립니다. 변경 내용 미리 보기 메시지 상자가 표시되면 확인을 선택합니다. 라이선스 승인 메시지 상자에서 동의함을 선택합니다.

  10. 패키지가 추가되면 솔루션 탐색기 창에서 WatchFunctionsTest 프로젝트 아래의 UnitTest1.cs 파일을 마우스 오른쪽 단추로 클릭하고 이름 바꾸기를 선택합니다. 파일 이름을 WatchFunctionUnitTests.cs로 변경합니다. 표시되는 메시지 상자에서 UnitTest1의 모든 참조 이름을 WatchFunctionUnitTests로 바꾸기 위해 를 선택합니다.

  11. 솔루션 탐색기 창에서 WatchFunctionsTests 프로젝트 아래의 종속성을 마우스 오른쪽 단추로 클릭한 다음, 프로젝트 참조 추가를 선택합니다.

  12. 참조 관리자 창에서 WatchPortalFunction 프로젝트를 선택한 다음, 확인을 선택합니다.

WatchInfo 함수에 대한 단위 테스트 추가

이제 단위 테스트를 테스트 프로젝트에 추가할 수 있습니다. 고급 시계 시나리오에서는 모델이 요청의 쿼리 문자열에 제공되면 WatchInfo 함수에서 항상 OK 응답을 반환하고, 쿼리 문자열이 비어 있거나 model 매개 변수가 없는 경우 나쁨 응답을 반환해야 합니다.

이 동작을 확인하려면 Fact 테스트 쌍을 WatchFunctionsTests에 추가합니다.

  1. 솔루션 탐색기 창에서 WatchPortalFunction을 코드 창에 표시하려면 WatchFunctionUnitTests.cs 파일을 두 번 클릭합니다.

  2. 파일 맨 위의 목록에 다음 using 지시문을 추가합니다.

    using Microsoft.AspNetCore.Http;
    using Microsoft.AspNetCore.Http.Internal;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.Extensions.Primitives;
    using Microsoft.Extensions.Logging.Abstractions;
    
  3. Test1 메서드의 이름을 TestWatchFunctionSuccess로 변경합니다.

  4. 다음 코드를 TestWatchFunctionSuccess 메서드의 본문에 추가합니다. 이 명령문은 모의 HTTP 컨텍스트 및 HTTP 요청을 만듭니다. 요청에는 model 매개 변수를 포함하고 abc로 설정된 쿼리 문자열이 있습니다.

    var queryStringValue = "abc";
    var request = new DefaultHttpRequest(new DefaultHttpContext())
    {
        Query = new QueryCollection
        (
            new System.Collections.Generic.Dictionary<string, StringValues>()
            {
                { "model", queryStringValue }
            }
        )
    };
    
  5. 메서드에 다음 문을 추가합니다. 이 명령문은 더미 로거를 만듭니다.

    var logger = NullLoggerFactory.Instance.CreateLogger("Null Logger");
    
  6. 다음 코드를 메서드에 추가합니다. 이러한 명령문은 더미 요청과 로거를 매개 변수로 전달하여 WatchInfo 함수를 호출합니다.

    var response = WatchPortalFunction.WatchInfo.Run(request, logger);
    response.Wait();
    
  7. 다음 코드를 메서드에 추가합니다. 이 코드는 함수의 응답이 올바른지 확인합니다. 이 경우 함수는 본문의 예상 데이터를 포함하는 OK 응답을 반환해야 합니다.

    // Check that the response is an "OK" response
    Assert.IsAssignableFrom<OkObjectResult>(response.Result);
    
    // Check that the contents of the response are the expected contents
    var result = (OkObjectResult)response.Result;
    dynamic watchinfo = new { Manufacturer = "abc", CaseType = "Solid", Bezel = "Titanium", Dial = "Roman", CaseFinish = "Silver", Jewels = 15 };
    string watchInfo = $"Watch Details: {watchinfo.Manufacturer}, {watchinfo.CaseType}, {watchinfo.Bezel}, {watchinfo.Dial}, {watchinfo.CaseFinish}, {watchinfo.Jewels}";
    Assert.Equal(watchInfo, result.Value);
    

    전체 메서드는 다음과 같습니다.

    [Fact]
    public void TestWatchFunctionSuccess()
    {
        var queryStringValue = "abc";
        var request = new DefaultHttpRequest(new DefaultHttpContext())
        {
            Query = new QueryCollection
            (
                new System.Collections.Generic.Dictionary<string, StringValues>()
                {
                    { "model", queryStringValue }
                }
            )
        };
    
        var logger = NullLoggerFactory.Instance.CreateLogger("Null Logger");
    
        var response = WatchPortalFunction.WatchInfo.Run(request, logger);
        response.Wait();
    
        // Check that the response is an "OK" response
        Assert.IsAssignableFrom<OkObjectResult>(response.Result);
    
        // Check that the contents of the response are the expected contents
        var result = (OkObjectResult)response.Result;
        dynamic watchinfo = new { Manufacturer = "abc", CaseType = "Solid", Bezel = "Titanium", Dial = "Roman", CaseFinish = "Silver", Jewels = 15 };
        string watchInfo = $"Watch Details: {watchinfo.Manufacturer}, {watchinfo.CaseType}, {watchinfo.Bezel}, {watchinfo.Dial}, {watchinfo.CaseFinish}, {watchinfo.Jewels}";
        Assert.Equal(watchInfo, result.Value);
    }
    
  8. TestWatchFunctionFailureNoQueryStringTestWatchFunctionFailureNoModel이라는 두 메서드를 더 추가합니다. TestWatchFunctionFailureNoQueryString은 쿼리 문자열이 제공되지 않으면 WatchInfo 함수가 정상적으로 실패하는지 확인합니다. TestWatchFunctionFailureNoModel은 model 매개 변수가 없는 쿼리 문자열이 함수에 전달되면 동일한 실패가 있는지 확인합니다.

    [Fact]
    public void TestWatchFunctionFailureNoQueryString()
    {
        var request = new DefaultHttpRequest(new DefaultHttpContext());
        var logger = NullLoggerFactory.Instance.CreateLogger("Null Logger");
    
        var response = WatchPortalFunction.WatchInfo.Run(request, logger);
        response.Wait();
    
        // Check that the response is an "Bad" response
        Assert.IsAssignableFrom<BadRequestObjectResult>(response.Result);
    
        // Check that the contents of the response are the expected contents
        var result = (BadRequestObjectResult)response.Result;
        Assert.Equal("Please provide a watch model in the query string", result.Value);
    }
    
    [Fact]
    public void TestWatchFunctionFailureNoModel()
    {
        var queryStringValue = "abc";
        var request = new DefaultHttpRequest(new DefaultHttpContext())
        {
            Query = new QueryCollection
            (
                new System.Collections.Generic.Dictionary<string, StringValues>()
                {
                    { "not-model", queryStringValue }
                }
            )
        };
    
        var logger = NullLoggerFactory.Instance.CreateLogger("Null Logger");
    
        var response = WatchPortalFunction.WatchInfo.Run(request, logger);
        response.Wait();
    
        // Check that the response is an "Bad" response
        Assert.IsAssignableFrom<BadRequestObjectResult>(response.Result);
    
        // Check that the contents of the response are the expected contents
        var result = (BadRequestObjectResult)response.Result;
        Assert.Equal("Please provide a watch model in the query string", result.Value);
    }
    

테스트 실행

  1. 상단 메뉴 모음의 테스트에서 모든 테스트 실행을 선택합니다.

    Visual Studio의 테스트 메뉴 스크린샷. 사용자가 실행 -> 모든 테스트를 선택했습니다.

  2. 테스트 탐색기 창에서 세 가지 테스트가 모두 성공적으로 완료됩니다.

    팀 탐색기 창의 스크린샷. 세 가지 테스트가 모두 성공적으로 실행됨

  3. 솔루션 탐색기 창에서 WatchPortalFunction 프로젝트 아래의 WatchInfo.cs를 코드 편집기에 표시하기 위해 파일을 두 번 클릭합니다.

  4. 다음 코드를 찾습니다.

    // Retrieve the model id from the query string
    string model = req.Query["model"];
    
  5. model 변수를 설정한 명령문을 다음과 같이 변경합니다. 이 변경은 개발자의 코드 작성 실수를 시뮬레이션합니다.

    string model = req.Query["modelll"];
    
  6. 상단 메뉴 모음의 테스트에서 모든 테스트 실행을 선택합니다. 이번에는 TestWatchFunctionSuccess 테스트가 실패합니다. 이 실패는 WatchInfo 함수가 쿼리 문자열에서 modelll이라는 매개 변수를 찾지 못하여 발생하며, 이에 따라 함수에서 나쁨 응답을 반환했습니다.

    팀 탐색기 창의 스크린샷. 실패한 TestWatchFunctionSuccess 테스트

이 단원에서는 단위 테스트 프로젝트를 만들고 Azure Function에 대한 단위 테스트를 구현하는 방법을 알아보았습니다.