Blog on Dan North & Associates Limited 10月02日
Mockito让单元测试更简单
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

Mockito是一个流行的Java模拟框架,它通过提供简单直观的语法和无需预先设置期望的方式,极大地简化了单元测试。与传统的模拟框架如JMock和EasyMock不同,Mockito专注于测试驱动开发(TDD)中的交互式测试,允许开发者直接在测试中验证方法调用,而不是在测试前设置复杂的期望。这种‘事后验证’的方法让测试更加直观和易于编写,减少了测试代码的复杂性。Mockito还支持模拟接口和具体类,并通过Java 5泛型提供类型安全的操作。作者Szczepan Faber指出,Mockito的设计理念是让模拟更简单,更符合TDD的实践需求,使得开发者能够更专注于业务逻辑的测试,而不是模拟对象的配置。

🔍 Mockito通过提供简单直观的语法和无需预先设置期望的方式,极大地简化了单元测试。它允许开发者在测试中直接验证方法调用,而不是在测试前设置复杂的期望,从而减少了测试代码的复杂性。

🔄 与传统的模拟框架如JMock和EasyMock不同,Mockito专注于测试驱动开发(TDD)中的交互式测试。它通过‘事后验证’的方法,让测试更加直观和易于编写,使得开发者能够更专注于业务逻辑的测试。

🛠️ Mockito支持模拟接口和具体类,并通过Java 5泛型提供类型安全的操作。这种灵活性使得开发者能够更广泛地应用Mockito来模拟各种对象,从而更有效地进行单元测试。

📚 作者Szczepan Faber指出,Mockito的设计理念是让模拟更简单,更符合TDD的实践需求。这种设计使得Mockito成为现代Java开发中单元测试的流行选择,帮助开发者提高测试效率和代码质量。

…or why Mockito is my new friend.

So what’s endotesting?#

The pioneers of the technique we now know as mocking presented a paper at the XP 2000 conference, where they first introduced the idea to a wider audience. They prefaced it with the quote:

“Once,” said the Mock Turtle at last, with a deep sigh, “I was a real Turtle.”

The Lewis Carroll reference appeals to me because I often cite Humpty Dumpty as the real creator of behaviour-driven development:

“When I use a word, " said Humpty Dumpty in a rather scornful tone, “it means just what I choose it to mean, neither more nor less.”

These folks - Tim Mackinnon, Steve Freeman and Philip Craig - realized that you could drop in a fake instance of an object that you could get to “fail fast” if it received a method call it wasn’t expecting:

We propose a technique called Mock Objects in which we replace domain code with dummy implementations that emulate real code. These Mock Objects are passed to the target domain code which they test from inside, hence the term Endo-Testing.

This approach - endotesting - led to the creation of a number of open source frameworks, most notably JMock (written by Tim and Steve among others) and EasyMock. The basic premise of these frameworks was to make it easy to create a mock object and prime it with a number of expectations. (Your makeCheese method will be called with the parameter "brie". When this happens, return the value BRIE.)

Having set up a mock object with its expectations, you wire it up into a bunch of objects and then poke the outermost object. If all goes well you can interrogate your mock and it will tell you its makeCheese method was called. If it doesn’t go well - well that’s when the magic happens. If you call an unexpected method on a mock it will fail during the test, giving you a stack trace from exactly where the unexpected method call happens. You don’t have to start rummaging through the call sequence trying to work out where it all went wrong, and your test doesn’t carry on running after the unexpected method call to produce a bogus result that doesn’t make any sense.

How we use mocks#

Now if you are wiring together a complex group of objects to do something, endotesting-style mock objects are invaluable. I had a situation a while ago where we had a suspect XML message being sent over the wire to an external system. We wrote an integration test where we injected a mock for the external system into a complex object structure - the mock was primed to only accept a valid XML message - and when it failed the stack trace gave us a direct line into the failure. It turned out one of the eight objects wired together was doing an incorrect transformation on part of the XML which meant the outbound message was invalid. By using a mock object in this way we were able to identify exactly where the error was occurring.

However nowadays mocks are mostly used for describing interactions between objects in the context of test-driven development. Your object under test interacts with one or two other objects that you represent by interfaces (roles) that you probably inject into its constructor as mocks. Your test is verifying that the object calls the appropriate methods on its collaborators. In this simplistic scenario, the reason for a failure is usually pretty obvious - you haven’t written the code yet or you are passing the wrong parameters into the method call.

Say you are developing a simple calculation where you expect the answer to be 7 but the code is producing the answer 10, then it isn’t that difficult to work out where the problem is. You don’t need the code to fail as soon as the value of 10 is produced. The fact that the answer is 10 and you expected 7 is enough to triangulate the error. This is classic post hoc state-based testing. Similarly, if you expect your object to call makeCheese on the CheeseFactory and it doesn’t, well you are likely to have a pretty good idea where it doesn’t. So all you really need your mocking library to do is act like a flight recorder black box. You can ask it after the fact: hey CheeseFactory, did my object call your makeCheese method? If it didn’t you probably know why. (And if you don’t it may be an indication that your test is too complicated.)

A more intuitive approach to mocking#

This then means that you can treat assertions on method calls just like you treat assertions on state changes, asking the various collaborators what happened after the fact rather than priming your mock objects ahead of time, which in turn means you don’t need to turn your brain inside-out creating a flow like this:

 // Given // ... create mocks // ... wire up my object // Expect (eh?) funky.dslWith("stringsForMethods") // When myObject.makeBrie(); // Then verifyExpectationsSetTwoParagraphsEarlier(); // you remember right?

where it is much more intuitive to write something like:

 // Given // ... create mocks // ... wire up my object // When myObject.makeBrie(); // Then verify(CheeseFactory).makeCheese("brie"); // how nice is that?

This, then, is the premise of Mockito. It uses a similar syntax to EasyMock - which means my generics-aware IDE is able to do proper completions and refactorings - but it doesn’t have any of that record/playback nonsense. Nor does it require me to do any of the clever @{{ anonymous constructor }}@ gymnastics that JMock 2 introduced. It just sets up mocks on interfaces (although it allows me to mock concrete classes which I think is a retrograde step - remember kids, mock roles, not objects) and gives me a black box recorder that I can interrogate after the fact.

When I asked Mockito’s author, Szczepan Faber, how he came up with the insight that mocking in the context of TDD was a completely separate problem from endotesting, he replied: what’s endotesting?

It turned out that in order to create a mocking framework to support TDD it helped to have no idea where the encumbent frameworks - JMock and EasyMock - had come from. In the same way that a unit testing framework got co-opted as the enabler for TDD in Java, an endotesting framework became the enabler for method assertions in interaction-based testing. But if you start from the ground up - with a nod to EasyMock and with a pragmatic dose of Java 5 generics - you can get a simple, elegant and intuitive framework for verifying interactions between collaborators. There’s still a place for traditional endotesting in the context of gnarly integration tests, but I’m liking Mockito. I think you will too.

Check out

Goalwards®

, our new business agility practice!

Fish AI Reader

Fish AI Reader

AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

FishAI

FishAI

鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

联系邮箱 441953276@qq.com

相关标签

Mockito 单元测试 测试驱动开发 Java 模拟框架
相关文章