데이터베이스 의존성을 지닌.NET Core 애플리케이션 통합 테스트

카탈로그
소개하다.
배경.
코드 사용
취미
간략하게 설명했습니다.NET Core 데이터베이스 테스트의 문제점그런 다음 GitHub의 구체적인 코드 예제를 통해 솔루션을 설명합니다.
소개하다.
데이터베이스 의존성을 가진 응용 프로그램에 대해 자동 테스트를 하는 것은 어려운 작업이다.데이터베이스가 완전히 시뮬레이션할 수 있는 것이 아니기 때문에 단원 테스트는 당신에게 도움이 되지 않을 것입니다.업데이트, delete, insert를 만들면, 검색 후 select 검색을 실행해서 검색 결과를 검사할 수 있지만, 필요하지 않은 부작용을 검사하지 않습니다.영향을 받을 수 있는 시계가 필요한 것보다 많거나, 실행하는 조회가 필요한 것보다 많다.이것은 이 문제들의 해결 방안이다.
배경.
TDD for를 보유합니다.NET Core의 경험은 도움이 될 수 있고 xUnit의 경험이 가장 좋고 EF Core의 경험이 도움이 될 수 있습니다.
코드 사용
우선, 이것은 테스트할 코드입니다.주입할 데이터베이스 상하문 의존 관계와 데이터를 데이터베이스에 저장하는 방법이 존재한다.추가하고 저장한 실체를 방법의 출력으로 되돌려줍니다.
public class TodoRepository : ITodoRepository
{
   private readonly ProjectContext _projectContext;

   public TodoRepository(ProjectContext projectContext)
   {
        _projectContext = projectContext;
   }

   public async Task SaveItem(TodoItem item)
   {
       var newItem = new Entities.TodoItem()
       {
            To do = item.Todo
       };
       _projectContext.TodoItems.Add(newItem);
       await _projectContext.SaveChangesAsync();
       return newItem;
   }
}

논리적으로 이 의존 관계는 정확한 해결이 필요하다.Startup 클래스에는 이러한 용도로 사용할 수 있는 방법이 있습니다.위에서 설명한 메모리 라이브러리 클래스는 데이터베이스 상하문과 컨트롤러에 의존하는 것처럼 여기에 추가됩니다.
public void ConfigureServices(IServiceCollection services)
{
   services.AddControllers();
   services.AddDbContext(options =>
   {
       var connectionString = Configuration["ConnectionString"];
       options.UseSqlite(connectionString,
       sqlOptions =>
       {
            sqlOptions.MigrationsAssembly
               (typeof(Startup).GetTypeInfo().Assembly.GetName().Name);
       });
   });
   services.AddTransient();
}

테스트할 의존 관계를 해석할 수 있지만, 현재 우리는 의존 관계를 테스트 목적에 사용해야 한다.이러한 테스트는 다음과 같습니다.
public class TodoRepositoryTest : TestBase
{
    private ITodoRepository _todoRepository;

    private readonly List _entityChanges =
            new List();

    public TodoRepositoryTest(WebApplicationFactory webApplicationFactory) : 
            base(webApplicationFactory, @"Data Source=../../../../project3.db")
    {
    }

    [Fact]
    public async Task SaveItemTest()
    {
        // arrange
        var todoItem = new TodoItem()
        {
            To do = "TestItem"
        };
            
        // act
        var savedEntity = await _todoRepository.SaveItem(todoItem);

        // assert
        Assert.NotNull(savedEntity);
        Assert.NotEqual(0, savedEntity.Id);
        Assert.Equal(todoItem.Todo, savedEntity.Todo);
        var onlyAddedItem = _entityChanges.Single();
        Assert.Equal(EntityState.Added,onlyAddedItem.EntityState);
        var addedEntity = (Database.Entities.TodoItem)onlyAddedItem.Entity;
        Assert.Equal(addedEntity.Id, savedEntity.Id);
    }

    public override void AddEntityChange(object newEntity, EntityState entityState)
    {
        _entityChanges.Add((newEntity, entityState));
    }

    protected override void SetTestInstance(ITodoRepository testInstance)
    {
        _todoRepository = testInstance;
    }
}

클래스에는 다음과 같은 메서드와 변수가 있습니다.
  • _todoRepository: 테스트할 인스턴스
  • _entityChanges: 성명할 entitychanges(변경된 종류, 예를 들어 추가/갱신된 내용과 실체 자체)
  • SaveItemTest: 실제 작업을 완료하는 테스트 방법입니다.이것은 방법 파라미터를 만들고 이 방법을 호출한 다음에 모든 관련 내용을 단언합니다. 만약에 메인 키에 값을 분배했다면, 실제적으로 하나의 실체만 변경되었을 때, 이 실체가 변경된 것이 확실히 증가되었을 뿐만 아니라, 만약 추가된 실체가 우리가 원하는 유형을 가지고 있다면.우리는 이 단언을 한 후에 선택 검색을 실행하지 않았다.이것은 다른 방법을 통해 테스트를 실행할 때, 우리는 모든 실체의 변경 사항만 받아들일 수 있기 때문일 수 있다.
  • AddEntityChange: 이것은 방금 언급한 또 다른 방법입니다.그것은 실체 자체를 포함하는 모든 실체 변경 사항을 받아들인다.
  • SetTestInstancetodoRepository의 테스트 실례는 이 방법을 통해 설정해야 한다.

  • 모든 템플릿 코드가 있는 기본 클래스에서 이 SetTestInstance 메서드를 호출하여 데이터베이스 통합 테스트를 설정합니다.기본 클래스:
    public abstract class TestBase : IDisposable, ITestContext, 
                    IClassFixture>
    {
        protected readonly HttpClient HttpClient;
    
        protected TestBase(WebApplicationFactory webApplicationFactory,
                           string newConnectionString)
        {
            HttpClient = webApplicationFactory.WithWebHostBuilder(whb =>
            {
                whb.ConfigureAppConfiguration((context, configbuilder) =>
                {
                    configbuilder.AddInMemoryCollection(new Dictionary
                    {
                            {"ConnectionString", newConnectionString}
                    });
                });
                whb.ConfigureTestServices(sc =>
                {
                    sc.AddSingleton(this);
                    ReplaceDbContext(sc, newConnectionString);
                    var scope = sc.BuildServiceProvider().CreateScope();
                    var testInstance = scope.ServiceProvider.GetService();
                    SetTestInstance(testInstance);
                 });
             }).CreateClient();
         }
    
         public void Dispose()
         {
             Dispose(true);
             GC.SuppressFinalize(this);
         }
    
         public abstract void AddEntityChange(object newEntity, EntityState entityState);
    
         private void ReplaceDbContext(IServiceCollection serviceCollection, 
                                       string newConnectionString)
         {
             var serviceDescriptor =
                 serviceCollection.FirstOrDefault
                       (descriptor => descriptor.ServiceType == typeof(ProjectContext));
             serviceCollection.Remove(serviceDescriptor);
             serviceCollection.AddDbContext();
         }
    
         protected abstract void SetTestInstance(TTestType testInstance);
    
         protected virtual void Dispose(bool disposing)
         {
             if (disposing) HttpClient.Dispose();
         }
    }

    기류의 가장 중요한 부분은 구조 함수다.xUnit에서 테스트의 초기화는 일반적으로 구조 함수에서 완성됩니다.일단 정확하게 완성하면 쉽게 테스트를 진행할 수 있다.여기서 가장 중요한 방법은 다음과 같습니다.
  • AddInMemoryCollection: 이 예에서는 연결 문자열의 특정 테스트 구성 매개변수를 설정합니다.
  • AddSingleton: 데이터베이스 상하문에서 업데이트를 얻기 위해 테스트 자체가 하나의 예로 해석됩니다.
  • ReplaceDbContext: 기존 데이터베이스 컨텍스트를 상속된 데이터베이스 컨텍스트로 대체하여 기능을 확장하고 테스트를 업데이트할 수 있습니다.
  • CreateClient: Program 클래스와 Startup 클래스의 코드를 트리거하는 방법 호출.
  • GetService: 이 메서드 호출을 사용하여 테스트 메서드를 호출한 인스턴스를 분석해야 합니다.Program 클래스와 Startup 클래스의 코드를 트리거할 수 있기 때문에 가능합니다.
  • SetTestInstance: 이 방법을 호출하여 테스트 방법을 호출하는 실례를 설정해야 합니다.

  • 여기(TestProjectContext)에 새로운 의존 관계를 도입했기 때문에 이 의존 관계를 실현해야 한다.
    public class TestProjectContext : ProjectContext
    {
       private readonly ITestContext _testContext;
    
       public TestProjectContext(DbContextOptions options, 
                                 ITestContext testContext) : base(options)
       {
            _testContext = testContext;
       }
    
       public override async Task SaveChangesAsync
                       (CancellationToken cancellationToken = new CancellationToken())
       {
            Action updateEntityChanges = () => { };
            var entries = ChangeTracker.Entries();
            foreach (var entry in entries)
            {
                 var state = entry.State;
                 updateEntityChanges += () => _testContext.AddEntityChange(entry.Entity, state);
            }
    
            var result = await base.SaveChangesAsync(cancellationToken);
            updateEntityChanges();
            return result;
        }
    }

    매번 실체 변경 사항을 저장할 때마다 (이 프로그램에서는SaveChangesAsync에서 수행합니다) 변경 사항은 변경 사항을 데이터베이스에 저장한 후에 호출됩니다.이렇게 하면, 우리의 테스트 클래스는 항상 단언된 저장된 변경 사항을 받을 것입니다.이제 테스트 문제가 해결되었습니다.전체 코드는 GiHub에 있습니다.
    취미
    나는 내가 발견한 이런 업무 방식을 정말 좋아한다.샘플 코드를 작성하는 것은 매우 번거롭지만, 이것은 일회용 작업이다.Entity Framework Core 3.1을 사용하는 각 데이터베이스 테스트에서 이 코드를 재사용할 수 있습니다.나는 테스트가 필요한 모든 것을 테스트할 수 있다.완성된 업데이트, insert와 delete, 영향을 받은 실체와 변경된 실체의 총수도 의미가 있습니다.select를 테스트한 후, 어떤 조회도 실행하지 않아도 모든 작업을 완성할 수 있습니다.

    좋은 웹페이지 즐겨찾기