新闻阅读器
News Reader 是一款简单的 Flutter 应用,可以调用纽约时报热门文章 API,并显示文章列表,当点击列表项时显示详情(一个典型的母/子应用)。
我们将使用该 API 的“最受欢迎”部分。
http://api.nytimes.com/svc/mostpopular/v2/mostviewed/{section}/{period}.json?api-key=sample-key
要测试此 API,您可以使用 URL 中 section 部分的 all-sections,period 部分的 7(可用的 period 值是 1、7 和 30,代表 API 返回结果的时间范围,单位为天)。
http://api.nytimes.com/svc/mostpopular/v2/mostviewed/all-sections/7.json?api-key=sample-key
要求
- Android Studio
- Flutter SDK 2.8.1
API 密钥
要成功连接到应用使用的API,需要一个 API 密钥。获得 API 密钥后,请在 /nyt_flutter/lib/common/contant.dart 中更改 API_KEY 属性,然后运行应用。
应用架构
本示例遵循 BLoC 模式 + Clean Architecture。
模型/实体
模型是领域对象。它代表我们正在处理的实际数据或信息。模型的一个例子可能是联系人(包含姓名、电话号码、地址等)或实时流媒体发布点的特征。
关于模型需要记住的关键是,它包含信息,但不包含操作该信息的行为或服务。它不负责格式化文本以在屏幕上漂亮地显示,或从远程服务器获取项目列表(实际上,在该列表中,每个项目很可能都是它自己的模型)。业务逻辑通常与模型分开,并封装在其他操作模型的类中。
数据层
以模型/实体的形式提供存储库所需的所有数据。
远程数据源
管理所有服务器/外部 API 调用。
本地数据源
管理所有本地数据存储:例如 SQLite 实现、Room、Realm…
Repository
当涉及管理数据 CRUD 操作时,这是决策者类。在此层中可以执行的操作包括缓存机制、管理连续 API 调用等…
用例/交互器
代表业务概念、当前情况信息和业务规则。
BLoC
BLoC 库中有三个主要的小部件
- Bloc
- BlocBuilder
- BlocProvider
您需要它们来设置 BLoCs,根据应用状态的变化构建这些 BLoCs,并设置条件。让我们看看如何实现每个小部件并在您的应用业务逻辑中使用它。
Bloc
Bloc 小部件是实现所有业务逻辑所需的关键组件。要使用它,请继承 Bloc 类并覆盖 mapEventToState 和 initialState 方法。
BlocBuilder
BlocBuilder 是一个响应新状态并通过构建 BLoCs 来做出响应的小部件。此小部件可以调用多次,并且像一个函数一样响应状态的变化,通过创建看起来像新 UI 组件的小部件。
BlocProvider
此小部件充当依赖注入,这意味着它可以一次将 BLoCs 提供给属于同一子树的多个小部件。BlocProvider 用于构建可供子树中所有小部件使用的 bloc。
入门
此存储库实现了以下质量门
- 静态代码检查:运行 lint 来检查代码是否存在任何问题。
- 单元测试:运行 单元测试
- 代码覆盖率:使用 LCOV 生成代码覆盖率报告
- 集成测试:使用 Flutter 集成测试运行功能测试
这些步骤可以手动运行,也可以使用持续集成工具(如 Bitrise)运行。
检出代码
检出并运行代码
git clone https://github.com/oudaykhaled/nyt-flutter-clean-architecture-unit-test.git
主要库/工具
| 类别 | 库/工具 | 链接 |
|---|---|---|
| 开发 | Flutter – Dart | https://flutterdart.cn/ |
| IDE | Android Studio | https://developer.android.com.cn/studio |
| 单元测试 | Flutter 单元测试 | https://docs.flutterdart.cn/cookbook/testing/unit/introduction |
| 代码覆盖率 | LCOV | https://github.com/linux-test-project/lcov |
| 静态代码检查 | Lint for Dart/Flutter | https://pub.dev/packages/lint |
| 集成测试 | Flutter 集成测试 | https://docs.flutterdart.cn/cookbook/testing/integration/introduction |
| CI/CD | Bitrise | https://app.bitrise.io/ |
| 依赖注入 | injectable | https://pub.dev/packages/injectable |
| 服务定位器 | get_it | https://pub.dev/packages/get_it |
| 演示层管理 | flutter_bloc | https://pub.dev/packages/flutter_bloc |
| 网络层管理 | retrofit | https://pub.dev/packages/retrofit |
| 代码生成器 | Freezed | https://pub.dev/packages/freezed |
| HTTP 客户端 | Dio | https://pub.dev/packages/dio |
| 图片缓存 | cached_network_image | https://pub.dev/packages/cached_network_image |
| Mock 库 | Mockito | https://pub.dev/packages/mockito |
设置先决条件
安装 LCOV
在终端运行以下命令 sudo apt-get install lcov
安装 scrcpy
在终端运行以下命令 sudo apt install scrcpy
生成文件
在终端运行以下命令 flutter pub run build_runner watch --delete-conflicting-outputs
运行质量门和部署命令
Linting
测试
Flutter 中的测试分为 2 种类型
测试
位于 /test – 这些是在您的机器上运行的测试。当您的测试没有 Flutter 框架依赖项或您可以模拟 Flutter 框架依赖项时,请使用这些测试来最小化执行时间。

集成测试
位于 /integration_test – 这些是在硬件设备或模拟器上运行的测试。这些测试可以访问所有 Flutter API,为您提供对您正在测试的应用程序 Context 等信息的访问权限,并允许您从测试代码中控制被测应用程序。在编写集成和功能 UI 测试以自动化用户交互时,或者当您的测试具有无法满足的 mock 对象依赖项时,请使用这些测试。


运行单元测试
Flutter 应用程序的单元测试在 Flutter 文档中有详细解释。在本存储库中,
从 Android Studio
- 右键单击类并选择“运行”
- 要查看覆盖率,请选择“使用覆盖率运行”
测试覆盖率
测试覆盖率使用 LCOV 库
为了运行 test 和 integration_test 并生成代码覆盖率报告,请创建一个脚本文件来完成此任务。
red=$(tput setaf 1)
none=$(tput sgr0)
filename=
open_browser=
show_help() {
printf "
Script for running all unit and widget tests with code coverage.
(run it from your root Flutter's project)
*Important: requires lcov
Usage:
$0 [--help] [--open] [--filename <path>]where:
-o, --open Open the coverage in your browser, Default is google-chrome you can change this in the function open_cov(). -h, --help print this message -f <path>, --filename <path> Run a particular test file. For example: -f test/a_particular_test.dart
Or you can run all tests in a directory
-f test/some_directory/"
}
run_tests() {
if [[ -f "pubspec.yaml" ]]; then
rm -f coverage/lcov.info
rm -f coverage/lcov-final.info
flutter test --coverage "$filename"
ch_dir
else
printf "\n${red}Error: this is not a Flutter project${none}\n"
exit 1
fi
}
run_report() {
if [[ -f "coverage/lcov.info" ]]; then
lcov -r coverage/lcov.info lib/resources/l10n/\* lib/\*/fake_\*.dart \
-o coverage/lcov-final.info
genhtml -o coverage coverage/lcov-final.info
else
printf "\n${red}Error: no coverage info was generated${none}\n"
exit 1
fi
}
ch_dir(){
dir=$(pwd)
input="$dir/coverage/lcov.info"
output="$dir/coverage/lcov_new.info"
echo "$input"
while read line
do
secondString="SF:$dir/"
echo "${line/SF:/$secondString}" >> $output
done < "$input"
mv $output $input
}
open_cov(){
# This depends on your system
# Google Chrome:
# google-chrome coverage/index-sort-l.html # Mozilla: firefox coverage/index-sort-l.html
}
while [ "$1" != "" ]; do
case $1 in
-h|--help)
show_help
exit ;;
-o|--open)
open_browser=1
;;
-f|--filename)
shift
filename=$1
;;
*)
show_help
exit ;;
esac shift
done
run_tests
remove_from_coverage -f coverage/lcov.info -r '.g.dart$'
remove_from_coverage -f coverage/lcov.info -r '.freezed.dart$'
remove_from_coverage -f coverage/lcov.info -r '.config.dart$'
run_report
if [ "$open_browser" = "1" ]; then
open_cov
fi
以下行已添加,以便在生成代码覆盖率报告时忽略生成的文件
remove_from_coverage -f coverage/lcov.info -r '.g.dart$'
remove_from_coverage -f coverage/lcov.info -r '.freezed.dart$'
remove_from_coverage -f coverage/lcov.info -r '.config.dart$'
从命令行
sh test_with_coverage.sh
测试覆盖率结果可在
CI-CD – 通过 Bitrise 构建(yml 文件)
此存储库包含一个 bitrise 文件,该文件用于定义 Bitrise 的**声明式管道**,用于 CI/CD 来构建代码、运行质量门、代码覆盖率、静态分析并部署到 Bitrise。
以下是 Bitrise 声明式管道的结构
---
format_version: '11'
default_step_lib_source: 'https://github.com/bitrise-io/bitrise-steplib.git'
project_type: flutter
trigger_map:
- push_branch: '*'
workflow: primary
- pull_request_source_branch: '*'
workflow: primary
workflows:
deploy:
steps:
- activate-ssh-key@4:
run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}'
- git-clone@6: {}
- script@1:
title: Do anything with Script step
- certificate-and-profile-installer@1: {}
- flutter-installer@0:
inputs:
- is_update: 'false'
- cache-pull@2: {}
- flutter-analyze@0:
inputs:
- project_location: $BITRISE_FLUTTER_PROJECT_LOCATION
- flutter-test@1:
inputs:
- project_location: $BITRISE_FLUTTER_PROJECT_LOCATION
- flutter-build@0:
inputs:
- project_location: $BITRISE_FLUTTER_PROJECT_LOCATION
- platform: both
- xcode-archive@4:
inputs:
- project_path: $BITRISE_PROJECT_PATH
- scheme: $BITRISE_SCHEME
- distribution_method: $BITRISE_DISTRIBUTION_METHOD
- configuration: Release
- deploy-to-bitrise-io@2: {}
- cache-push@2: {}
primary:
steps:
- activate-ssh-key@4:
run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}'
- git-clone@6: {}
- script@1:
title: Do anything with Script step
- flutter-installer@0:
inputs:
- is_update: 'false'
- cache-pull@2: {}
- [email protected]:
inputs:
- project_location: $BITRISE_FLUTTER_PROJECT_LOCATION
- flutter-test@1:
inputs:
- project_location: $BITRISE_FLUTTER_PROJECT_LOCATION
- deploy-to-bitrise-io@2: {}
- cache-push@2: {}
meta:
bitrise.io:
stack: linux-docker-android-20.04
machine_type_id: elite
app:
envs:
- opts:
is_expand: false
BITRISE_FLUTTER_PROJECT_LOCATION: .
- opts:
is_expand: false
BITRISE_PROJECT_PATH: .
- opts:
is_expand: false
BITRISE_SCHEME: .
- opts:
is_expand: false
BITRISE_DISTRIBUTION_METHOD: development
以下是 Bitrise 将执行的管道的说明
使用 Bitrise 构建应用程序
应遵循以下步骤来自动化 Bitrise 的应用程序构建
- 在 Bitrise 上创建一个帐户。
- 按照向导在 Bitrise 上创建 Flutter 项目。
- 在
workflows选项卡中,选择<>bitrise.yaml选项卡。 - 选择
Store in app repository以读取存储库 yaml 文件。
此存储库已附加到公共 Bitrise 项目。
许可证
Apache 许可证,第 2.0 版
https://apache.ac.cn/licenses/LICENSE-2.0




