java中带有mockito的记录器
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/41793901/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Logger with mockito in java
提问by user3205761
I'm trying to verify a logger message with mockito.
我正在尝试使用 mockito 验证记录器消息。
However, I can not run my junit class to coverage all lines of code.
但是,我无法运行我的 junit 类来覆盖所有代码行。
Do you know the reason why?
你知道原因吗?
My code:
我的代码:
public class App {
private static final Logger LOGGER = Logger.getLogger(App.class);
public List<String> addToListIfSizeIsUnder3(final List<String> list, final String value) {
if (list == null) {
LOGGER.error("A null list was passed in");
return null;
}
if (list.size() < 3) {
list.add(value);
} else {
LOGGER.debug("The list already has {} entries"+ list.size());
}
return list;
}
}
My JUnit class
我的 JUnit 课程
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import java.util.ArrayList;
import java.util.List;
import org.apache.log4j.Appender;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.spi.LoggingEvent;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class AppTest {
private App uut;
@Mock
private Appender mockAppender;
@Captor
private ArgumentCaptor<LoggingEvent> captorLoggingEvent;
@Before
public void setup() {
uut = new App();
Logger root = Logger.getRootLogger();
root.addAppender(mockAppender);
root.setLevel(Level.INFO);
}
/**
* I want to test with over 3 elements.
*/
@Test
public void testWithOver3Element() {
List<String> myList = new ArrayList<String>();
myList.add("value 1");
myList.add("value 2");
myList.add("value 3");
myList.add("value 4");
List<String> outputList = uut.addToListIfSizeIsUnder3(myList, "some value");
Assert.assertEquals(4, outputList.size());
Assert.assertFalse(myList.contains("some value"));
try {
verify(mockAppender, times(1)).doAppend(captorLoggingEvent.capture());
} catch (AssertionError e) {
e.printStackTrace();
}
LoggingEvent loggingEvent = captorLoggingEvent.getAllValues().get(0);
Assert.assertEquals("The list already has {} entries", loggingEvent.getMessage());
Assert.assertEquals(Level.DEBUG, loggingEvent.getLevel());
}
}
ERROR:
错误:
Wanted but not invoked: mockAppender.doAppend(); -> at AppTest.testWithOver3Element(AppTest.java:52) Actually, there were zero interactions with this mock.
at AppTest.testWithOver3Element(AppTest.java:52) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) at java.lang.reflect.Method.invoke(Unknown Source) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229) at org.junit.runners.ParentRunner.run(ParentRunner.java:309) at org.mockito.internal.runners.JUnit45AndHigherRunnerImpl.run(JUnit45AndHigherRunnerImpl.java:37) at org.mockito.runners.MockitoJUnitRunner.run(MockitoJUnitRunner.java:62) at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86) at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:678) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)
需要但未调用:mockAppender.doAppend(); -> 在 AppTest.testWithOver3Element(AppTest.java:52) 实际上,与此模拟的交互为零。
在 AppTest.testWithOver3Element(AppTest.java:52) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) at java。 lang.reflect.Method.invoke(Unknown Source) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java: 12) 在 org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44) 在 org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) 在 org.junit.internal.runners .statements.RunBefores.evaluate(RunBefores.java:26) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271) at org.junit.runners。BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238) at org.junit。 runners.ParentRunner$1.schedule(ParentRunner.java:63) 在 org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236) 在 org.junit.runners.ParentRunner.access$000(ParentRunner.java:53) 在 org .junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229) 在 org.junit.runners.ParentRunner.run(ParentRunner.java:309) 在 org.mockito.internal.runners.JUnit45AndHigherRunnerImpl.run(JUnit45AndHigherRunnerImpl.java: 37) 在 org.mockito.runners.MockitoJUnitRunner.run(MockitoJUnitRunner.java:62) 在 org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86) 在 org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38) 在 org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459) 在 org .eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:678) 在 org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382) 在 org.eclipse.jdt .internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)[382] 第382话[382] 第382话
回答by Dawood ibn Kareem
Your appender isn't getting fired because you've set it to INFO
level, but your code is calling LOGGER.debug
. DEBUG
is a lower level of logging than INFO
, so your appender won't see it.
您的 appender 没有被触发,因为您已将其设置为INFO
level,但您的代码正在调用LOGGER.debug
. DEBUG
是比 低的日志记录级别INFO
,因此您的 appender 不会看到它。
回答by GhostCat
Your problem is that there are inconsistencies between your testing and your production code.
您的问题是您的测试和生产代码之间存在不一致。
The test is passing a list with 3 elements, and the production code says that means that the second argument is notadded. But your test case expects it to be added!
测试传递了一个包含 3 个元素的列表,生产代码说这意味着没有添加第二个参数。但是您的测试用例需要添加它!
So the very first thing here is to ensure that your tests really cover the behavior that you have depicted within the production code that you are showing.
因此,这里的第一件事是确保您的测试真正涵盖了您在展示的生产代码中描述的行为。
Then i am wondering why your test works with a real logger here; I think it would be much easier for you to provide a mockedlogger and specify the expected calls for it!
然后我想知道为什么你的测试在这里使用真正的记录器;我认为您提供一个模拟记录器并指定它的预期调用会容易得多!
回答by Bob Lukens
There are a couple of things you could do to improve your code:
您可以做以下几件事来改进您的代码:
Switch to slf4j. This will allow you to code to an interface and be agnostic of the logging implementation under the covers (apache log4j in your case).
Switching to slf4j will allow you to not have to concatenate strings when passing to the logging framework - example:
- this statement: LOGGER.debug("The list already has {} entries" + list.size());
- can be written like this: LOGGER.debug("The list already has {} entries", list.size());
切换到 slf4j。这将允许您对接口进行编码,并且不知道幕后的日志记录实现(在您的情况下为 apache log4j)。
切换到 slf4j 将允许您在传递给日志框架时不必连接字符串 - 例如:
- 这条语句:LOGGER.debug("列表已经有{}个条目" + list.size());
- 可以这样写:LOGGER.debug("The list already has {} entries", list.size());
This has the added benefit of making the place holder in the string literal actually work.
这具有使字符串文字中的占位符实际工作的额外好处。
You are attempting to assert and capture calls to an object indirectly via the the Logging framework. This will be fragile and error prone as you never know what calls will be made internally in the Logging framework. Mock only your direct dependencies.
Don't test logging statements. It's not exactly visible behavior of the class and it makes the test fragile and complicated. Plus, treating the logging statements as you would say using an ArrayList (i.e. part of the language) allows them to be fully exercised AND they output info to the console that may be helpful in debugging a failing test. An example of being fragile is, if you change the logging statement to add more info or maybe you add another logging statement to the method, this test could break for no good reason. At the very least don't assert the number of times called as this will be extremely fragile.
您正在尝试通过 Logging 框架间接断言和捕获对对象的调用。这将是脆弱且容易出错的,因为您永远不知道将在 Logging 框架内部进行哪些调用。仅模拟您的直接依赖项。
不要测试日志语句。这不是类的完全可见的行为,它使测试变得脆弱和复杂。另外,像使用 ArrayList(即语言的一部分)那样处理日志语句允许它们被完全执行,并且它们将信息输出到控制台,这可能有助于调试失败的测试。脆弱的一个例子是,如果您更改日志记录语句以添加更多信息,或者您可能向方法添加另一个日志记录语句,则此测试可能会无缘无故地中断。至少不要断言调用的次数,因为这将非常脆弱。
All of that said, if you must test the interactions with the Logging framework - here is a modified version of your code that runs and provides the same functionality. This is basically option #3 from the list of improvements -
综上所述,如果您必须测试与 Logging 框架的交互 - 这里是运行并提供相同功能的代码的修改版本。这基本上是改进列表中的选项 #3 -
package com.spring.mockito;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.apache.log4j.Logger;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.runners.MockitoJUnitRunner;
import java.util.Arrays;
import java.util.List;
@RunWith(MockitoJUnitRunner.class)
public class AppTest {
// create a mock of the logger
@Mock
private Logger logger;
private App uut;
// Not needed - dont test something that gets called through something else
// @Captor
// private ArgumentCaptor<LoggingEvent> captorLoggingEvent;
@Before
public void setup() {
// spy the class under test so we can override the logger method to return our mock logger
uut = spy(new App());
when(uut.logger()).thenReturn(logger);
// Not needed test with the mock directly.
// Logger root = Logger.getRootLogger();
// root.addAppender(mockAppender);
// root.setLevel(Level.DEBUG);
}
/**
* I want to test with over 3 elements.
*/
@Test
public void testWithOver3Element() {
List<String> myList = Arrays.asList("value 1", "value 2", "value 3", "value 4");
List<String> outputList = uut.addToListIfSizeIsUnder3(myList, "some value");
Assert.assertEquals(4, outputList.size());
Assert.assertFalse(myList.contains("some value"));
verify(logger, times(1)).debug("The list already has {} entries4");
// not needed
// try {
// verify(mockAppender, times(1)).doAppend(captorLoggingEvent.capture());
// } catch (AssertionError e) {
// e.printStackTrace();
// }
//
// LoggingEvent loggingEvent = captorLoggingEvent.getAllValues().get(0);
// Assert.assertEquals("The list already has {} entries", loggingEvent.getMessage());
// Assert.assertEquals(Level.DEBUG, loggingEvent.getLevel());
}
public static class App {
private static final Logger LOGGER = Logger.getLogger(App.class);
public List<String> addToListIfSizeIsUnder3(final List<String> list, final String value) {
if (list == null) {
logger().error("A null list was passed in");
return null;
}
if (list.size() < 3) {
list.add(value);
} else {
// if you use slf4j this concatenation is not needed
logger().debug("The list already has {} entries" + list.size());
}
return list;
}
// make a package private method for testing purposes to allow you to inject a mock
Logger logger() {
return LOGGER;
}
}
}
You can also look at packages like PowerMockito for mocking statics - but only if absolutely needed.
您还可以查看像 PowerMockito 这样的包来模拟静态——但前提是绝对需要。
Hope this helps.
希望这可以帮助。
回答by Dred
I make it like that to avoid NPE while I check my methods with SLF4J logger
当我使用 SLF4J 记录器检查我的方法时,我这样做是为了避免 NPE
class MyClass{
public Logger logger = LoggerFactory.getLogger(MyClass.class);
//other your fields and methods
}
class MyTestClass{
private MyClass myClass = mock(MyClass.class);
private Logger log = mock(Logger.class);
@Before
public void init() {
myClass.logger = log;
}
@Test
public void firstTest() {
doNothing().when(log).debug(any());
}
}