单元测试的最佳实践

Best practice in Unit Testing

我想知道在单元测试 (C#/NUnit) 中是否有任何最佳实践来测试这样的解决方案: 想象一下,我在 BLL(业务逻辑层)中有方法 GetOrderById(long orderId),它通过它的 ID 给我一个订单 :)。所以我需要编写测试,使用硬编码的 Id 参数调用此方法,但是例如 var Id = 1; 如果没有具有此类 ID 的记录,测试将失败

[Test]
public void GetOrderById() 
{
    //var id = ?

    Assert.DoesNotThrow(() => {
        _orderService.GetOrderById(id);
    });
}

我需要一些解决方案来随时工作,也许创建临时记录?或者更好的解决方案..

有什么想法吗?

So I need to write test that will call this method with hardcoded Id parameter, but for example var Id = 1; If there is no record with such id, test will fail

这表明您不是针对接口编程,或者您正在创建集成测试,而不是单元测试。很有可能,它是两者的结合。

您的 BLL 应该仅通过接口引用数据访问层 (DAL)。它不应该有对 类 的硬编码引用。这些具体 类 应该只在 运行 时间通过注入提供。因此,在测试时,您提供一个模拟的 DAL,当测试提供正确的 Id 时,它将有一个带有特定 Id 的记录。同样,当测试 BLL 正确处理丢失的记录时,它不会有带有该 ID 的记录。

因为 DAL 是模拟的,它完全在测试的控制之下,因此很容易设置这些 success/failure 条件。

首先你必须实现依赖倒置原则,然后你将能够通过依赖注入使用 MockObjects。

你没有提供太多关于你想通过这个测试达到什么目的的信息。我猜您想测试该方法是否能够从数据库中获取订单。正如 David Arno 提到的,这就是集成测试。通常,针对数据库或文件系统的测试很糟糕,并且会导致测试缓慢而脆弱。 如果您已使用接口将数据访问层与业务层分离,则可以使用 Subsititue(在 nUnit 库中)并将其提供给您的业务层。

外汇:

IDataAccess interfaceStub = Substitute.For<IDataAccess>();

如果这还不够,就像我在这种情况下认为的那样,您希望数据访问层 return 对订单服务方法 GetOrderById 有用的东西。您可以创建一个 "testable version" 数据访问层。

可能是这样的:

    //A few simple tests
    [TestMethod]
    public void CheckThatOrderExist()
    {
        var service = new OrderServiceTestable();
        var order = service.GetOrderById(1);//This will be found in the list
        Assert.IsNotNull(order);
    }

    [TestMethod]
    public void CheckThatOrderDoesNotExist()
    {
        var service = new OrderServiceTestable();
        var order = service.GetOrderById(2);//This will not be found
        Assert.IsNull(order);
    }

    //Your data access layer
    public class OrderService
    {
        protected virtual IList<Order> OrderList { get; set; }

        public Order GetOrderById(int id)
        {
            return OrderList.SingleOrDefault(x => x.Id == id);
        }
    }

    //Order object
    public class Order
    {
        public int Id { get; set; }
    }

    //This class inherits the order service and over write the list of orders. An instance of this class is used in the tests.
    public class OrderServiceTestable : OrderService
    {
        protected new List<Order> OrderList;

        public OrderServiceTestable()
        {
            OrderList = new List<Order> {new Order {Id = 1}}; //This will overwrite the list of orders because its virtual in the order service class
        }
    }

看到我做了什么吗?通过继承真实的 class 并覆盖虚拟的属性或方法。您可以让您的可测试覆盖任何数据,同时您可以测试 OrderService class 中的实际方法。这将有助于进行健壮且快得令人眼花缭乱的测试。您不测试数据访问层,而只测试业务层中的方法。但是,这可能需要您做一些工作。

话虽如此,我还是建议用接口解耦