【问题标题】:Writing unit-test using EF6 (Entity Framework 6)使用 EF6(实体框架 6)编写单元测试
【发布时间】:2018-03-27 23:14:08
【问题描述】:

我有一个使用 .NET Framework 4.6.1 和 EF6 的 ASP.NET Core 项目。 现在我想编写一些单元测试,并且已经花费了数小时来配置内存中的 SQLite 数据库以使用 EF6。但它不起作用。

所以,问题是如何在没有任何模拟(而不是内存数据库)的情况下使用 EF6 测试我的项目?

我当前的代码:

public class DataAccessLayer : DbContext
{
  public DataAccessLayer(string connectionString)
     : base(connectionString) {
  }

  public DataAccessLayer(DbConnection connection)
     : base(connection, true) {
  }

  public DbSet<User> Users { get; set; }

  public DbSet<Setting> Settings { get; set; }

  public DbSet<UserRole> UserRoles { get; set; }

  public DbSet<MainKey> MainKeys { get; set; }
}

[Table("Users")]
public class User
{
  [Key]
  [Required]
  public int UserID { get; set; }

  [Required][StringLength(50)]
  public string UserName { get; set; }

  ...
}

public class Testbase
{
  protected DataAccessLayer Context { get; private set; }

  [TestInitialize]
  public virtual void SetUp()
  {
     var connection = this.CreateConnection();
     connection.Open();
     this.Context = new DataAccessLayer(connection);
     this.Context.Database.CreateIfNotExists();
  }

  private SQLiteConnection CreateConnection() {
     var connectionStringBuilder = new SQLiteConnectionStringBuilder { DataSource = ":memory:" };
     return new SQLiteConnection(connectionStringBuilder.ToString());
  }
}

如果我尝试添加用户,我会收到以下错误:

System.Data.SQLite.SQLiteException:SQL 逻辑错误或缺少数据库 没有这样的表:用户。

假设我的表格是通过调用this.Context.Database.CreateIfNotExists(); 生成的,还是我弄错了?

【问题讨论】:

  • 我知道它不能回答你的问题,但你应该考虑github.com/tamasflamich/effort
  • FWIW,内存数据库或否,这不是单元测试;这是一个集成测试。在这方面,您确实应该使用与您的实际站点运行相同的数据库平台,即类似 SQL Server,而不是 SQLite。
  • 这些不是单元测试。对于单元测试,您可能希望模拟 db 等依赖项。

标签: c# entity-framework sqlite unit-testing asp.net-core


【解决方案1】:

考虑使用 Nuget 包Effort

这是一个用于单元测试的简单快速的内存数据库。

您可以从一个空数据库开始,然后使用数据库播种器自行填充,也可以使用测试 CSV 文件中的值填充它。

Tutorials Effort - Entity Framework Unit Testing Tool

带有博客和帖子的数据库的简单示例。博客和帖子之间的一对多关系

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

    public virtual ICollection<Post> Posts { get; set; } 
}

public class Post
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public int BlogId { get; set; }
    public virtual Blog Blog { get; set; } 
}

public class BloggingContext : DbContext
{
    public BloggingContext() : base() { } // constructor using config file

    public BloggingContext(string nameOrConnectionString) : base(nameOrConnectionString) { }
    public BloggingContext(DbConnection connection) : base(connection, true) { }
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }
}

你不会得到一个数据库的连接字符串,而是你得到一个 DbConnection 到它。因此 BloggingContext 的第三个构造函数。

在此构造函数中传递给超类的布尔值是告诉 DbContext 它拥有该连接:DbContext 应该在连接被 Disposed 时关闭并释放连接。

这是与您正常使用 DbContext 的唯一区别。对 DbContext 和 DbSets 的所有其他调用都是正常的。

例子

static void Main(string[] args)
{
    var connection = Effort.DbConnectionFactory.CreateTransient();

    using (var dbContext = new BloggingContext(connection))
    {
        var addedBlog = dbContext.Blogs.Add(new Blog[]
        {
            Name = "1",
            Posts = new Post[]
            { 
                new Post() {Title = "1st", Content = "a"},
                new Post() {Title = "2nd", Content = "b"},
                new Post() {Title = "3rd", Content = "c"},
            },
        });
        dbContext.SaveChanges();
    }

    using (var dbContext = new BloggingContext(connection))
    {
        var allPosts = context.Posts.ToList();
        foreach (var post in allPosts)
        {
            Console.WriteLine($"{post.Id}: {post.Title}");
        }
    }

一个提示:在开发过程中,有时很难看出测试失败是因为不正确的测试(数据)还是因为正在测试的代码不正确。在调试期间检查数据库中的内容是相当困难的。因此,我倾向于使用充满测试值的真实数据库来开发测试,一旦很少需要调试测试,就切换到内存数据库。事实上,对我来说这是第二个或第三个 DbContext 构造函数之间的切换

【讨论】:

  • 用这么简单的代码行让我赞叹不已!它现在可以工作了:)谢谢!
猜你喜欢
  • 2017-07-03
  • 1970-01-01
  • 1970-01-01
  • 2023-04-09
  • 2016-12-15
  • 1970-01-01
  • 2013-10-17
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多