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

Joh*_*ner 2 unit-testing entity-framework

关于一件事,我已经读了很多(数十篇文章):

如何对其中包含实体框架代码的业务逻辑代码进行单元测试。

我有3层的WCF服务:

  • 服务层
  • 业务逻辑层
  • 资料存取层

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

我已阅读拉吉斯拉夫Mrnka的答案在这里,并在这里 的原因,我们应该假\伪造的DbContext

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

并且: “当然,您的方法在某些情况下有效,但单元测试策略必须在所有情况下均有效-要使其起作用,必须将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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

随附的代码中演示了我的业务逻辑中的功能。该函数具有到数据库的多个“行程”。我不明白,我究竟怎么可以剥夺这个函数的代码EF出一个单独的程序,让我能够单元测试这个功能(通过注入一些假数据而不是EF数据),以及集成测试组装包含“ EF功能”。

拉迪斯拉夫或任何其他人可以帮忙吗?

[编辑]

这是业务逻辑中的另一个代码示例,我不明白如何从测试的方法中“删除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));
}
Run Code Online (Sandbox Code Playgroud)

Jur*_*hár 5

如果你想单元测试你的TaskManager类,你应该使用的库dessign模式,注入库如UserRepository或ClientTaskStatusRepository到这个类。然后,不用构造CloseDBEntities对象,您将使用这些存储库并调用其方法,例如:

User currentUser = userRepository.GetUser(loggedInUserId);
ClientTaskStatus taskStatus = 
    clientTaskStatusRepository.GetTaskStatus(taskId, clientId);
Run Code Online (Sandbox Code Playgroud)

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

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