[unit-testing] 단위 테스트간에 EF7 InMemory 공급자를 재설정하려면 어떻게해야합니까?

단위 테스트에 EF7 InMemory 공급자를 사용하려고하지만 테스트 간 InMemory 데이터베이스의 지속적인 특성으로 인해 문제가 발생합니다.

다음 코드는 내 문제를 보여줍니다. 한 테스트는 작동하고 다른 테스트는 항상 실패합니다. 테스트 사이에 _context를 null로 설정하더라도 두 번째 테스트 실행에는 항상 4 개의 레코드가 있습니다.

[TestClass]
public class UnitTest1
{

    private SchoolContext _context;

    [TestInitialize]
    public void Setup()
    {
        Random rng = new Random();

        var optionsBuilder = new DbContextOptionsBuilder<SchoolContext>();
        optionsBuilder.UseInMemoryDatabase();

        _context = new SchoolContext(optionsBuilder.Options);
        _context.Students.AddRange(
            new Student { Id = rng.Next(1,10000), Name = "Able" },
            new Student { Id = rng.Next(1,10000), Name = "Bob" }
        );
        _context.SaveChanges();
    }

    [TestCleanup]
    public void Cleanup()
    {
        _context = null;
    }

    [TestMethod]
    public void TestMethod1()
    {
        Assert.AreEqual(2, _context.Students.ToList().Count());
    }

    [TestMethod]
    public void TestMethod2()
    {
        Assert.AreEqual(2, _context.Students.ToList().Count());
    }

}

public class Student
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class SchoolContext : DbContext
{
    public SchoolContext(DbContextOptions options) : base(options) { }

    public DbSet<Student> Students { get; set; }
}



답변

다음 호출은 메모리 내 데이터 저장소를 지 웁니다.

_context.Database.EnsureDeleted();


답변

파티에 조금 늦었지만 나도 같은 문제에 부딪 쳤지 만 결국 내가했던 것은.

각 테스트에 다른 데이터베이스 이름을 지정합니다.

optionsBuilder.UseInMemoryDatabase(Guid.NewGuid().ToString());

이렇게하면 추가 할 필요가 없습니다.

_context.Database.EnsureDeleted();

모든 테스트에서


답변

DbContextOptionsBuilder의 코드 정의를 다음과 같이 변경하기 만하면됩니다.

        var databaseName = "DatabaseNameHere";
        var dbContextOption = new DbContextOptionsBuilder<SchoolContext>()
                                    .UseInMemoryDatabase(databaseName, new InMemoryDatabaseRoot())
                                    .Options;

new InMemoryDatabaseRoot () 는 Id의 지속 문제없이 새 데이터베이스를 만듭니다. 따라서 지금은 필요하지 않습니다.

       [TestCleanup]
       public void Cleanup()
       {
           _context = null;
       }


답변

나는 두 가지 대답의 조합으로 갈 것입니다. 테스트가 병렬로 실행되는 경우 다른 테스트를 실행하는 동안 데이터베이스가 삭제 될 수 있으므로 30 개 이상의 테스트를 실행할 때 산발적 인 오류가 발생했습니다.

임의의 db 이름을 지정하고 테스트가 완료되면 삭제되었는지 확인합니다.

public class MyRepositoryTests : IDisposable {
  private SchoolContext _context;

  [TestInitialize]
  public void Setup() {
    var options = new DbContextOptionsBuilder<ApplicationDbContext>()
      // Generate a random db name
      .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
      .Options;
      _context = new ApplicationDbContext(options);
  }

  [TestCleanup]
  public void Cleanup()
    _context.Database.EnsureDeleted(); // Remove from memory
    _context.Dispose();
  }
}


답변

나는 DbContext다음과 같은 고정물을 사용합니다

public class DbContextFixture
    where TDbContext : DbContext
{
    private readonly DbContextOptions _dbContextOptions =
        new DbContextOptionsBuilder()
            .UseInMemoryDatabase("_", new InMemoryDatabaseRoot())
            .Options;

    public TDbContext CreateDbContext()
    {
        return (TDbContext)(typeof(TDbContext)
            .GetConstructor(new[] { typeof(DbContextOptions) })
            .Invoke(new[] { _dbContextOptions }));
    }
}

이제 간단히 할 수 있습니다

public class MyRepositoryTests : IDisposable {
    private SchoolContext _context;
    private DbContextFixture<ApplicationDbContext> _dbContextFixture;

    [TestInitialize]
    public void Setup() {
        _dbContextFixture = new DbContextFixture<ApplicationDbContext>();
        _context = _dbContextFixture.CreateDbContext();
        _context.Students.AddRange(
            new Student { Id = rng.Next(1,10000), Name = "Able" },
            new Student { Id = rng.Next(1,10000), Name = "Bob" }
        );
        _context.SaveChanges();
    }

    [TestCleanup]
    public void Cleanup()
        _context.Dispose();
        _dbContextFixture = null;
    }

    [TestMethod]
    public void TestMethod1()
    {
        Assert.AreEqual(2, _context.Students.ToList().Count());
    }

    [TestMethod]
    public void TestMethod2()
    {
        Assert.AreEqual(2, _context.Students.ToList().Count());
    }
}

이 솔루션은 스레드로부터 안전합니다. 자세한 내용은 내 블로그 를 참조하십시오.


답변

여기에있는 예제는 RemoveRange를 통해이를 수행합니다. https://docs.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-3.1

db.<Entity>.RemoveRange(db.<entity>);


답변

다음은 각 단위 테스트를 서로 격리하기위한 2 센트 접근 방식입니다. C # 7, XUnit 및 EF core 3.1을 사용하고 있습니다.

샘플 TestFixture 클래스.

public class SampleIntegrationTestFixture : IDisposable
    {

        public DbContextOptionsBuilder<SampleDbContext> SetupInMemoryDatabase()
            => new DbContextOptionsBuilder<SampleDbContext>().UseInMemoryDatabase("MyInMemoryDatabase");

 private IEnumerable<Student> CreateStudentStub()
            => new List<Student>
            {
            new Student { Id = rng.Next(1,10000), Name = "Able" },
            new Student { Id = rng.Next(1,10000), Name = "Bob" }
            };

        public void Dispose()
        {
        }
   }

샘플 IntegrationTest 클래스

 public class SampleJobIntegrationTest : IClassFixture<SampleIntegrationTestFixture >
 {
    private DbContextOptionsBuilder<SampleDbContext> DbContextBuilder { get; }
    private SampleDbContext SampleDbContext { get; set; }

 public SampleJobIntegrationTest(SampleIntegrationTestFixture
 sampleIntegrationTestFixture )
  {
        SampleIntegrationTestFixture = sampleIntegrationTestFixture ;

        SampleDbContextBuilder = sampleIntegrationTestFixture .SetupInMemoryDatabase();
  }



  [Fact]
    public void TestMethod1()
    {
using(SampleDbContext = new SampleDbContext(SampleDbContextBuilder.Options))

        var students= SampleIntegrationTestFixture.CreateStudentStub();
            {
            SampleDbContext.Students.AddRange(students);

        SampleDbContext.SaveChanges();

  Assert.AreEqual(2, _context.Students.ToList().Count());

                SampleDbContext.Database.EnsureDeleted();
            }

    }