如何对记录器中的消息执行 JUnit 断言
我有一些正在测试的代码,它们调用 Java 记录器来报告其状态。 在 JUnit 测试代码中,我想验证此记录器中是否创建了正确的日志条目。大致如下:
methodUnderTest(bool x){
if(x)
logger.info("x happened")
}
@Test tester(){
// perhaps setup a logger first.
methodUnderTest(true);
assertXXXXXX(loggedLevel(),Level.INFO);
}
我想这可以通过专门调整的记录器(或处理程序或格式化程序)来完成,但我更愿意重新使用已经存在的解决方案。 (而且,说实话,我不清楚如何从记录器获取 logRecord,但假设这是可能的。)
I have some code-under-test that calls on a Java logger to report its status.
In the JUnit test code, I would like to verify that the correct log entry was made in this logger. Something along the following lines:
methodUnderTest(bool x){
if(x)
logger.info("x happened")
}
@Test tester(){
// perhaps setup a logger first.
methodUnderTest(true);
assertXXXXXX(loggedLevel(),Level.INFO);
}
I suppose that this could be done with a specially adapted logger (or handler, or formatter), but I would prefer to re-use a solution that already exists. (And, to be honest, it is not clear to me how to get at the logRecord from a logger, but suppose that that's possible.)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(30)
我也多次需要这个。我在下面整理了一个小样本,您可以根据自己的需要进行调整。基本上,您创建自己的 Appender 并将其添加到您想要的记录器中。如果您想收集所有内容,根记录器是一个不错的起点,但如果您愿意,也可以使用更具体的记录器。完成后不要忘记删除 Appender,否则可能会造成内存泄漏。下面我在测试中完成了它,但是
setUp
或@Before
和tearDown
或@After
可能是更好的地方,取决于您的需求。此外,下面的实现将所有内容收集到内存中的
List
中。如果您要记录大量日志,您可能会考虑添加一个过滤器来删除无聊的条目,或者将日志写入磁盘上的临时文件(提示:LoggingEvent
是Serialized
,因此,如果您的日志消息是,您应该能够序列化事件对象。)I've needed this several times as well. I've put together a small sample below, which you'd want to adjust to your needs. Basically, you create your own
Appender
and add it to the logger you want. If you'd want to collect everything, the root logger is a good place to start, but you can use a more specific if you'd like. Don't forget to remove the Appender when you're done, otherwise you might create a memory leak. Below I've done it within the test, butsetUp
or@Before
andtearDown
or@After
might be better places, depending on your needs.Also, the implementation below collects everything in a
List
in memory. If you're logging a lot you might consider adding a filter to drop boring entries, or to write the log to a temporary file on disk (Hint:LoggingEvent
isSerializable
, so you should be able to just serialize the event objects, if your log message is.)这是一个简单高效的Logback解决方案。
它不需要添加/创建任何新类。
它依赖于
ListAppender
:一个白盒 logback 附加程序,其中日志条目添加到我们可以用来做出断言的公共列表字段中。这是一个简单的例子。
Foo 类:
FooTest 类:
JUnit 断言听起来不太适合断言列表元素的某些特定属性。
AssertJ 或 Hamcrest 等匹配器/断言库似乎更好:
使用 AssertJ 它将是:
Here is a simple and efficient Logback solution.
It doesn't require to add/create any new class.
It relies on
ListAppender
: a whitebox logback appender where log entries are added in apublic List
field that we could so use to make our assertions.Here is a simple example.
Foo class :
FooTest class :
JUnit assertions don't sound very adapted to assert some specific properties of the list elements.
Matcher/assertion libraries as AssertJ or Hamcrest appears better for that :
With AssertJ it would be :
对于 Junit 5 (Jupiter) Spring 的 OutputCaptureExtension 非常有用。它可以通过@ExtendWith注释为整个测试类或单个测试方法注册。它从 Spring Boot 2.2 开始可用,可在 spring-boot-test< /a> 神器。
示例(取自 javadoc):
For Junit 5 (Jupiter) Spring's OutputCaptureExtension is quite useful. It can be registered for an entire test class or for an individual test method via the
@ExtendWith
annotation. It's available since Spring Boot 2.2 and is available in the spring-boot-test artifact.Example (taken from javadoc):
非常感谢这些(令人惊讶的)快速且有用的答案;他们让我找到了正确的解决方案。
我想使用这个代码库,使用 java.util.logging 作为其记录器机制,并且我对这些代码感觉不够熟悉,无法将其完全更改为 log4j 或记录器接口/外观。但根据这些建议,我“破解”了一个 julhandler 扩展,这可以作为一种享受。
下面是一个简短的总结。扩展
java.util.logging.Handler
:显然,您可以从
LogRecord
存储您喜欢/想要/需要的任意数量,或者将它们全部推入堆栈,直到您完成为止。得到溢出。在准备 junit-test 的过程中,您创建一个
java.util.logging.Logger
并向其中添加一个新的LogHandler
:调用
setUseParentHandlers( )
是让正常处理程序保持沉默,以便(对于此 junit-test 运行)不会发生不必要的日志记录。执行您的测试代码需要使用此记录器的任何操作,运行测试并断言Equality:(当然,您可以将这项工作的大部分移至
@Before
方法中,并进行各种其他改进,但这会使本演示文稿变得混乱。)Thanks a lot for these (surprisingly) quick and helpful answers; they put me on the right way for my solution.
The codebase were I want to use this, uses java.util.logging as its logger mechanism, and I don't feel at home enough in those codes to completely change that to log4j or to logger interfaces/facades. But based on these suggestions, I 'hacked-up' a j.u.l.handler extension and that works as a treat.
A short summary follows. Extend
java.util.logging.Handler
:Obviously, you can store as much as you like/want/need from the
LogRecord
, or push them all into a stack until you get an overflow.In the preparation for the junit-test, you create a
java.util.logging.Logger
and add such a newLogHandler
to it:The call to
setUseParentHandlers()
is to silence the normal handlers, so that (for this junit-test run) no unnecessary logging happens. Do whatever your code-under-test needs to use this logger, run the test and assertEquality:(Of course, you would move large part of this work into a
@Before
method and make assorted other improvements, but that would clutter this presentation.)我也遇到了同样的挑战,最终来到了这个页面。虽然我已经晚了 11 年才回答这个问题,但我想也许它对其他人仍然有用。我发现
davidxxx
with Logback 和 ListAppander 的答案非常有用。我对多个项目使用了相同的配置,但是当我需要更改某些内容时,复制/粘贴它并维护所有版本并不是那么有趣。我认为最好用它建立一个图书馆并回馈社区。它可与 SLFJ4、Apache Log4j2、Java Util Logging、JBoss Logging、Google Flogger 以及 Lombok 注释配合使用。请查看这里:LogCaptor 了解详细示例以及如何将其添加到您的项目中。示例情况:
使用 LogCaptor 的示例单元测试:
我不太确定是否应该在此处发布此内容,因为它也可以被视为推广“我的库”的一种方式,但我认为这对于拥有以下功能的开发人员可能会有所帮助同样的挑战。
I also ran into the same challanged and ended up at this page. Although I am 11 years too late to answers the question, I thought maybe it could be still usefull for others. I found the answer of
davidxxx
with Logback and the ListAppander very usefull. I used the same configuration for multiple projects, however it was not so fun to copy/paste it and maintaining all the version when I needed to changes something. I thought it would be better to make a library out of it and contribute back to the community. It works with SLFJ4, Apache Log4j2, Java Util Logging, JBoss Logging, Google Flogger and with Lombok annotations. Please have a look here: LogCaptor for detailed examples and how to add it to your project.Example situation:
Example unit test with usage of LogCaptor:
I wasn't quite sure if I should post this here, because it could also be seen as a way to promote "my library" but I thought it could be helpful for developers who have the same challenges.
实际上,您正在测试依赖类的副作用。对于单元测试,您只需要验证
使用正确的参数调用了 。因此,使用模拟框架来模拟记录器,这将允许您测试自己的类的行为。
这是一个有趣的权衡:模拟该依赖类需要做多少工作?设置包含依赖类的测试工具并对依赖项的结果进行验证需要多少钱?一般来说,如果我的“单元”具有高度算法性,我更愿意花时间使用模拟来隔离它。
Effectively you are testing a side-effect of a dependent class. For unit testing you need only to verify that
was called with the correct parameter. Hence use a mocking framework to emulate logger and that will allow you to test your own class's behaviour.
It is an interesting trade-off: how much work is it to mock that dependent class? How much does it cost to set up a test harness including the dependent class and perform verification of the results of the dependency? Generally, if my "Unit" is highly algorithmic I prefer to invest time to isolate it using mocks.
另一种选择是模拟 Appender 并验证消息是否已记录到此 Appender。 Log4j 1.2.x 和mockito 的示例:
Another option is to mock Appender and verify if message was logged to this appender. Example for Log4j 1.2.x and mockito:
对于 log4j2,解决方案略有不同,因为 AppenderSkeleton 不再可用。此外,如果您期望多个日志消息,则使用 Mockito 或类似的库创建带有 ArgumentCaptor 的 Appender 将不起作用,因为 MutableLogEvent 会在多个日志消息上重用。我发现 log4j2 的最佳解决方案是:
For log4j2 the solution is slightly different because AppenderSkeleton is not available anymore. Additionally, using Mockito, or similar library to create an Appender with an ArgumentCaptor will not work if you're expecting multiple logging messages because the MutableLogEvent is reused over multiple log messages. The best solution I found for log4j2 is:
模拟是这里的一个选项,尽管这会很困难,因为记录器通常是私有静态最终的 - 因此设置模拟记录器并不是小菜一碟,或者需要修改被测试的类。
您可以创建一个自定义 Appender(或任何名称),并通过仅测试配置文件或运行时(在某种程度上取决于日志记录框架)注册它。
然后您可以获取该附加程序(如果在配置文件中声明,则静态获取;如果在运行时插入它,则通过其当前引用获取),并验证其内容。
Mocking is an option here, although it would be hard, because loggers are generally private static final - so setting a mock logger wouldn't be a piece of cake, or would require modification of the class under test.
You can create a custom Appender (or whatever it's called), and register it - either via a test-only configuration file, or runtime (in a way, dependent on the logging framework).
And then you can get that appender (either statically, if declared in configuration file, or by its current reference, if you are plugging it runtime), and verify its contents.
受到@RonaldBlschke解决方案的启发,我想出了这个:
...这允许你这样做:
你可能可以让它以更智能的方式使用 hamcrest,但我已经把它留在这里了。
Inspired by @RonaldBlaschke's solution, I came up with this:
... which allows you to do:
You could probably make it use hamcrest in a smarter way, but I've left it at this.
哇。我不确定为什么这这么难。我发现我无法使用上面的任何代码示例,因为我使用的是 log4j2 而不是 slf4j。这是我的解决方案:
Wow. I'm unsure why this was so hard. I found I was unable to use any of the code samples above because I was using log4j2 over slf4j. This is my solution:
这是我为 logback 所做的。
我创建了一个 TestAppender 类:
然后在 testng 单元测试类的父级中,我创建了一个方法:
我在 src/test/resources 中定义了一个 logback-test.xml 文件,并添加了一个测试附加程序:
并将此附加程序添加到根附加程序:
现在,在从父测试类扩展的测试类中,我可以获取附加程序并获取记录的最后一条消息,并验证消息、级别、可抛出的内容。
Here is what i did for logback.
I created a TestAppender class:
Then in the parent of my testng unit test class I created a method:
I have a logback-test.xml file defined in src/test/resources and I added a test appender:
and added this appender to the root appender:
Now in my test classes that extend from my parent test class I can get the appender and get the last message logged and verify the message, the level, the throwable.
正如其他人提到的,您可以使用模拟框架。为了使其工作,您必须在类中公开记录器(尽管我可能更喜欢将其打包为私有而不是创建公共设置器)。
另一个解决方案是手动创建一个假记录器。您必须编写假记录器(更多固定代码),但在这种情况下,我更喜欢针对模拟框架中保存的代码增强测试的可读性。
我会做这样的事情:
As mentioned from the others you could use a mocking framework. For this to make work you have to expose the logger in your class (although I would propably prefere to make it package private instead of creating a public setter).
The other solution is to create a fake logger by hand. You have to write the fake logger (more fixture code) but in this case I would prefer the enhanced readability of the tests against the saved code from the mocking framework.
I would do something like this:
最简单的方法
Easiest way
请注意,在 Log4J 2.x 中,公共接口
org.apache.logging.log4j.Logger
不包含setAppender()
和removeAppender()< /代码> 方法。
但是,如果您没有做任何太花哨的事情,您应该能够将其转换为实现类
org.apache.logging.log4j.core.Logger
,它确实公开了这些方法。这是 Mockito 和 AssertJ:
Note that in Log4J 2.x, the public interface
org.apache.logging.log4j.Logger
doesn't include thesetAppender()
andremoveAppender()
methods.But if you're not doing anything too fancy, you should be able to cast it to the implementation class
org.apache.logging.log4j.core.Logger
, which does expose those methods.Here's an example with Mockito and AssertJ:
检查这个库 https://github.com/Hakky54/log-captor
包含在你的maven中归档库的引用:
在 java 代码测试方法中,您应该包含以下内容:
Check this library https://github.com/Hakky54/log-captor
Include in your maven file the reference for the library:
In java code test method you should include this:
对我来说,您可以通过使用
JUnit
和Mockito
来简化您的测试。我建议采用以下解决方案:
这就是为什么我们对不同消息数量的测试具有很好的灵活性
As for me you can simplify your test by using
JUnit
withMockito
.I propose following solution for it:
That's why we have nice flexibility for tests with different message quantity
您不需要在类实现中依赖硬编码的静态全局记录器,您可以在默认构造函数中提供默认记录器,然后使用特定构造函数来设置对所提供记录器的引用。
You don't need to rely on hardcoded static global Loggers in your class implementation, you can provide a default logger in the default constructor and then use a specific constructor to set a reference to the provided logger.
这是解决这个问题的一个很好且优雅的方法:
https://www.baeldung.com/junit-asserting-logs
Here is a nice and elegant way to approach this problem:
https://www.baeldung.com/junit-asserting-logs
如果我想做的只是查看记录了某个字符串(而不是验证确切的日志语句,这太脆弱了),我所做的就是将 StdOut 重定向到缓冲区,执行 contains,然后重置 StdOut:
What I have done if all I want to do is see that some string was logged (as opposed to verifying exact log statements which is just too brittle) is to redirect StdOut to a buffer, do a contains, then reset StdOut:
Log4J2 的 API 略有不同。您也可能正在使用它的异步附加程序。我为此创建了一个锁存的附加程序:
像这样使用它:
The API for Log4J2 is slightly different. Also you might be using its async appender. I created a latched appender for this:
Use it like this:
另一个值得一提的想法是创建一个 CDI 生产者来注入您的记录器,以便模拟变得容易,尽管这是一个较旧的主题。 (它还提供了不必再声明“整个记录器语句”的优点,但这与主题无关)
示例:
创建要注入的记录器:
限定符:
在生产代码中使用记录器:
在您的生产代码中测试记录器测试代码(给出一个easyMock示例):
Another idea worth mentioning, although it's an older topic, is creating a CDI producer to inject your logger so the mocking becomes easy. (And it also gives the advantage of not having to declare the "whole logger statement" anymore, but that's off-topic)
Example:
Creating the logger to inject:
The qualifier:
Using the logger in your production code:
Testing the logger in your test code (giving an easyMock example):
使用 Jmockit (1.21) 我能够编写这个简单的测试。
该测试确保特定的错误消息仅被调用一次。
Using Jmockit (1.21) I was able to write this simple test.
The test makes sure a specific ERROR message is called just once.
模拟 Appender 可以帮助捕获日志行。
示例可在以下位置找到:http://clearqa.blogspot.co。 uk/2016/12/test-log-lines.html
Mocking the Appender can help capture the log lines.
Find sample on: http://clearqa.blogspot.co.uk/2016/12/test-log-lines.html
使用下面的代码。我在 Spring 集成测试中使用相同的代码,其中使用 log back 进行日志记录。使用方法assertJobIsScheduled 断言日志中打印的文本。
Use the below code. I am using same code for my spring integration test where I am using log back for logging. Use method assertJobIsScheduled to assert the text printed in the log.
您可能会尝试测试两件事。
这两件事实际上是不同的事情,因此可以单独测试。然而,测试第二个(消息文本)非常有问题,我建议完全不要这样做。消息文本的测试最终将包括检查一个文本字符串(预期的消息文本)是否与日志记录代码中使用的文本字符串相同,或者可以简单地从该文本字符串派生而来。
请注意,让程序代码(也许实现一些业务逻辑)直接调用文本日志记录接口是糟糕的设计(但不幸的是很常见)。负责业务逻辑的代码还决定一些日志记录策略和日志消息的文本。它将业务逻辑与用户界面代码混合在一起(是的,日志消息是程序用户界面的一部分)。这些东西应该分开。
因此,我建议业务逻辑不要直接生成日志消息的文本。相反,将其委托给日志记录对象。
实现
一个接口
,它描述了您的业务逻辑可能使用的内部API。接口
。然后,您可以通过创建一个实现内部日志记录 API 的模拟记录器,并在测试的设置阶段使用依赖项注入来测试您的业务逻辑类是否正确地向日志记录接口告知有关事件的信息。
像这样:
There are two things that you might be trying to test.
Those two things are actually different things, and so could be tested separately. However, testing the second (the text of messages) is so problematic, I recommend against doing it at all. A test of a message text will ultimately consist of checking that one text string (the expected message text) is the same as, or can be trivially derived from, the text string used in your logging code.
Note that having your program code (implementing some business logic, perhaps) directly calling the text logging interface is poor design (but unfortunately very commom). Code that is responsible for business logic is also deciding some logging policy and the text of log messages. It mixes business logic with user interface code (yes, log messages are part of your program's user interface). Those things should be separate.
I therefore recommend that business logic does not directly generate the text of log messages. Instead have it delegate to a logging object.
implements
aninterface
, which describes the internal API your business logic may use.interface
.You can then test that your business logic classes correctly tell the logging interface about events, by creating a mock logger, which implements the internal logging API, and using dependency injection in the set up phase of your test.
Like this:
我回答了 log4j 的类似问题,请参阅 how-can-i-test-with-junit-that-a-warning-was-logged- with-log4
这是较新的示例,使用 Log4j2(使用 2.11.2 测试)和 junit 5;
使用以下 Maven 依赖项
I answered a similar question for log4j see how-can-i-test-with-junit-that-a-warning-was-logged-with-log4
This is newer and example with Log4j2 (tested with 2.11.2) and junit 5;
Using the following maven dependencies
就我而言,我解决了同样的问题,如下所示:
In my case I solved the same issue as bellow:
通过添加 Appender 进行单元测试并不能真正测试 Logger 的配置。因此,我认为这是一种独特的情况,其中单元测试不会带来那么多价值,但集成测试会带来很多价值(特别是如果您的日志记录有一些审计目的)。
为了为其创建集成测试,我们假设您正在使用一个简单的
ConsoleAppender
运行并想要测试其输出。然后,您应该测试如何将消息从System.out
写入其自己的ByteArrayOutputStream
。从这个意义上说,我会执行以下操作(我正在使用 JUnit 5):
这样,您就可以通过简单地测试其输出:
如果这样做,您将为您的项目带来更多价值,并且不需要使用内存中的实现、创建一个新的 Appender 等等。
Unit testing by adding an Appender does not really test the Logger's configuration. So, I think that it's one of the unique cases in which unit tests do not bring that much value, but an integration test brings a lot of value (especially if your logging has some auditing purposes).
In order to create an integration test for it, let us suppose that you are running with a simple
ConsoleAppender
and want to test its output. Then, you should test how the message is written to its ownByteArrayOutputStream
fromSystem.out
.In that sense, I would do the following (I'm using JUnit 5):
In that way, you are able to test its output by simply:
If you do so, you will bring much more value to your project and will not need to using an in-memory implementation, create a new Appender, or whatsoever.