我的视图模型的单元测试

本文关键字:单元测试 模型 视图 我的 | 更新日期: 2023-09-27 18:36:50

我是单元测试的新手,我真的被困在 atm 上,所以我真的可以使用一些帮助。

一些应用程序信息
我在 MVVM 中有一个 WPF 应用程序。它从数据库(通过 .edmx 生成的类)获取数据。
所有 linq 查询都由数据库类中的方法处理。在 CustomerListViewModel 中,它列出了要在 CustomerListView 中显示的所有客户。

我的问题
我是单元测试的新手。我已经读过它并试图让它工作。但据我了解,它应该/可以在不接触数据库的情况下完成。我试图找到尽可能多的信息,但它不适用于我所拥有的。现在我基本上被困住了。

我的问题如何对这段代码进行单元测试?如何知道是否已成功查询数据库(在单元测试中是否触及数据库)?
(如果我理解了这篇文章,我可以自己弄清楚其余的类和方法)

代码
客户列表视图模型:

public CustomerListViewModel()
{
    MyObservableCollection<Customer> listCustomers = new MyObservableCollection<Customer>();
    ListCustomers = App.Database.GetCustomerList();
}
private void GetListCustomers()
{
    ListCustomers = App.Database.GetCustomerList();
    if (App.Database.hasError)
        App.Messenger.NotifyColleagues("SetStatus", App.Database.errorMessage);
}

数据库:

public MyObservableCollection<Customer> GetCustomerList()
{
    hasError = false;
    MyObservableCollection<Customer> customerList = new MyObservableCollection<Customer>();
    try
    {
        QRM_Entities dc = new QRM_Entities();
        var query =
            from customers in dc.Customer
            select customers;
        foreach (Customer cust in query)
        {
            customerList.Add(cust);
        }
    }
    catch (Exception ex)
    {
        errorMessage = "GetCustomerList() error, " + ex.Message;
        hasError = true;
    }
    return customerList;
}

我的视图模型的单元测试

您当前设置ViewModel的方式将使单元测试几乎不可能。问题出在这一行上:

ListCustomers = App.Database.GetCustomerList();

我认为App是静态的,Database是您用作数据访问层的类。因此,每次调用CustomerListViewModel的构造函数时,您都会调用App的实际Static实现,您必须在创建视图模型之前设置这些实现,这意味着您将始终使用实际的数据库进行测试,这显然是您试图绕过的。

在我最喜欢的软件原理中,依赖反转原理,其前提是解耦模块,以便您的高级模块依赖于较低级别模块的抽象。而这些细节应该取决于这种抽象。实际上,您应该开发到一个接口,并将此接口提供给依赖项。

以您的示例为例,我将为您的数据库交互提取接口并将其提供给您的视图模型,但我会更进一步,将其提供给将提供给您的视图模型的模型。

IDatabase:

public interface IDatabase
{
    IEnumerable<ICustomer> GetCustomerList();
}

ICustomerListModel:

public interface ICustomerListModel
{
    ObservableCollection<ICustomer> Customers
    {
        get;
    }
}

客户列表模型

public class CustomerListModel : ICustomerListModel
{
    private readonly IDatabase database;
    private readonly ObservableCollection<ICustomer> customers;
    public CustomerListModel(IDatabase database)
    {
        this.database = database;
        this.customers = new ObservableCollection(database.GetCustomerList());
    }
    public ObservableCollection<ICustomer> Customers
    {
        get
        {
            return this.customers;
        }
    }
}

客户列表视图模型

public class CustomerListViewModel
{
    private readonly ICustomerListModel customerListModel;
    public CusomterListViewModel(ICustomerListModel customerListModel)
    {
        this.customerListModel = customerListModel;
    }
    public ObservableCollection<ICustomer> Customers
    {
        get
        {
            return this.customerListModel.Customers;
        }
    }
}

所以你可以在这里看到的是,我已经为我请求信息的数据库提取了一个接口,这意味着我不关心IDatabase的实现,我刚才在我调用GetCustomerList()时它为我提供了ICustomer的集合。

因此,我将IDatabase的副本injectCusomterListModel类中,然后我可以查询该副本,知道我会正确获得我想要的东西。然后,我将ICustomerListModel注入ICustomerListViewModel,以便将收藏品呈现给View

因此,为了测试CustomerListModel,我将进行如下测试:

[Fact]
public void Customers_IsCorrectlyInitialisedAtStartup_Test()
{
    var databaseMock = new Mock<IDatabse>();
    var customer = new Customer();
    var customers = new [] { customer };
    databaseMock.Setup(mock => mock.GetCustomerList())
        .Returns(customers);
    var sut = new CustomerListModel(databaseMock.Object);
    Assert.Equal(customers, sut.Customers);
}

在这篇文章中,我嘲笑了IDatabase的一个版本,现在你可以看到我如何在CustomerListModel版本中不关心我有什么IDatabase只要我能称之为GetCustomerList()。这有一个设置,用于在调用GetCustomerList()时返回ICustomer。最后,我断言 Customers 集合已正确填充了IDatabase调用的返回。

单元测试是一门艺术,一开始很难理解,但是当你一开始让它工作时,你会很快学会它。您可能希望查看一些内容,以帮助您生成单元可测试代码并实际测试:

  • 坚实的原则,每个软件工程师至少应该熟悉的原则,将有助于生成可测试的代码。
  • 依赖注入,
  • 关于依赖注入的维基页面,概述了将依赖注入构造函数的利弊,何时以及如何在进一步的示例中使用它。
  • Moq,一个友好且易于使用的 C# 模拟框架,我将其用作上面示例的一部分。