如何编写用于日志记录的单元测试

2020-11-11 19:42:29

每隔一段时间,我就会被问到是否应该为日志记录功能编写单独的测试。我对这个问题的回答是典型的咨询师回答:“这要看情况”。从本质上讲,日志记录是一个基础设施问题。最终结果是写入应用程序外部资源的日志数据。通常情况下,生成的数据最终会保存在文件或数据库中,甚至可能会保存在云服务中。

因为日志记录跨越了应用程序的进程边界,所以编写社交测试来验证这一特定功能更有用。在这种情况下使用单独测试是没有意义的。

也就是说,在某些情况下,业务需求明确规定日志记录应该是应用程序接口的一部分。在这种情况下,日志记录的意图应该由代码明确表达,而代码也应该通过单独的测试来执行。史蒂夫·弗里曼(Steve Freeman)和纳特·普赖斯(Nat Pryce)合著的优秀著作《Growing Object Oriented Software Guided by Test》提到,通常有两种不同类型的日志记录:

支持日志包含针对执行操作活动的人员的消息。这些消息用于确定系统运行是否正确。这些消息的日志级别通常是ERROR或INFO类型。

另一方面,诊断日志保存针对软件开发人员的消息。这些消息提供了对正在运行的系统的详细信息的有价值的见解。这些消息的日志级别通常为DEBUG或TRACE类型。

考虑到这两种类型的日志记录,基本思想是表达支持日志记录意图的代码应该通过单独的测试来执行。启动诊断日志记录的代码语句通常不在测试范围内。

让我们看一个同时演示支持和诊断日志记录的示例。

公共类ExpenseSheetController:控制器{私有只读ICommandHandler;CreateExpenseSheet&>;_CommandHandler;私有只读ISupportNotifier_SupportNotifier;公共ExpenseSheetController(ICommandHandler<;CreateExpenseSheet>;命令处理程序,ISupportNotifiersupportNotifier){_CommandHandler=命令句柄;_supportNotifier=supportNotifier;}[HttpPost][ServiceFilter(typeof(perpeof。Return BadRequest();}_supportNotifier.ExpenseSheetCreated(formModel.EmployeeId);Return OK();}}

这里我们实现了一个控制器,它可以接收创建新报销单的请求。注意,这个控制器类的构造函数需要ISupportNotifier接口的一个实例。当发生异常时,Create方法的实现使用此依赖项来记录错误。它还用于在成功创建报销单时进行日志记录。

公共类支持通知器:ISupportNotifier{Private Readonly ILogger<;SupportNotifier>;_Logger;Public SupportNotifier(ILogger<;SupportNotifier>;Logger){_logger=logger;}Public void ExpenseSheetCreated(GUID EmploeID){_logger.LogInformation(";为ID';{ployeeID}';';";);}Public void ExpenseSheetCreated(GUID EmployeeID){_logger.LogInformation(";为ID';{ployeeID}';";);}Public void ErrorDuringExpenseSheet。{ployeeId}';";);}}。

这段代码演示了支持日志记录根据上下文使用日志级别、错误或信息。验证SupportNotifier类本身的代码可以通过使用社交测试来完成。为SupportNotifier类编写保留性测试不是一个好主意。这意味着应该将test Double用作ILogger的实例。正如我们在之前的一篇博客文章中已经提到的,最好避免对您不拥有的类型使用测试替身。在这种情况下,甚至很难做到这一点,因为ILogger的Logxx方法实际上是扩展方法,而不是常规方法。

[规范]公共类When_handling_a_request_for_creating_a_new_expense_sheet{[建立]公共空上下文(){var命令句柄=Substitute.For<;ICommandHandler<;CreateExpenseSheet>;>;();_SupportNotifier=Substitute.For<;ISupportNotifier>;();_sut=新的ExpenseSheetController(命令句柄,_SupportNotifier);}[因为]公共空的(){var formModel=new CreateExpenseSheetFormModel{EmployeeID=new Guid(";94EDE8F3-9675-4DD7-A18F-E37B1F323699";)};_sut.Create(FormModel);}[Observation]PUBLIC VALID THEN_IT_HUTIFY_NOTIFY_SUPPORT(){_supportNotifier.Receired().ExpenseSheetCreated(new Guid(";94EDE8F3-9675-4DD7-A18F-E37B1F323699";));}Private ExpenseSheetController_Sut;Private ISupportNotifierSupportNotifier;}[规范]公共类When_an_error_occurs_while_handling_a_request_for_creating_a_new_expense_sheet{[建立]公共无效上下文(){_SupportNotifier=Substitute.For<;ISupportNotifier>;();_Except=新的InvalidOperationException(";Meltdown";);变量命令Handler=Substitute.For<;ICommandHandler<;CreateExpenseSheet>;>;();命令Handler.WhenForAnyArgs(ch=>;ch.Handle(NULL)).Throw(_Except);_sut=new ExpenseSheetController(命令处理程序,_supportNotifier);}[因为]PUBLIC VOID of(){var formModel=new CreateExpenseSheetFormModel{EmployeeID=new Guid(";D1067157-5C73-4140-9D29-0FE5C1C4C2FB";)};_sut.Create(FormModel);}[Observation]PUBLIC VALID Then_it_should_notify_support_that_a_new_expense_sheet_has_been_created(){_supportNotifier.Receired().ErrorDuringExpenseSheetCreation(_Exception,new GUID(";D1067157-5C73-4140-9D29-0FE5C1C4C2FB";));}私有费用SheetController_sut;私有ISupportNotifier_supportNotifier;私有异常异常;}。

这些测试验证是否在创建报销单或引发异常时进行支持日志记录。通过这种方式,我们表达了操作需求的意图。

通过应用此属性,可以注册PerformanceTracing操作筛选器以包围控制器方法的执行。让我们来看看这个操作过滤器的实现。

公共类PerformanceTracing:ActionFilterAttribute{私有只读ILogger;PerformanceTracing;_logger;私有只读秒表_stopwatch;公共PerformanceTracing(ILogger<;PerformanceTracing>;记录器){_logger=记录器;_stopwatch=新秒表();}公共重写void OnActionExecuting(ActionExecutingContext Context){_stopWatch.Start();}公共重写void OnActionExecuted(ActionExecuted。在";+$";{_stopWatch.ElapsedMillisecond}毫秒内执行的控制器{ControlerName}的操作';{ControlerActionName}';);}}。

这个实现是诊断日志记录的一个很好的例子。操作筛选器测量控制器方法的执行时间并记录结果。注意,我们将ILogger接口直接注入到构造函数中。通过使用ServiceFilter属性注册PerformanceTracing操作筛选器,我们可以确保ILogger的实例得到解析和正确注入。我们没有为这个实现提供任何测试。

我认为将支持日志记录和诊断日志记录视为两个独立的概念是有用的,尽管它们通常在幕后使用相同的机制。

如果您和您的团队想要了解更多关于如何编写可维护的单元测试并最大限度地利用TDD实践的知识,请务必查看我们的培训和研讨会或查看书籍部分。请随时联系INFO NULL@null主体-it null.be。

感谢您访问我的博客。我从2000年起就是一名专业的软件开发人员。从Y2K+5开始写博客。提供XP实践方面的培训和指导。令人敬畏的谈话名单的策展人。欧洲虚拟ALT.NET会议的前组织者。一直在思考和学习各种技术。