四、使用Mock对象
很多情况下,代码需要与外部依赖打交道,如一个REST地址,数据库链接、外部IO等;这些依赖有些速度过慢、有些不够稳定,不符合单元测试要求的快速、可重复等原则性要求,因此引入了Mock对象这一概念。与Mock相关的还有Stub这个单词。
- stub 桩,它针对指定的输入缓存了行为
- mock 模拟对象,增加了对输入条件校验、注入等功能,简单来说,它保证在收到预期参数时表现出预定义的行为,常用的有两个框架
- mockito 较为易用
- powermock 功能更加强大,能够对静态方法和私有函数进行Mock
一般来说,在编写stub之后,需要将其注入依赖对象中,也即依赖注入(DI),框架上有Spring DI和Google Guice等。
修改代码结构使其更具可测性
为了使得测试更加容易,有时需要修改代码,如将依赖以成员变量的形式传入被测类中,如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | public class AddressRetriever{ private Http http; //将外部依赖以构造函数的方式引入,对单元测试更加友好 public AddressRetriever(Http http){ this .http = http; } public Address retrieve( double latitude, double longitude) throws IOException, ParseException{ String params = String.format( "lat=%.6flon=%.6f" , latitude, longitude); String response = http.get( "http://open.mapquestapi.com/nominatim/v1/reverse?format=json&" +params); JSONObject obj = (JSONObject) new JSONParse().parse(reponse); //... } } |
但不仅限于构造函数,还可以通过set方法或其他依赖注入框架实现。
为Stub增加一点智能
如这个桩:
1 2 3 4 5 6 | Http http = new Http(){ @Override public String get(String uri) throws IOException{ return "{\"address\":{" + "\"house_number\":\"324\"," + "\"road\":\"North Tejon Street\"," } } |
这个桩接受任何uri即可返回对应的结果,没有对输入进行判断,我们期望的是:在收到预期参数时提供预期的输入,可以通过在get()方法中加入判断实现,这样的通用功能引入Mock工具。
使用Mock工具简化测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | public class AddressRetrieverTest { @Test public void answersAppropriateAddressForValidCoordinates() throws IOException, ParseException { Http http = mock(Http. class ); when(http.get(contains( "lat=38.000000&lon=-104.000000" ))).thenReturn( "{\"address\":{" + "\"house_number\":\"324\"," // ... + "}" ); AddressRetriever retriever = new AddressRetriever(http); Address address = retriever.retrieve( 38.0 ,- 104.0 ); assertThat(address.houseNumber, equalTo( "324" )); } |
when().thenReturn()模式就是Mockito设置的常用方式。
介绍一种DI工具
DI工具有很多,如Spring DI和Google Guice,但是moctito内建的DI工具也能满足绝大部分的需要,步骤如下:
- 使用@Mock注解创建一个模拟对象
- 使用@InjectMocks注解声明一个目标对象
- 在目标对象初始化完毕后,调用MockitoAnnotations.initMocks(this)方法完成注入
下面是示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | public class AddressRetrieverTest{ @Mock private Http http; @InjectMocks private AddressRetriever retriever; @Before public void createRetriever(){ retriever = new AddressRetriever(); MockitoAnnotations.initMocks( this ); } @Test public void answersAppropriateAddressForValidCoordinates() throws IOException, ParseException{ when(http.get(contains( "lat=38.000000&lon=-104.000000" ))) .thenReturn( "{\"address\":{" + "\"house_number\":\"324\"," //... } } |
最后需要注意的是,如果使用了Mock,那不是直接测试生产代码,而是在于生产代码中加了鸿沟,单元测试的正确性依赖于被Mock对象的正确性,因此单元测试需要配合端到端的集成测试。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探