Flutter for OpenHarmony:mockito 单元测试的替身演员,轻松模拟复杂依赖(测试驱动开发必备) 深度解析与鸿蒙适配指南

Flutter for OpenHarmony:mockito 单元测试的替身演员,轻松模拟复杂依赖(测试驱动开发必备) 深度解析与鸿蒙适配指南

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net

在这里插入图片描述

前言

在软件开发中,单元测试(Unit Testing)是保证代码质量的基石。然而,在测试某个具体的业务逻辑(如 UserService)时,我们往往会遇到各种外部依赖,比如数据库查询、网络请求、设备传感器等。

如果直接调用真实的 DatabaseHttpClient,不仅测试速度慢,而且容易因为网络抖动或环境问题导致测试失败。此外,我们很难复现一些极端场景(如 500 服务器错误、数据库连接超时)。

Mockito 就是为了解决这个问题而生的。它允许我们创建对象的 Mock(替身),并精确控制这些替身的行为(Stubbing)和验证它们的交互(Verification)。

在 OpenHarmony 应用开发中,使用 mockito 可以让我们在开发机(Host)上就能快速验证大部分业务逻辑,通过后再部署到鸿蒙真机进行集成测试,极大地提高了开发效率。

一、核心概念与工作原理

1.1 什么是 Mock?

Mock 对象是真实对象的“傀儡”。它继承自真实类,但不会执行真实类的任何代码。相反,它记录了所有对它的调用,并根据你的配置返回预设的值。

1.2 Mockito 的黑魔法:代码生成 (Codegen)

早期版本的 Mockito 依赖 noSuchMethod 和反射(Mirrors),但在 Flutter 中禁止使用反射(为了 AOT 优化)。因此,现在的 mockito 配合 build_runner 使用,在编译时生成 Mock 类。

@GenerateMocks

生成

使用

验证

RealClass.dart

build_runner

RealClass.mocks.dart

单元测试

Mock 对象

1.3 核心操作三部曲

  1. Stubbing (桩): when(mock.method()).thenReturn(value) —— “当调用这个方法时,请返回这个值”。
  2. Execution (执行): 运行被测代码,被测代码会调用 Mock 对象。
  3. Verification (验证): verify(mock.method()) —— “验证这个方法是否被调用过,且参数正确”。

二、集成与基础用法

2.1 添加依赖

mockito 通常只在 dev_dependencies 中使用。

dev_dependencies:flutter_test:sdk: flutter mockito: ^5.6.3 build_runner: ^2.4.0 

2.2 定义被测类

假设我们有一个从 API 获取用户信息的服务:

// user_api.dartimport'dart:convert';import'package:http/http.dart'as http;classUserApi{finalhttp.Client client;UserApi(this.client);Future<String>fetchUserName(int id)async{final response =await client.get(Uri.parse('https://api.example.com/users/$id'));if(response.statusCode ==200){returnjsonDecode(response.body)['name'];}else{throwException('Failed to load user');}}}

2.3 生成 Mock 类

创建一个测试文件,使用注解 @GenerateMocks

// user_api_test.dartimport'package:mockito/annotations.dart';import'package:http/http.dart'as http;import'package:flutter_test/flutter_test.dart';import'package:mockito/mockito.dart';import'user_api.dart';// 引入生成的文件import'user_api_test.mocks.dart';@GenerateMocks([http.Client])voidmain(){// ...}

然后在终端运行命令生成代码:

flutter pub run build_runner build 

2.4 编写测试用例

voidmain(){ late MockClient mockClient; late UserApi userApi;setUp((){ mockClient =MockClient(); userApi =UserApi(mockClient);});test('returns name if the http call completes successfully',()async{// 1. Stubbing: 模拟 http.Client 返回 200 OKwhen(mockClient.get(Uri.parse('https://api.example.com/users/1'))).thenAnswer((_)async=>http.Response('{"name": "张三"}',200));// 2. Executionfinal name =await userApi.fetchUserName(1);// 3. Verification: 验证结果expect(name,'张三');// 验证 get 方法确实被调用了一次verify(mockClient.get(Uri.parse('https://api.example.com/users/1'))).called(1);});test('throws an exception if the http call completes with an error',(){// 1. Stubbing: 模拟返回 404when(mockClient.get(Uri.parse('https://api.example.com/users/1'))).thenAnswer((_)async=>http.Response('Not Found',404));// 2. Execution & Verificationexpect(userApi.fetchUserName(1), throwsException);});}
在这里插入图片描述


在这里插入图片描述

三、进阶技巧

3.1 参数匹配 (Argument Matchers)

有时候我们不关心具体的参数值,只关心参数类型。

// 匹配任何 Uriwhen(mockClient.get(any)).thenAnswer(...)// 命名参数匹配when(obj.method(foo:anyNamed('foo'))).thenReturn(...)// 捕获参数进行验证test('captures arguments',(){// ... trigger ...// 验证调用并捕获参数final captured =verify(mockClient.get(captureAny)).captured;print(captured.first);// 输出 Uri 对象});

3.2 顺序验证 (InOrder)

验证多个方法的调用顺序。

test('verify interaction order',(){final cat =MockCat(); cat.eat(); cat.sleep();verifyInOrder([ cat.eat(), cat.sleep(),]);});

3.3 模拟异常与重试逻辑

开发鸿蒙应用时,网络波动是常态。我们可以 Mock 抛出异常来测试 App 的重试机制是否工作。

test('测试重试逻辑',()async{// 第一次调用抛出异常,第二次成功when(mockApi.getData()).thenThrow(Exception('Network Error')).thenAnswer((_)async=>'Success');// 调用业务逻辑...});
在这里插入图片描述

四、OpenHarmony 适配与实战:模拟鸿蒙原生通道 (Platform Channel)

在 OpenHarmony 开发中,我们经常需要通过 MethodChannel 调用系统的原生能力(如获取电池电量、调用 HiLog)。在写 UI 测试或逻辑测试时,我们不能真的去调底层,因为测试环境可能只是一个 Dart VM。

我们可以 Mock MethodChannel 的 handlers。

4.1 场景:电池电量获取

假设我们有一个 BatteryService

import'package:flutter/services.dart';classBatteryService{staticconst platform =MethodChannel('ohos.samples.battery');Future<String>getBatteryLevel()async{try{final int result =await platform.invokeMethod('getBatteryLevel');return'当前电量 $result%';}onPlatformExceptioncatch(e){return"获取电量失败: '${e.message}'.";}}}

4.2 编写测试

Flutter 官方提供了 TestDefaultBinaryMessenger 来模拟底层消息。但在单元测试层面,mockito 也可以派上用场,或者我们可以直接 mock 对 BatteryService 的依赖。

这里我们展示如何使用 mockito 测试一个依赖 BatteryServiceViewModel

// battery_view_model.dartclassBatteryViewModel{finalBatteryService _service;String status ='未知';BatteryViewModel(this._service);Future<void>refresh()async{ status =await _service.getBatteryLevel();}}// battery_view_model_test.dart@GenerateMocks([BatteryService])import'battery_view_model_test.mocks.dart';voidmain(){test('ViewModel updates status from service',()async{final mockService =MockBatteryService();final viewModel =BatteryViewModel(mockService);// Stubbing: 模拟鸿蒙设备返回了 100% 电量when(mockService.getBatteryLevel()).thenAnswer((_)async=>'当前电量 100%');await viewModel.refresh();expect(viewModel.status,'当前电量 100%');});}

4.3 为什么这对鸿蒙开发重要?

鸿蒙设备的 API(如 @ohos.batteryInfo)只能在真机或模拟器运行。如果你的 Dart 代码中混杂了原生调用,一旦离开真机环境就会报错。

通过 Dependency Injection (依赖注入) 配合 Mockito,你可以让 Dart 业务逻辑与鸿蒙原生 API 解耦。

  • 开发时:在 PC 上运行测试,Mock 掉鸿蒙 API,开发效率极高。
  • 运行时:注入真实的 BatteryService,调用鸿蒙底层。

五、总结

mockito 是测试驱动开发(TDD)的利器。它让我们能够隔离关注点,专注于当前模块的逻辑,而不必担心外部世界的复杂性。

对于 OpenHarmony 开发者,掌握 Mock 技术意味着你可以构建出架构更清晰、可测试性更强的应用。无论是模拟网络、数据库,还是模拟鸿蒙特有的系统服务,mockito 都能帮你轻松搞定。

最佳实践

  1. 多用接口:针对 Interface 编程,而不是针对 Implementation。Mock 接口比 Mock 具体类更容易且风险更小。
  2. 不要 Mock 数据类:对于 DTO(Data Transfer Object)或简单的实体类,直接 new 一个真的对象,不要 Mock 它。
  3. 保持测试简单:如果你的 Mock 配置(when…then…)写了几十行,说明被测代码可能耦合太重,需要重构了。

六、完整实战示例:带超时的网络请求模拟器

import'package:mockito/mockito.dart';import'package:test/test.dart';// 1. 业务接口abstractclassNetworkService{Future<String>request(String path);}// 2. 模拟生成的 Mock 类classMockNetworkServiceextendsMockimplementsNetworkService{@overrideFuture<String>request(String? path)=>super.noSuchMethod(Invocation.method(#request,[path]), returnValue:Future.value(""),)asFuture<String>;}// 3. 业务逻辑类classDataManager{finalNetworkService _service;DataManager(this._service);Future<String>safeRequest()async{try{// 模拟 2 秒延迟外的逻辑returnawait _service.request('/data').timeout(Duration(seconds:1));}catch(e){return"Timeout or Error";}}}voidmain(){test('模拟请求超时场景',()async{final mockService =MockNetworkService();final manager =DataManager(mockService);// Stubbing: 让请求延迟 2 秒返回,从而触发业务层的超时when(mockService.request(any)).thenAnswer((_)async{awaitFuture.delayed(Duration(seconds:2));return"Delayed Data";});final result =await manager.safeRequest();expect(result,"Timeout or Error");verify(mockService.request('/data')).called(1);});}
在这里插入图片描述

Read more

深挖 DeepSeek 隐藏玩法·智能炼金术2.0版本

深挖 DeepSeek 隐藏玩法·智能炼金术2.0版本

前引:屏幕前的你还在AI智能搜索框这样搜索吗?“这道题怎么写”“苹果为什么红”“怎么不被发现翘课” ,。看到此篇文章的小伙伴们!请准备好你的思维魔杖,开启【霍格沃茨模式】,看我如何更新秘密的【知识炼金术】,我们一起来解锁更加刺激的剧情!友情提醒:《《《前方高能》》》 目录 在哪使用DeepSeek 如何对提需求  隐藏玩法总结 几个高阶提示词 职场打工人 自媒体创作 电商实战 程序员开挂 非适用场地 “服务器繁忙”如何解决 (1)硅基流动平台 (2)Chatbox + API集成方案 (3)各大云平台 搭建个人知识库 前置准备 下载安装AnythingLLM 选择DeepSeek作为AI提供商 创作工作区 导入文档 编辑  编辑 小编寄语 ——————————————————————————————————————————— 在哪使用DeepSeek 我们解锁剧情前,肯定要知道在哪用DeepSeek!咯,为了照顾一些萌新朋友,它的下载方式我放在下面了,拿走不谢!  (1)

By Ne0inhk
【AI大模型】DeepSeek + 通义万相高效制作AI视频实战详解

【AI大模型】DeepSeek + 通义万相高效制作AI视频实战详解

目录 一、前言 二、AI视频概述 2.1 什么是AI视频 2.2 AI视频核心特点 2.3 AI视频应用场景 三、通义万相介绍 3.1 通义万相概述 3.1.1 什么是通义万相 3.2 通义万相核心特点 3.3 通义万相技术特点 3.4 通义万相应用场景 四、DeepSeek + 通义万相制作AI视频流程 4.1 DeepSeek + 通义万相制作视频优势 4.1.1 DeepSeek 优势 4.1.2 通义万相视频生成优势 4.2

By Ne0inhk
【DeepSeek微调实践】DeepSeek-R1大模型基于MS-Swift框架部署/推理/微调实践大全

【DeepSeek微调实践】DeepSeek-R1大模型基于MS-Swift框架部署/推理/微调实践大全

系列篇章💥 No.文章01【DeepSeek应用实践】DeepSeek接入Word、WPS方法详解:无需代码,轻松实现智能办公助手功能02【DeepSeek应用实践】通义灵码 + DeepSeek:AI 编程助手的实战指南03【DeepSeek应用实践】Cline集成DeepSeek:开源AI编程助手,终端与Web开发的超强助力04【DeepSeek开发入门】DeepSeek API 开发初体验05【DeepSeek开发入门】DeepSeek API高级开发指南(推理与多轮对话机器人实践)06【DeepSeek开发入门】Function Calling 函数功能应用实战指南07【DeepSeek部署实战】DeepSeek-R1-Distill-Qwen-7B:本地部署与API服务快速上手08【DeepSeek部署实战】DeepSeek-R1-Distill-Qwen-7B:Web聊天机器人部署指南09【DeepSeek部署实战】DeepSeek-R1-Distill-Qwen-7B:基于vLLM 搭建高性能推理服务器10【DeepSeek部署实战】基于Ollama快速部署Dee

By Ne0inhk

DeepSeek各版本说明与优缺点分析_deepseek各版本区别

DeepSeek各版本说明与优缺点分析 DeepSeek是最近人工智能领域备受瞩目的一个语言模型系列,其在不同版本的发布过程中,逐步加强了对多种任务的处理能力。本文将详细介绍DeepSeek的各版本,从版本的发布时间、特点、优势以及不足之处,为广大AI技术爱好者和开发者提供一份参考指南。 1. DeepSeek-V1:起步与编码强劲 DeepSeek-V1是DeepSeek的起步版本,这里不过多赘述,主要分析它的优缺点。 发布时间: 2024年1月 特点: DeepSeek-V1是DeepSeek系列的首个版本,预训练于2TB的标记数据,主打自然语言处理和编码任务。它支持多种编程语言,具有强大的编码能力,适合程序开发人员和技术研究人员使用。 优势: * 强大编码能力:支持多种编程语言,能够理解和生成代码,适合开发者进行自动化代码生成与调试。 * 高上下文窗口:支持高达128K标记的上下文窗口,能够处理较为复杂的文本理解和生成任务。 缺点: * 多模态能力有限:该版本主要集中在文本处理上,缺少对图像、语音等多模态任务的支持。 * 推理能力较弱:尽管在自然语言

By Ne0inhk