如何在Asp中使用带有子实体的数据模型的单元测试类。净MVC 3

时间:2022-11-30 16:32:25

In my web store app I have a "Cart" class that can add, remove, and compute total value of items. The data model is the folowing: 1 Item contains 1 Product and 1 Shipping. Product has Price field and Shipping has Cost field. Here is the Cart class code:

在我的web store应用程序中,我有一个“Cart”类,可以添加、删除和计算项目的总价值。数据模型为folowing: 1 Item包含1个产品,1 Shipping。产品有价格领域,航运有成本领域。这里是Cart类代码:

public class CartLine
{
    public Item Item { get; set; }
    public int Quantity { get; set; }
}

public class Cart
{
    private List<CartLine> lineCollection = new List<CartLine>();

    // methods: 
    // Add(Item item, int quantity) 
    // Remove(Item item)

    public decimal ComputeTotalProductValue()
    {
        return lineCollection.Sum(l => l.Item.Product.Price*l.Quantity);
    }

    // methods with the same principle: 
    // ComputeTotalShippingValue()
    // ComputeOveralValue()
}

And here is my unit test (that of course doesn't work):

这是我的单元测试(当然不起作用):

[TestMethod]
    public void Can_Compute_Total_Values()
    {
        // Arrange 
        var itemMock = new Mock<IItemsRepository>();
        itemMock.Setup(i => i.GetItems()).Returns(new[]
            {
                new Item { ItemId = 1, ProductId = 1, ShippingId = 1 },
                new Item { ItemId = 2, ProductId = 2, ShippingId = 2 }
            });

        var productMock = new Mock<IProductRepository>();
        productMock.Setup(p => p.GetProducts()).Returns(new[]
            {
                new Product { ProductId = 1, Price = 10 },
                new Product { ProductId = 2, Price = 20 }
            });

        var shippingMock = new Mock<IShippingRepository>();
        shippingMock.Setup(s => s.GetShippings()).Returns(new[]
            {
                new Shipping { ShippingId = 1, Cost = 2 },
                new Shipping { ShippingId = 2, Cost = 5 }
            });

        var item1 = itemMock.Object.GetItems().ToArray()[0];
        var item2 = itemMock.Object.GetItems().ToArray()[1];    

        var target = new Cart();

        //Act
        target.Add(item1, 2);
        target.Add(item2, 4);

        decimal totalProduct = target.ComputeTotalProductValue();
        decimal totalShipping = target.ComputeTotalShippingValue();
        decimal overalSum = target.ComputeOveralValue();

        // Assert
        Assert.AreEqual(totalProduct, 100);
        Assert.AreEqual(totalShipping, 24);
        Assert.AreEqual(overalSum, 124);
    }
}

The issue is probably associated with binding Items to Products and Shipping. How can I do this?

这个问题可能与将项目绑定到产品和运输有关。我该怎么做呢?

Thanks in advance!

提前谢谢!

2 个解决方案

#1


2  

The underlying problem here is that details about how you persist your model have leaked in to your model classes, and as a result classes that use your models (like Cart uses Item) have some obligation to understand your persistence layer, which they should not need to do. This is too much coupling, which is what makes your test hard.

这里的基本问题是,关于如何持久化模型的细节泄露给了模型类,因此使用模型的类(如Cart使用Item)有一些义务来理解持久化层,而它们不应该这样做。这是太多的耦合,这使得您的测试非常困难。

Consider redefining Item and adding an ItemDescription like so:

考虑重新定义项目并添加项目描述如下:

public class Item
{
   public Product Product {get;set;}
   public Shipping Shipping {get;set;}
}

public class ItemDescription
{
   public int ProductId {get;set;}
   public int ShippingId {get;set;}
}

This allows you to test the Cart, and other classes that use your Item class quite easily. I can add an example of that if you need it, but I'm guessing it will be pretty obvious to you. If you can't change Item for some reason, turn this approach upside down and create a FullyConsitituedItem class for your Cart to use.

这允许您测试购物车和其他使用您的项目类的类。如果你需要的话,我可以添加一个例子,但是我猜你会很容易理解。如果由于某些原因您不能更改项目,则将此方法颠倒过来,并创建一个完全可更改的Cart类以供您使用。

The cost here is that you have to create another class that contructs Items from ItemDescriptions, but you actually would have been doing that anyway, it would just have been done piecemeal all over the place. Adding ItemDescription makes it explicit, which is nicer and more readable. Certainly more testable.

这里的成本是,你必须创建另一个类来构造来自itemdescription的项目,但实际上你已经这么做了,它将会在各处零碎地完成。添加ItemDescription使其显式,更美观、可读性更好。当然更可测试的。

#2


0  

You need to directly assign Product and Shipping for item1 and item2 in test method.

您需要在测试方法中直接为item1和item2分配产品和发货。

See code example:

看到代码示例:

[TestMethod]
public void Can_Compute_Total_Values()
{
    // Arrange 
    var item1 = new Item {
        Product = new Product { Price = 10 },
        Shipping = new Shipping { Cost = 2 }
    };

    var item2 = new Item {
        Product = new Product { Price = 20 },
        Shipping = new Shipping { Cost = 5 }
    };

    var target = new Cart();

    //Act
    target.Add(item1, 2);
    target.Add(item2, 4);

    decimal totalProduct = target.ComputeTotalProductValue();

    // Assert
    Assert.AreEqual(totalProduct, 100);

    Console.ReadLine();
}

As it was mentioned in comments there is no need to mock repositories as they are not used in the method which is being tested. This method rather expects that Product and Shipping are already initialized.
So you need to prepare correct initial data for test manually :).

正如注释中提到的,不需要模拟存储库,因为在正在测试的方法中没有使用它们。该方法更希望产品和发货已经初始化。因此,您需要准备正确的初始数据进行手工测试:)。

#1


2  

The underlying problem here is that details about how you persist your model have leaked in to your model classes, and as a result classes that use your models (like Cart uses Item) have some obligation to understand your persistence layer, which they should not need to do. This is too much coupling, which is what makes your test hard.

这里的基本问题是,关于如何持久化模型的细节泄露给了模型类,因此使用模型的类(如Cart使用Item)有一些义务来理解持久化层,而它们不应该这样做。这是太多的耦合,这使得您的测试非常困难。

Consider redefining Item and adding an ItemDescription like so:

考虑重新定义项目并添加项目描述如下:

public class Item
{
   public Product Product {get;set;}
   public Shipping Shipping {get;set;}
}

public class ItemDescription
{
   public int ProductId {get;set;}
   public int ShippingId {get;set;}
}

This allows you to test the Cart, and other classes that use your Item class quite easily. I can add an example of that if you need it, but I'm guessing it will be pretty obvious to you. If you can't change Item for some reason, turn this approach upside down and create a FullyConsitituedItem class for your Cart to use.

这允许您测试购物车和其他使用您的项目类的类。如果你需要的话,我可以添加一个例子,但是我猜你会很容易理解。如果由于某些原因您不能更改项目,则将此方法颠倒过来,并创建一个完全可更改的Cart类以供您使用。

The cost here is that you have to create another class that contructs Items from ItemDescriptions, but you actually would have been doing that anyway, it would just have been done piecemeal all over the place. Adding ItemDescription makes it explicit, which is nicer and more readable. Certainly more testable.

这里的成本是,你必须创建另一个类来构造来自itemdescription的项目,但实际上你已经这么做了,它将会在各处零碎地完成。添加ItemDescription使其显式,更美观、可读性更好。当然更可测试的。

#2


0  

You need to directly assign Product and Shipping for item1 and item2 in test method.

您需要在测试方法中直接为item1和item2分配产品和发货。

See code example:

看到代码示例:

[TestMethod]
public void Can_Compute_Total_Values()
{
    // Arrange 
    var item1 = new Item {
        Product = new Product { Price = 10 },
        Shipping = new Shipping { Cost = 2 }
    };

    var item2 = new Item {
        Product = new Product { Price = 20 },
        Shipping = new Shipping { Cost = 5 }
    };

    var target = new Cart();

    //Act
    target.Add(item1, 2);
    target.Add(item2, 4);

    decimal totalProduct = target.ComputeTotalProductValue();

    // Assert
    Assert.AreEqual(totalProduct, 100);

    Console.ReadLine();
}

As it was mentioned in comments there is no need to mock repositories as they are not used in the method which is being tested. This method rather expects that Product and Shipping are already initialized.
So you need to prepare correct initial data for test manually :).

正如注释中提到的,不需要模拟存储库,因为在正在测试的方法中没有使用它们。该方法更希望产品和发货已经初始化。因此,您需要准备正确的初始数据进行手工测试:)。