다음을 통해 공유


ASP.NET Core MVC 앱 테스트

팁 (조언)

이 콘텐츠는 .NET Docs 또는 오프라인으로 읽을 수 있는 무료 다운로드 가능한 PDF로 제공되는 eBook인 'ASP.NET Core와 Azure로 현대 웹 애플리케이션 설계하기'에서 발췌한 내용입니다.

ASP.NET Core 및 Azure를 통해 최신 웹 애플리케이션 설계에 관한 eBook 커버 썸네일입니다.

"제품 단위 테스트가 마음에 들지 않으면 고객이 제품을 테스트하는 것을 좋아하지 않을 가능성이 높습니다." _-익명의-

복잡한 소프트웨어는 변경에 대응하여 예기치 않은 방식으로 실패할 수 있습니다. 따라서 변경 후 테스트는 가장 사소한(또는 가장 중요하지 않은) 애플리케이션을 제외한 모든 애플리케이션에 필요합니다. 수동 테스트는 소프트웨어를 테스트하는 데 있어 속도가 가장 느리고, 안정성이 가장 낮고, 비용이 가장 많이 드는 방법입니다. 아쉽게도 애플리케이션을 테스트할 수 있도록 설계되지 않은 경우 사용 가능한 유일한 테스트 수단이 될 수 있습니다. 4장에서 설명하는 아키텍처 원칙을 따르기 위해 작성된 애플리케이션은 대부분 단위 테스트가 가능해야 합니다. ASP.NET Core 애플리케이션은 자동화된 통합 및 기능 테스트를 지원합니다.

자동화된 테스트 종류

소프트웨어 애플리케이션에 대한 자동화된 테스트에는 여러 종류가 있습니다. 가장 간단하고 가장 낮은 수준의 테스트는 단위 테스트입니다. 약간 더 높은 수준에서 통합 테스트와 기능 테스트가 있습니다. UI 테스트, 부하 테스트, 스트레스 테스트 및 연기 테스트와 같은 다른 종류의 테스트는 이 문서의 범위를 벗어됩니다.

단위 테스트

단위 테스트는 애플리케이션 논리의 단일 부분을 테스트합니다. 아닌 항목 중 일부를 나열하여 자세히 설명할 수 있습니다. 단위 테스트는 코드에서 종속성 또는 인프라와 작동하는 방식을 테스트하지 않으므로 통합 테스트가 필요합니다. 단위 테스트는 코드가 작성된 프레임워크를 테스트하지 않습니다. 작동한다고 가정하거나 그렇지 않은 경우 버그를 제출하고 해결 방법을 코딩해야 합니다. 단위 테스트는 메모리 및 프로세스에서 완전히 실행됩니다. 파일 시스템, 네트워크 또는 데이터베이스와 통신하지 않습니다. 단위 테스트는 코드만 테스트해야 합니다.

단위 테스트는 외부 종속성이 없는 코드의 단일 단위만 테스트하므로 매우 빠르게 실행되어야 합니다. 따라서 몇 초 안에 수백 개의 단위 테스트의 테스트 제품군을 실행할 수 있어야 합니다. 가능하면 공유 소스 제어 리포지토리로 푸시하기 전마다, 그리고 반드시 빌드 서버의 모든 자동화된 빌드와 함께 자주 실행하세요.

통합 테스트

데이터베이스 및 파일 시스템과 같은 인프라와 상호 작용하는 코드를 캡슐화하는 것이 좋지만, 여전히 해당 코드 중 일부를 사용할 수 있고 이를 테스트하려고 할 수도 있습니다. 또한 애플리케이션의 종속성이 완전히 해결되면 코드 계층이 예상대로 상호 작용하는지도 확인해야 합니다. 이 기능은 통합 테스트에서 수행해야 합니다. 통합 테스트는 종종 외부 종속성 및 인프라에 의존하기 때문에 단위 테스트보다 느리고 설정하기가 더 어려운 경향이 있습니다. 따라서 통합 테스트에서 단위 테스트로 테스트할 수 있는 테스트는 피해야 합니다. 단위 테스트로 지정된 시나리오를 테스트할 수 있는 경우 단위 테스트로 테스트해야 합니다. 할 수 없는 경우 통합 테스트를 사용하는 것이 좋습니다.

통합 테스트에는 단위 테스트보다 더 복잡한 설정 및 해체 절차가 있는 경우가 많습니다. 예를 들어 실제 데이터베이스에 반하는 통합 테스트는 각 테스트를 실행하기 전에 데이터베이스를 알려진 상태로 되돌리는 방법이 필요합니다. 새 테스트가 추가되고 프로덕션 데이터베이스 스키마가 진화함에 따라 이러한 테스트 스크립트의 크기와 복잡성이 증가하는 경향이 있습니다. 많은 대규모 시스템에서는 공유 소스 제어에 대한 변경 내용을 확인하기 전에 개발자 워크스테이션에서 전체 통합 테스트 제품군을 실행하는 것은 실용적이지 않습니다. 이러한 경우 통합 테스트는 빌드 서버에서 실행될 수 있습니다.

기능 테스트

통합 테스트는 시스템의 일부 구성 요소가 올바르게 함께 작동하는지 확인하기 위해 개발자의 관점에서 작성됩니다. 기능 테스트는 사용자의 관점에서 작성되며 요구 사항에 따라 시스템의 정확성을 확인합니다. 다음 발췌문은 단위 테스트에 비해 기능 테스트에 대해 생각하는 방법에 대한 유용한 비유를 제공합니다.

"시스템의 개발은 여러 번 집의 건물에 비유된다. 이 비유는 정확하지는 않지만 단위 테스트와 기능 테스트의 차이를 이해하기 위해 확장할 수 있습니다. 유닛 테스트는 주택의 건설 현장을 방문하는 건물 조사관과 유사합니다. 그는 집의 다양한 내부 시스템, 기초, 프레이밍, 전기, 배관 등에 초점을 맞추고 있습니다. 그는 집의 각 부분이 올바르고 안전하게 작동하도록 테스트하여 건축 기준을 충족시키는지 확인합니다. 이 시나리오의 기능 테스트는 동일한 건설 현장을 방문하는 주택 소유자와 유사합니다. 그는 내부 시스템이 적절하게 동작하고 건물 조사관이 작업을 수행하고 있다고 가정합니다. 주택 소유자는이 집에 사는 것이 어땠는지에 초점을 맞추고 있습니다. 그는 집이 어떻게 보이는지, 다양한 방이 편안한 크기이며, 집이 가족의 요구에 맞는지, 아침 태양을 잡을 수있는 좋은 장소의 창문인지에 관심이 있습니다. 주택 소유자는 집에서 기능 테스트를 수행하고 있습니다. 그는 사용자의 관점을 가지고 있습니다. 건물 조사관은 집에서 단위 테스트를 수행하고 있습니다. 그는 건축업자의 관점을 가지고 있다"고 말했다.

원본: 단위 테스트 및 기능 테스트

"제가 자주 하는 말이 있습니다. '개발자로서 두 가지 방식으로 실패합니다: 우리가 작업을 잘못 만들거나, 잘못된 작업을 만듭니다.' 단위 테스트는 작업이 올바르게 만들어졌는지 확인하고, 기능 테스트는 올바른 작업을 만들고 있는지를 확인합니다."

기능 테스트는 시스템 수준에서 작동하므로 어느 정도의 UI 자동화가 필요할 수 있습니다. 통합 테스트와 마찬가지로 일반적으로 일종의 테스트 인프라에서도 작동합니다. 이 활동은 단위 및 통합 테스트보다 느리고 부서지기 쉽습니다. 사용자가 예상한 대로 시스템이 동작하고 있다고 확신해야 하는 만큼의 기능 테스트만 있어야 합니다.

피라미드 테스트

마틴 파울러는 그림 9-1에 표시된 테스트 피라미드에 대해 썼습니다.

피라미드 테스트

그림 9-1. 피라미드 테스트

피라미드의 여러 계층과 상대 크기는 다양한 종류의 테스트와 애플리케이션에 대해 작성해야 하는 횟수를 나타냅니다. 볼 수 있듯이, 대규모 단위 테스트 기반을 두고, 그 위에 더 적은 통합 테스트 계층을 두며, 가장 적은 기능 테스트 계층으로 구성하는 것이 좋습니다. 각 계층에는 하위 계층에서 적절하게 수행할 수 없는 테스트만 있어야 합니다. 특정 시나리오에 필요한 테스트 종류를 결정하려고 할 때 테스트 피라미드를 염두에 두어야 합니다.

테스트할 내용

자동화된 테스트를 작성하는 데 경험이 부족한 개발자에게 일반적인 문제는 테스트할 사항을 마련하는 것입니다. 좋은 시작점은 조건부 논리를 테스트하는 것입니다. 조건문(if-else, switch 등)에 따라 변경되는 동작이 있는 메서드가 있는 경우 특정 조건에 대한 올바른 동작을 확인하는 몇 가지 이상의 테스트를 마련할 수 있어야 합니다. 코드에 오류 조건이 있는 경우 코드를 통해 "행복 경로"에 대한 테스트를 하나 이상 작성하고(오류 없음) "슬픈 경로"(오류 또는 비정형 결과 포함)에 대한 테스트를 하나 이상 작성하여 오류 발생 시 애플리케이션이 예상대로 작동하는지 확인하는 것이 좋습니다. 마지막으로, 코드 검사와 같은 메트릭에 집중하지 않고 실패할 수 있는 테스트에 집중합니다. 코드 범위가 더 많을수록 일반적으로 더 낫습니다. 그러나 복잡하고 중요 비즈니스용 메서드에 대한 몇 가지 테스트를 더 작성하는 것은 일반적으로 테스트 코드 검사 메트릭을 개선하기 위해 자동 속성에 대한 테스트를 작성하는 것보다 시간을 더 잘 사용하는 것입니다.

테스트 프로젝트 구성

테스트 프로젝트는 가장 적합한 방식으로 구성할 수 있습니다. 테스트를 유형(단위 테스트, 통합 테스트) 및 테스트 대상(프로젝트별, 네임스페이스별)으로 구분하는 것이 좋습니다. 이 분리가 단일 테스트 프로젝트 내의 폴더로 구성되는지 또는 여러 테스트 프로젝트로 구성되는지 여부는 디자인 결정입니다. 하나의 프로젝트는 가장 간단하지만 많은 테스트가 있는 대규모 프로젝트의 경우 또는 다른 테스트 집합을 보다 쉽게 실행하기 위해 여러 다른 테스트 프로젝트를 사용할 수 있습니다. 많은 팀이 테스트 중인 프로젝트를 기반으로 테스트 프로젝트를 구성합니다. 여러 프로젝트가 있는 애플리케이션의 경우, 특히 각 프로젝트에 있는 테스트의 종류에 따라 테스트 프로젝트를 구분하는 경우, 상당히 많은 수의 테스트 프로젝트가 발생할 수 있습니다. 타협된 접근법은 애플리케이션당 테스트 종류별로 하나의 프로젝트를 갖고, 테스트 프로젝트 내의 폴더를 사용해 테스트 중인 프로젝트와 클래스를 나타내는 것입니다.

일반적인 방법은 'src' 폴더 아래에 애플리케이션 프로젝트를 구성하고 애플리케이션의 테스트 프로젝트를 병렬 'tests' 폴더 아래에 구성하는 것입니다. 이 조직이 유용하다면 Visual Studio에서 일치하는 솔루션 폴더를 만들 수 있습니다.

솔루션에서 조직 테스트

그림 9-2. 솔루션에서 조직 테스트

원하는 테스트 프레임워크를 사용할 수 있습니다. xUnit 프레임워크는 잘 작동하며 모든 ASP.NET Core 및 EF Core 테스트가 기록됩니다. 그림 9-3에 표시된 템플릿을 사용하거나 CLI에서 사용하여 Visual Studio에서 xUnit 테스트 프로젝트를 추가할 수 있습니다 dotnet new xunit.

Visual Studio에서 xUnit 테스트 프로젝트 추가

그림 9-3. Visual Studio에서 xUnit 테스트 프로젝트 추가

테스트 이름 지정

각 테스트가 수행하는 작업을 나타내는 이름을 사용하여 일관된 방식으로 테스트 이름을 지정합니다. 제가 성공적으로 수행한 한 가지 방법은 테스트하는 클래스 및 메서드에 따라 테스트 클래스의 이름을 지정하는 것입니다. 이 방법을 사용하면 많은 작은 테스트 클래스가 생성되지만 각 테스트의 책임이 무엇인지 매우 명확하게 알 수 있습니다. 테스트 클래스 이름을 설정하여 테스트할 클래스와 메서드를 식별하기 위해 테스트 메서드 이름을 사용하여 테스트할 동작을 지정할 수 있습니다. 이 이름에는 예상되는 동작과 이 동작을 생성해야 하는 모든 입력 또는 가정이 포함되어야 합니다. 몇 가지 예제 테스트 이름:

  • CatalogControllerGetImage.CallsImageServiceWithId

  • CatalogControllerGetImage.LogsWarningGivenImageMissingException

  • CatalogControllerGetImage.ReturnsFileResultWithBytesGivenSuccess

  • CatalogControllerGetImage.ReturnsNotFoundResultGivenImageMissingException

이 방법의 변형은 각 테스트 클래스 이름을 "Should"로 종료하고 시제를 약간 수정합니다.

  • CatalogControllerGetImage 해야 함.전화ImageServiceWithId

  • CatalogControllerGetImage 해야 한다.로그WarningGivenImageMissingException

일부 팀에서는 두 번째 명명 방법이 더 명확하지만 약간 더 길어진다고 생각합니다. 어떤 경우든 테스트 동작에 대한 인사이트를 제공하는 명명 규칙을 사용하여 하나 이상의 테스트가 실패할 때 실패한 사례를 이름에서 알 수 있도록 합니다. 테스트 결과에 표시되는 경우 이러한 이름은 값을 제공하지 않기 때문에 ControllerTests.Test1과 같이 테스트 이름을 모호하게 지정하지 마세요.

많은 작은 테스트 클래스를 생성하는 위와 같은 명명 규칙을 따르는 경우 폴더 및 네임스페이스를 사용하여 테스트를 추가로 구성하는 것이 좋습니다. 그림 9-4에서는 여러 테스트 프로젝트 내에서 폴더별로 테스트를 구성하는 한 가지 방법을 보여 줍니다.

테스트 중인 클래스를 기반으로 폴더별 테스트 클래스 구성

그림 9-4. 테스트 중인 클래스에 따라 폴더별로 테스트 클래스를 구성합니다.

특정 애플리케이션 클래스에 테스트할 메서드가 많고 테스트 클래스가 많은 경우 이러한 클래스를 애플리케이션 클래스에 해당하는 폴더에 배치하는 것이 좋습니다. 이 조직은 파일을 다른 폴더로 구성하는 방법과 다르지 않습니다. 다른 많은 파일이 포함된 폴더에 3~4개 이상의 관련 파일이 있는 경우 해당 파일을 자체 하위 폴더로 이동하는 것이 도움이 되는 경우가 많습니다.

ASP.NET Core 앱의 단위 테스트

잘 설계된 ASP.NET Core 애플리케이션에서는 대부분의 복잡성과 비즈니스 논리가 비즈니스 엔터티 및 다양한 서비스에 캡슐화됩니다. 컨트롤러, 필터, viewmodel 및 뷰를 사용하는 ASP.NET Core MVC 앱 자체에는 몇 가지 단위 테스트가 필요합니다. 지정된 작업의 대부분의 기능은 작업 메서드 자체 외부에 있습니다. 단위 테스트를 통해 라우팅 또는 전역 오류 처리 작업을 올바르게 수행할 수 없는지 테스트합니다. 마찬가지로 모델 유효성 검사 및 인증 및 권한 부여 필터를 포함한 모든 필터는 컨트롤러의 작업 메서드를 대상으로 하는 테스트로 단위 테스트할 수 없습니다. 이러한 동작 원본이 없으면 대부분의 작업 메서드는 약간 작아야 하며, 대부분의 작업은 해당 작업을 사용하는 컨트롤러와 독립적으로 테스트할 수 있는 서비스에 위임해야 합니다.

단위 테스트를 위해 코드를 리팩터링해야 하는 경우도 있습니다. 자주 이 활동에는 추상화 식별 및 종속성 주입을 사용하여 인프라에 대해 직접 코딩하는 대신 테스트하려는 코드의 추상화에 액세스하는 작업이 포함됩니다. 예를 들어 이미지를 표시하기 위한 이 쉬운 작업 방법을 고려합니다.

[HttpGet("[controller]/pic/{id}")]
public IActionResult GetImage(int id)
{
  var contentRoot = _env.ContentRootPath + "//Pics";
  var path = Path.Combine(contentRoot, id + ".png");
  Byte[] b = System.IO.File.ReadAllBytes(path);
  return File(b, "image/png");
}

이 메서드에 대한 단위 테스트는 파일 시스템에서 읽는 데 사용하는 System.IO.File에 대한 직접적인 종속성 때문에 수행하기 어렵습니다. 이 동작을 테스트하여 예상대로 작동하는지 확인할 수 있지만 실제 파일로 작동하는 것은 통합 테스트입니다. 이 메서드의 경로를 단위 테스트할 수 없습니다. 곧 기능 테스트를 사용하여 이 테스트를 수행하는 방법을 확인할 수 있습니다.

파일 시스템 동작을 직접 단위 테스트할 수 없고 경로를 테스트할 수 없는 경우 테스트할 항목이 무엇인가요? 단위 테스트를 가능하게 하기 위해 리팩터링한 후 오류 처리와 같은 일부 테스트 사례 및 누락된 동작을 발견할 수 있습니다. 파일을 찾을 수 없을 때 메서드는 어떻게 합니까? 무엇을 해야 하나요? 이 예제에서 리팩터링된 메서드는 다음과 같습니다.

[HttpGet("[controller]/pic/{id}")]
public IActionResult GetImage(int id)
{
  byte[] imageBytes;
  try
  {
    imageBytes = _imageService.GetImageBytesById(id);
  }
  catch (CatalogImageMissingException ex)
  {
    _logger.LogWarning($"No image found for id: {id}");
    return NotFound();
  }
  return File(imageBytes, "image/png");
}

_logger_imageService 둘 다 종속성으로 삽입됩니다. 이제 작업 메서드에 전달된 것과 동일한 ID가 전달 _imageService되고 결과 바이트가 FileResult의 일부로 반환되는지 테스트할 수 있습니다. 또한 이 동작이 중요한 애플리케이션 동작(즉, 개발자가 NotFound 문제를 진단하기 위해 추가한 임시 코드가 아님)을 가정하여 오류 로깅이 예상대로 발생하고 이미지가 누락된 경우 결과가 반환되는지 테스트할 수도 있습니다. 실제 파일 논리는 별도의 구현 서비스로 이동되었으며 누락된 파일의 경우 애플리케이션별 예외를 반환하도록 보강되었습니다. 통합 테스트를 사용하여 이 구현을 독립적으로 테스트할 수 있습니다.

대부분의 경우 컨트롤러에서 전역 예외 처리기를 사용하려고 하므로 논리의 양은 최소화되어야 하며 단위 테스트는 가치가 없을 수 있습니다. 기능 테스트 및 TestServer 아래에 설명된 클래스를 사용하여 대부분의 컨트롤러 작업 테스트를 수행합니다.

ASP.NET Core 앱 통합 테스트

ASP.NET Core 앱에서 대부분의 통합 테스트는 인프라 프로젝트에 정의된 서비스 및 기타 구현 유형을 테스트해야 합니다. 예를 들어 EF Core가 인프라 프로젝트에 있는 데이터 액세스 클래스에서 예상한 데이터를 성공적으로 업데이트하고 검색했는지 테스트 할 수 있습니다. ASP.NET Core MVC 프로젝트가 올바르게 작동하는지 테스트하는 가장 좋은 방법은 테스트 호스트에서 실행되는 앱에 대해 실행되는 기능 테스트를 사용하는 것입니다.

핵심 앱에 ASP.NET 기능 테스트

ASP.NET Core 애플리케이션의 경우 클래스를 TestServer 사용하면 기능 테스트를 매우 쉽게 작성할 수 있습니다. TestServerWebHostBuilder(또는 HostBuilder)으로 직접 구성하거나(일반적으로 애플리케이션에서 수행하는 것처럼), 버전 2.1부터 사용 가능한 형식인 WebApplicationFactory을(를) 사용하여 구성합니다. 테스트 호스트를 프로덕션 호스트와 최대한 가깝게 일치시키려면 테스트는 앱이 프로덕션에서 수행하는 것과 유사한 동작을 실행합니다. 이 WebApplicationFactory 클래스는 ASP.NET Core에서 Views와 같은 정적 리소스를 찾는 데 사용되는 TestServer의 ContentRoot를 구성하는 데 유용합니다.

테스트 클래스를 만들어 IClassFixture<WebApplicationFactory<TEntryPoint>>을(를) 구현하고, 이 클래스가 웹 애플리케이션의 TEntryPoint 클래스인 Startup를 구현하도록 하여 간단한 기능 테스트를 생성할 수 있습니다. 이 인터페이스를 사용하면 테스트 설비가 팩터리의 CreateClient 메서드를 사용하여 클라이언트를 만들 수 있습니다.

public class BasicWebTests : IClassFixture<WebApplicationFactory<Program>>
{
  protected readonly HttpClient _client;

  public BasicWebTests(WebApplicationFactory<Program> factory)
  {
    _client = factory.CreateClient();
  }

  // write tests that use _client
}

팁 (조언)

Program.cs 파일에서 최소 API 구성을 사용하는 경우 기본적으로 클래스가 내부로 선언되고 테스트 프로젝트에서 액세스할 수 없습니다. 대신 웹 프로젝트에서 다른 인스턴스 클래스를 선택하거나 Program.cs 파일에 추가할 수 있습니다.

// Make the implicit Program class public so test projects can access it
public partial class Program { }

메모리 내 데이터 저장소를 사용하도록 애플리케이션을 구성한 다음 테스트 데이터로 애플리케이션을 시드하는 등 각 테스트가 실행되기 전에 사이트의 일부 추가 구성을 수행하는 경우가 많습니다. 이 기능을 구현하려면 WebApplicationFactory<TEntryPoint>의 고유한 서브클래스를 만들고 그 ConfigureWebHost 메서드를 재정의하세요. 아래 예제는 eShopOnWeb FunctionalTests 프로젝트에서 나온 것이며 주 웹 애플리케이션에서 테스트의 일부로 사용됩니다.

using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.EntityFrameworkCore;
using Microsoft.eShopWeb.Infrastructure.Data;
using Microsoft.eShopWeb.Infrastructure.Identity;
using Microsoft.eShopWeb.Web;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System;

namespace Microsoft.eShopWeb.FunctionalTests.Web;
public class WebTestFixture : WebApplicationFactory<Startup>
{
  protected override void ConfigureWebHost(IWebHostBuilder builder)
  {
    builder.UseEnvironment("Testing");

    builder.ConfigureServices(services =>
    {
      services.AddEntityFrameworkInMemoryDatabase();

      // Create a new service provider.
      var provider = services
            .AddEntityFrameworkInMemoryDatabase()
            .BuildServiceProvider();

      // Add a database context (ApplicationDbContext) using an in-memory
      // database for testing.
      services.AddDbContext<CatalogContext>(options =>
      {
        options.UseInMemoryDatabase("InMemoryDbForTesting");
        options.UseInternalServiceProvider(provider);
      });

      services.AddDbContext<AppIdentityDbContext>(options =>
      {
        options.UseInMemoryDatabase("Identity");
        options.UseInternalServiceProvider(provider);
      });

      // Build the service provider.
      var sp = services.BuildServiceProvider();

      // Create a scope to obtain a reference to the database
      // context (ApplicationDbContext).
      using (var scope = sp.CreateScope())
      {
        var scopedServices = scope.ServiceProvider;
        var db = scopedServices.GetRequiredService<CatalogContext>();
        var loggerFactory = scopedServices.GetRequiredService<ILoggerFactory>();

        var logger = scopedServices
            .GetRequiredService<ILogger<WebTestFixture>>();

        // Ensure the database is created.
        db.Database.EnsureCreated();

        try
        {
          // Seed the database with test data.
          CatalogContextSeed.SeedAsync(db, loggerFactory).Wait();

          // seed sample user data
          var userManager = scopedServices.GetRequiredService<UserManager<ApplicationUser>>();
          var roleManager = scopedServices.GetRequiredService<RoleManager<IdentityRole>>();
          AppIdentityDbContextSeed.SeedAsync(userManager, roleManager).Wait();
        }
        catch (Exception ex)
        {
          logger.LogError(ex, $"An error occurred seeding the " +
                    "database with test messages. Error: {ex.Message}");
        }
      }
    });
  }
}

테스트는 이 사용자 지정 WebApplicationFactory를 사용하여 클라이언트를 만든 다음, 이 클라이언트 인스턴스를 사용하여 애플리케이션에 요청할 수 있습니다. 애플리케이션에는 테스트 어설션의 일부로 사용할 수 있는 데이터가 시드됩니다. 다음 테스트는 eShopOnWeb 애플리케이션의 홈페이지가 올바르게 로드되고 시드 데이터의 일부로 애플리케이션에 추가된 제품 목록을 포함하는지 확인합니다.

using Microsoft.eShopWeb.FunctionalTests.Web;
using System.Net.Http;
using System.Threading.Tasks;
using Xunit;

namespace Microsoft.eShopWeb.FunctionalTests.WebRazorPages;
[Collection("Sequential")]
public class HomePageOnGet : IClassFixture<WebTestFixture>
{
  public HomePageOnGet(WebTestFixture factory)
  {
    Client = factory.CreateClient();
  }

  public HttpClient Client { get; }

  [Fact]
  public async Task ReturnsHomePageWithProductListing()
  {
    // Arrange & Act
    var response = await Client.GetAsync("/");
    response.EnsureSuccessStatusCode();
    var stringResponse = await response.Content.ReadAsStringAsync();

    // Assert
    Assert.Contains(".NET Bot Black Sweatshirt", stringResponse);
  }
}

이 기능 테스트는 모든 미들웨어, 필터 및 바인더를 포함하여 전체 ASP.NET Core MVC/Razor Pages 애플리케이션 스택을 실행합니다. 지정된 경로("/")가 예상된 성공 상태 코드 및 HTML 출력을 반환하는지 확인합니다. 실제 웹 서버를 설정하지 않고도 이 작업을 수행하며 테스트에 실제 웹 서버를 사용하면 발생할 수 있는 많은 취약성을 방지합니다(예: 방화벽 설정 문제). TestServer에 대해 실행되는 기능 테스트는 일반적으로 통합 및 단위 테스트보다 느리지만 네트워크를 통해 테스트 웹 서버로 실행되는 테스트보다 훨씬 빠릅니다. 기능 테스트를 사용하여 애플리케이션의 프런트 엔드 스택이 예상대로 작동하는지 확인합니다. 이러한 테스트는 컨트롤러 또는 페이지에서 중복을 찾고 필터를 추가하여 중복 문제를 해결할 때 특히 유용합니다. 이상적으로, 이번 리팩터링은 애플리케이션의 동작을 변경하지 않을 것이며, 이를 확인하기 위해 일련의 기능 테스트가 수행됩니다.

참조 – ASP.NET Core MVC 앱 테스트