C#Net Core 3.1单元测试中自动生成标识列

C#Net Core 3.1单元测试中自动生成标识列,c#,.net-core,integration-testing,xunit.net,in-memory-database,C#,.net Core,Integration Testing,Xunit.net,In Memory Database,我的基础课 public class Book { public int Id { get; set;} public string Title { get; set;} } public class Author { public Author () { BookList = new HashSet<Book> (); for (int i = 0; i<

我的基础课

    public class Book {
        public int Id { get; set;}
        public string Title { get; set;} 
    }
    public class Author {
        public Author () {
            BookList = new HashSet<Book> ();
            for (int i = 0; i< 10; i++) {
                var book = new Book() {
                    Title = "Funny book" + i;
                }
                BookList.Add(book);
            }
        }
        public int Id { get; set;}
        public string Name { get; set;}
        public ICollection<Book> BookList { get; set; }
    }
在单元测试方法中,如果我没有为book对象指定Id,它将抛出一个错误,表示Id=1000的对象已经被另一个实体跟踪

我的理由如下:

(1) 当我创建Author测试数据时,它为每个作者创建了许多书籍。由于字段是SQL表的标识列,因此创建这些书籍时未指定Id

(2) 然后,我用指定的Id创建了图书测试数据。已成功创建测试数据

(3) 在单元测试期间,系统似乎对获取下一个可用Id感到困惑

有没有一种方法可以在不指定Id的情况下解决此问题


单元测试在netcore 2.1中运行良好。单元测试转换为net core 3.1后,问题就出现了。

如果您将Book类的定义更改为类似以下内容:

public class Book {
    public string Id { get; set;}
    public string Title { get; set;} 
}
您可以通过使用以下命令动态生成新guid来添加其他书籍:

Guid g = Guid.NewGuid();

然后,当您创建新书时,只需调用g.ToString()来获取基础值并将其分配给Id属性。当然,您还必须更改后端数据库表的结构以支持此功能。

想想这是如何工作的。。。。您正在告诉您的系统创建一本id为1000的书。运行方法后,数据库中现在有一本id为1000的书。然后再次运行该方法,结果失败,因为显然,id为1000的书籍仍在数据库中

接下来,您模拟您的数据库层并创建一个ID为1000的实体,但您永远无法摆脱它。下次运行方法时,可能会遇到实体跟踪问题

这个你称之为测试的东西,实际上不是一个测试,因为没有断言,你没有检查任何东西,因此它不是一个测试

这一切都非常复杂,必然会产生拉扯头发的问题。您试图做的事情告诉我,您真正想要的是集成测试,而不是单元测试

如果你想进行单元测试,我会说把EF从测试中去掉,把重点放在真正的功能上,比如业务规则等等

进行集成测试并不是一件坏事,但如果您在调用结束时创建了真实的数据,您还应该清理它

实际的集成测试如下所示:

  • 在db中创建测试手册
  • 在预期数据和实际数据之间使用适当的Assert语句,发出get并检查数据是否完全符合预期
  • 因此:

  • 用1000创建一本书
  • 检查通话结果
  • 从数据库加载id为1000的书籍
  • 使用断言检查您关心的事情
  • 从数据库中删除id为1000的书籍和所有相关数据
  • 这是集成测试的正常流程

    通常,在创建数据时,不应该告诉数据库要使用什么id,而是让它插入记录并告诉您唯一id是什么。这将解决许多问题,包括同时进行多个呼叫。不要在列上使用max函数计算出下一个id,因为它存在并发性问题

    根据您的系统,最好在db中将您的id字段标记为identity和auto increment,这样您的ORM就可以看到这一点,并且您的图层也会发生变化,所以现在您在尝试创建某些内容时不需要指定id

    但是,在某些情况下,您必须指定一个ID,在这种情况下,一个选项是使用像uniqueidentifier这样的东西,特别是GUID。那辆车相撞的可能性很小

    我会说,想想你想测试什么。是的,你可以模仿你的回购协议等,但如果你模仿他们,那么你在测试什么?如果您想测试您的项目是否正确创建,那么您需要一直访问数据库,因为没有模拟可以帮助您实现这一点。您也可以使用内存中的数据库,以加快速度,而不是实际接触真实的数据库

    但事实上,单元测试是关于功能、检查数据转换等的。模拟越少越好,它使测试具有可维护性,尤其是在重构代码时

    此外,如果可以的话,尽量避免使用ORM,以功能性的方式思考问题。
    这是一些数据,我调用这个方法,我应该得到这个结果。这将适用于单元测试。

    在我的测试中创建一本书时,我正在使用一个实用程序函数来分配Id。我试图在不指定Id的情况下找到解决方案。@user3097695通过修改该函数将Id指定为Guid.NewGuid()方法调用的结果,您不需要手动指定Id,因为每次调用该方法时系统都会生成新的Guid。由于数据库已在生产中一段时间,我们无法更改Id的定义。实际的API调用没有问题,但它只影响单元测试。我需要找到这些行为背后的深层原因。好吧,也许你可以使用一个helper函数从数据库中选择ID的最大值,并简单地为新ID增加1?我已经在为单元测试做这个了。我认为NetCore 3.1中可能有一个bug。我认为我不需要添加Id。系统应该自动添加下一个可用Id。您有一个有效的点。我想我想问的是为什么NetCore 2.1和NetCore 3.1之间存在不同的行为。
    [Fact]
        public async Task CreateBookTest()
        {       
            var context = BookContextMock.ConfigureBookContext(new ServiceCollection());
            BookRepository repo = new BookRepository(context);
            Book book = new BookSensor()
            {
                Title = "New Book 1",
            };
            var newBook = await repo.CreateBook(book);
        }
    
    public class Book {
        public string Id { get; set;}
        public string Title { get; set;} 
    }
    
    Guid g = Guid.NewGuid();