entity-framework - 单元测试 EF - 如何从 BL 中提取 EF 代码?

标签 entity-framework unit-testing

我已经阅读了关于一件事的这么多(数十篇文章):

如何对其中包含 Entity Framework 代码的业务逻辑代码进行单元测试。

我有一个 3 层的 WCF 服务:

  • 服务层
  • 业务逻辑层
  • 数据访问层

我的业务逻辑将 DbContext 用于所有数据库操作。 我所有的实体现在都是 POCO(以前是 ObjectContext,但我改变了它)。

我已经阅读了 Ladislav Mrnka 的 回答 herehere为什么我们应该模拟\伪造DbContext

他说: “这就是为什么我认为处理上下文/Linq-to-entities 的代码应该包含集成测试并针对真实数据库工作的原因。”

和: “当然,您的方法在某些情况下有效,但单元测试策略必须在所有情况下都有效——要使其有效,您必须将 EF 和 IQueryable 完全从您的测试方法中移走。”

我的问题是 - 你是如何做到这一点的???

public class TaskManager
{
    public void UpdateTaskStatus(
        Guid loggedInUserId,
        Guid clientId,
        Guid taskId,
        Guid chosenOptionId,
        Boolean isTaskCompleted,
        String notes,
        Byte[] rowVersion
    )
    {
        using (TransactionScope ts = new TransactionScope())
        {
            using (CloseDBEntities entities = new CloseDBEntities())
            {
                User currentUser = entities.Users.SingleOrDefault(us => us.Id == loggedInUserId);
                if (currentUser == null)
                    throw new Exception("Logged user does not exist in the system.");

                // Locate the task that is attached to this client
                ClientTaskStatus taskStatus = entities.ClientTaskStatuses.SingleOrDefault(p => p.TaskId == taskId && p.Visit.ClientId == clientId);
                if (taskStatus == null)
                    throw new Exception("Could not find this task for the client in the database.");

                if (taskStatus.Visit.CustomerRepId.HasValue == false)
                    throw new Exception("No customer rep is assigned to the client yet.");
                TaskOption option = entities.TaskOptions.SingleOrDefault(op => op.Id == optionId);
                if (option == null)
                    throw new Exception("The chosen option was not found in the database.");

                if (taskStatus.RowVersion != rowVersion)
                    throw new Exception("The task was updated by someone else. Please refresh the information and try again.");

                taskStatus.ChosenOptionId = optionId;
                taskStatus.IsCompleted = isTaskCompleted;
                taskStatus.Notes = notes;

                // Save changes to database
                entities.SaveChanges();
            }

            // Complete the transaction scope
            ts.Complete();
        }
    }
}

在附加的代码中,有一个来 self 的业务逻辑的功能演示。 该函数有几次到数据库的“旅行”。 我不明白如何才能将 EF 代码从这个函数中剥离到一个单独的程序集中,以便我能够单元测试这个函数(通过注入(inject)一些假数据而不是 EF 数据),并集成测试包含“EF 函数”的程序集。

Ladislav 或其他人可以帮忙吗?

[编辑]

这是我的业务逻辑中的另一个代码示例,我不明白如何从我的测试方法中“移动 EF 和 IQueryable 代码”:

public List<UserDto> GetUsersByFilters(
    String ssn, 
    List<Guid> orderIds, 
    List<MaritalStatusEnum> maritalStatuses, 
    String name, 
    int age
)
{
    using (MyProjEntities entities = new MyProjEntities())
    {
        IQueryable<User> users = entities.Users;

        // Filter By SSN (check if the user's ssn matches)
        if (String.IsNullOrEmusy(ssn) == false)
            users = users.Where(us => us.SSN == ssn);

        // Filter By Orders (check fi the user has all the orders in the list)
        if (orderIds != null)
            users = users.Where(us => UserContainsAllOrders(us, orderIds));

        // Filter By Marital Status (check if the user has a marital status that is in the filter list)
        if (maritalStatuses != null)
            users = users.Where(pt => maritalStatuses.Contains((MaritalStatusEnum)us.MaritalStatus));

        // Filter By Name (check if the user's name matches)
        if (String.IsNullOrEmusy(name) == false)
            users = users.Where(us => us.name == name);

        // Filter By Age (check if the user's age matches)
        if (age > 0)
            users = users.Where(us => us.Age == age);


        return users.ToList();
    }
}

private   Boolean   UserContainsAllOrders(User user, List<Guid> orderIds)
{
    return orderIds.All(orderId => user.Orders.Any(order => order.Id == orderId));
}

最佳答案

如果您想对您的 TaskManager 类进行单元测试,您应该使用Repository 设计模式 并将诸如 UserRepository 或 ClientTaskStatusRepository 之类的存储库注入(inject)到这个类。然后,您将使用这些存储库并调用它们的方法,而不是构造 CloseDBEntities 对象,例如:

User currentUser = userRepository.GetUser(loggedInUserId);
ClientTaskStatus taskStatus = 
    clientTaskStatusRepository.GetTaskStatus(taskId, clientId);

如果您想集成测试您的TaskManager 类,解决方案要简单得多。您只需要使用指向测试数据库的连接字符串初始化 CloseDBEntities 对象即可。实现此目的的一种方法是将 CloseDBEntities 对象注入(inject)到 TaskManager 类中。

您还需要在每次集成测试运行之前重新创建测试数据库,并用一些测试数据填充它。这可以使用 Database Initializer 来实现.

关于entity-framework - 单元测试 EF - 如何从 BL 中提取 EF 代码?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10940873/

相关文章:

python - 如何覆盖 nosetest 拆解功能?

java - 如何在HTTP客户端apache上编写测试?

c# - 大型团队如何使用数据库

c# - 无法在 Entity Framework 6 中一对一映射

c# - 按实体查询(示例)

.net - EF6 事务回滚

unit-testing - 是否有可能找出 TFS 上选定个人 checkin 的变更集的单元测试代码覆盖率?

java - 为 jOOQ 单元测试创​​建一个类似对象的任意表

c# - DbContext.SaveChanges() 没有更新数据库

c# - 是否有一种标准方法可以对依赖项不实现接口(interface)的代码进行单元测试?