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


81

단위 테스트에 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; }
}

답변:


110

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

_context.Database.EnsureDeleted();

감사합니다. 제 문제가 해결되었습니다. 나는 optionsBuilder.UseInMemoryDatabase (persist : false);를 시도했습니다. EFCore 제거하고 여기에 테스트간에 다른 컨텍스트를 갖는 또 다른 가능한 해결책을 우연히 발견 된 : docs.efproject.net/en/latest/miscellaneous/testing.html 비록 테스트 루트 조성물에 대해 선택된 답의 단순함을 선호
매트 샌더스

22
이것은 메모리 내 데이터베이스의 식별 열을 재설정하는 것으로 보이지 않습니다. 따라서 데이터에 행을 시드하는 경우 첫 번째 테스트는 ID 1, 두 번째 테스트 2 등의 행을 보게됩니다. 이것은 의도적으로 설계된 것입니까?
ssmith

4
2019 년이고 데이터베이스를 삭제하고 다시 생성 한 후에도 이드가 지속되는 문제는 여전히 문제입니다!
Tom

좋은. 이것은 내 문제를 해결했습니다! 내 오류는 내 테스트가 병렬로 실행되고 있다고 생각했지만 실제로는 메모리 내 데이터베이스가 제대로 지워지지 않았다는 것입니다.
Thorkil Værge

아래의 R4nc1d 답변도 사용하는 것이 좋습니다. 메모리에서 데이터베이스를 제거하고 새로운 식별 열을 사용하여 매번 새 데이터베이스를 얻도록합니다. 그냥 사용 [TearDown](즉, 실행 각 테스트 후).
CularBytes

34

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

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

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

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

_context.Database.EnsureDeleted();

모든 테스트에서


7
여전히 기억에 남지 않습니까?
Sander

1
예, 메모리에 살겠지만 using 문으로 컨텍스트를 래핑하면 자동으로 삭제됩니다.
R4nc1d

1
상위 답변에 추가로, 이것은 식별 열 재설정을 해결하는 데 사용할 수 있습니다. 나는 여전히을 사용할 것이고 EnsureDeleted, [TearDown]각 테스트 후에 실행될 방법 에 이것을 한 번만 추가하면 되므로 많은 고통이 없습니다.
CularBytes

8

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

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

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

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

InMemoryDatabaseRoot의 경우 +1입니다. 그러나 TestCleanup을 사용하고 컨텍스트를 null로 설정하고 각 TestInitialize에서 새 컨텍스트를 다시 생성 (동일한 데이터베이스 이름을 사용하고 InMemoryDatabaseRoot를 사용하지 않는다고 가정)하면 동일한 메모리 내 데이터베이스가 제공됩니다.
bobwah

3

나는 두 가지 대답의 조합으로 갈 것입니다. 테스트가 병렬로 실행되는 경우 다른 테스트를 실행하는 동안 데이터베이스가 삭제 될 수 있으므로 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();
  }
}

2

나는 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());
    }
}

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



0

다음은 각 단위 테스트를 서로 격리하기위한 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();
            }
      
    }
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.