简体   繁体   English

单元测试的最佳实践

[英]Best practice in Unit Testing

I want to know is there any best practice in Unit Testing (C#/NUnit) to test solution like this: Imagine I have method GetOrderById(long orderId) in BLL (Business Logic Layer), which gives me an Order by it's Id :). 我想知道单元测试(C#/ NUnit)中是否有最佳实践来测试像这样的解决方案:想象一下,我在BLL(业务逻辑层GetOrderById(long orderId)中有GetOrderById(long orderId)方法,该方法通过其ID给我一个订单:) 。 So I need to write test that will call this method with hardcoded Id parameter, but for example var Id = 1; 因此,我需要编写测试,使用硬编码的Id参数调用此方法,例如var Id = 1; If there is no record with such id, test will fail 如果没有具有该ID的记录,则测试将失败

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

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

I need some solution to make work any time, create temporary record maybe? 我需要一些解决方案来随时进行工作,也许创建临时记录? or smth better solution.. 或有更好的解决方案。

any ideas? 有任何想法吗?

So I need to write test that will call this method with hardcoded Id parameter, but for example var Id = 1; 因此,我需要编写测试,使用硬编码的Id参数调用此方法,例如var Id = 1; If there is no record with such id, test will fail 如果没有具有该ID的记录,则测试将失败

This indicates that you either aren't programming to interfaces, or that you are creating an integration test, rather than a unit test. 这表明您不是对接口进行编程,或者正在创建集成测试,而不是单元测试。 Quite likely, it's a combination of both. 这很可能是两者的结合。

You BLL should only have a reference to the Data Access Layer (DAL), via interfaces. 您的BLL应该仅通过接口引用数据访问层(DAL)。 It should not have a hard-coded references to actually classes. 它不应具有对实际类的硬编码引用。 Those concrete classes should only be supplied at run-time, via injection. 这些具体的类只能在运行时通过注入来提供。 Thus, when testing, you supply a mocked DAL, which will have a record with a particular Id when testing supplying a correct Id works. 因此,在测试时,您提供了一个模拟的DAL,当提供正确的ID进行测试时,该DAL将具有特定ID的记录。 Likewise, it will not have a record with that Id when testing the BLL correctly handles a missing record. 同样,当测试BLL正确处理丢失的记录时,它将没有该ID的记录。

Because the DAL is mocked, it's completely under the control of the tests and thus it's easy to set up these success/failure conditions. 由于DAL是模拟的,因此它完全在测试的控制之下,因此可以轻松设置这些成功/失败条件。

You are not providing much info on what you want to achieve with this test. 您没有提供有关此测试要达到的目标的大量信息。 I guess you want to test if the method is able to fetch an order from the database. 我猜您想测试该方法是否能够从数据库中获取订单。 This is, as David Arno mention, integration testing. 正如David Arno所提到的,这是集成测试。 In general, testing against the database or filesystem is bad and makes for slow and brittle tests. 通常,针对数据库或文件系统的测试是不好的,并且会导致缓慢而脆弱的测试。 If you have decoupled your data access layer from the business layer with an interface, you can use Subsititue (in nUnit lib) and provide that to your buisness layer. 如果您已通过接口将数据访问层与业务层分离,则可以使用Subsititue(在nUnit lib中)并将其提供给业务层。

Fx: 外汇:

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

If that is not enough, like i think in this case, you want your data access layer to return something useful for your order service method GetOrderById. 如果那还不够,就像我在这种情况下那样,您希望数据访问层返回对订单服务方法GetOrderById有用的信息。 You could make a "testable version" of the data access layer. 您可以创建数据访问层的“可测试版本”。

Could be something like this: 可能是这样的:

    //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
        }
    }

See what i did? 看看我做了什么? By inheriting the real class and overwriting properties or methods that are virtual. 通过继承真实的类并覆盖虚拟的属性或方法。 You can make your testable overwrite any data, at the same time you are able to test the actual method in the OrderService class. 您可以使可测试覆盖任何数据,同时可以测试OrderService类中的实际方法。 This will make for robust and blindingly fast tests. 这将使测试更加健壮和快速。 You dont test your data access layer, but only the method in your business layer. 您无需测试数据访问层,而仅测试业务层中的方法。 It may, however require some work on your part. 但是,这可能需要您进行一些工作。

With this say, i still recommend decoupling with interfaces. 话虽如此,我仍然建议与接口解耦。

首先,您必须实现Dependency Inversion原理,然后才能通过Dependency Injection使用MockObjects。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM