Tauri开发二: 调用后端Rust接口
在[Tauri开发一: Tauri开发工具介绍](Tauri开发一:Tauri开发工具介绍 )我们介绍了如何创建项目
但是项目运行起来后,前端和后端是完全分离的,互相之间完全没有交互
此章节我们主要看前端是如何调用后端的
1. Rust工程结构和Crate概念
在介绍调用后端接口之前,我们先介绍Rust几个组织代码的概念:
- Package
- Crate
- Module
而他们之间的关系为: Package Crate 和 Module 的关系是: Package 包含 Crate , Crate 包含 Module
同时Rust规定了他们三个之间的规则:
- 一个rust工程是一个Package
- 一个rust源码文件默认是一个Moudule:
lib.rs
和main.rs
除外,他们默认是一个Crate - 一个Package只能包含一个library Crate, 但是可以包含多个 binary Crate
2. 创建后端接口
tauri的接口就是Rust的函数,只需要在函数上面添加宏#[tauri::command]
即可
2.1 在main.rs
中创建接口
在main.rs
中增加以下代码:
#[tauri::command]
pub fn greet(name: &str) -> String {
format!("Hello, {}!", name)
}
我们需要告诉tauri这个接口,在main
函数中增加.invoke_handler(tauri::generate_handler![greet])
fn main() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![greet])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
2.2 调用后端接口
在上一篇文正中我们讲过,tauri前端的主要组成就是 @tauri-apps/api
, 他负责和后端的IPC通信
安装 @tauri-apps/api
:npm install @tauri-apps/api
我们创建一个文件src/lib/Greet.svelte
(src是前端代码的目录), 添加下面的代码:
<script>
import { invoke } from '@tauri-apps/api/tauri'
let name = ''
let greetMsg = ''
async function greet() {
greetMsg = await invoke('greet', { name })
}
</script>
<div>
<input id="greet-input" placeholder="Enter a name..." bind:value="{name}" />
<button on:click="{greet}">Greet</button>
<p>{greetMsg}</p>
</div>
然后在src/routes/+page.svelte
里面调用:
<script>
import Greet from '../lib/Greet.svelte'
</script>
<h1>Welcome to SvelteKit</h1>
<Greet />
运行cargo tauri dev
,查看功能是否可用
2.3 拆分代码
我们当前的rust工程,只有一个main.rs
源码文件,这是一个crate,但是crate的名字不是main,main.rs
对应的crate名字就是package的名字,而package的名字是在Cargo.toml
定义的,Tauri默认的是app
[package]
name = "app"
version = "0.1.0"
description = "A Tauri App"
#[tauri::command]
宏只能在binary crate里面使用,也就是main.rs
文件里面,但是如果我们的后端接口非常多的时候,我们的main.rs
就会变得非常臃肿,不方便维护。
记得我们前面说过,crate的下一级是module,因此我们可以把接口相关的内容都移动到一个tauri_commands
Module里面
创建一个文件src-tauri\src\tauri_commands.rs
,将commands的代码移动到里面:
一个rust源文件,默认就是一个Module
#[tauri::command]
pub fn greet(name: &str) -> String {
format!("Hello, {}!", name)
}
同时在main.rs
里面增加 mod tauri_commands
并将.invoke_handler(tauri::generate_handler![greet])
替换为.invoke_handler(tauri::generate_handler![tauri_commands::greet])
正常情况下,到这里我们的介绍应该完成了。
但是作为一个程序员,实际工作中我们的工作量不止这么点,我们有很多的精力花在了修改转测之后的bug上,原因就是因为我们没有做足够的测试,或者说 我们没有吧用户的行为测试,作为我们编码的最终目标。而这正好是TDD解决的问题。
3. TDD开发方式
正常的前后端分离的业务开发中,前后端都是分开测试的,测试使用的工具也不一样。我们当前的后端相当于只有一个API,我们现在就用TDD的方式来继续完善我们的代码
3.1 后端TDD
3.1.1 Rust集成测试介绍
Rust支持单元测试,但是我一直认为,单元测试(unit test)的功能,应该通过用户行为出发,真正的TDD应该只测试用户行为,因此我们这里只使用集成测试(integration test)
Rust的集成测试有一个非常重要的限制: 集成测试只能够测试library Crate
前面我们说过Package包含Crate, 但是Crate也分为两种: Library Crate
和 Binay Crate
.Binay Crate
就是我们前面遇到的main.rs
,这个Crate的名字和Package名字一致,都是在Cargo.toml
里面定义的
而lib.rs
文件对应的就是Library Crate
, 而且 一个工程下面只能有一个Library Crate
如果我们创建工程的时候使用命令:
cargo new my_project --lib
, cargo就会给我们创建一个目录,在src
目录下面不是main.rs
, 而是lib.rs
。
因此当前我们有两个主要的限制:
- 集成测试只能够测试
Library Crate
- 一个工程下面只能有一个
Library Crate
这导致我们需要创建一个lib.rs
作为Library Crate
, 然后我们测试lib.rs
里面的代码
而此时的main.rs
或者所属的Crate仅仅是一个薄薄的封装层,负责调用lib.rs
对应的crate业务代码
如果你写过spring代码,
main.rs
对应的Crate有点类似于Controller,lib.rs
对应的Crate有点类似于业务Service
我们不测试
main.rs
,只测试lib.rs
的方式,有点类似于 Spring TestContext Framework
而直接测试main.rs
有点类似于 Spring WebTestClient
3.1.2 增加业务层(lib.rs
对应的Crate)
创建文件 src-tauri\src\lib.rs
, 增加如下代码:
pub fn greet(name: &str) -> String {
format!("Hello, {}!", name)
}
同时更新我们tauri_commands.rs
为简单的调用:
lib.rs
对应的Crate名称是和main.rs
一致的,都叫做app
, 因此我们调用的时候使用app::greet()
#[tauri::command]
pub fn greet(name: &str) -> String {
app::greet(name)
}
运行一下业务,保证当前正常
3.1.3 TDD开发循环
3.1.3.1 搭建测试环境
集成测试默认的存在路径为src-tauri\tests
,我们新建目录,并创建文件src-tauri\tests\test_greeting.rs
- 切换命令行到
src-tauri
:cd src-tauri
- 运行rust test的命令为
cargo test
- 运行结果: cargo 默认运行三种类型的测试, 我们可以看到第二个测试用例为零
- 单元测试(
Running unittests src\main.rs
) - 集成测试(
Running tests\test_greeting.rs
) - Doc-Tests(
Doc-tests app
)
- 单元测试(
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running unittests src\main.rs (target\debug\deps\app-7826712535976156.exe)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running tests\test_greeting.rs (target\debug\deps\test_greeting-437d74c127a81ecb.exe)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests app
3.1.3.2 编写第一个测试用例
TDD的时候,我们写的一个用例经常是空输入的用例,这个地方对应的就是输入的name
是空字符串
此时我们希望程序返回什么呢,当前的返回是Hello,
,但是这个测试肯定会给你提单,我们应该更人性化一点,比如放回Please input your name!
由于每个测试的rust源码文件,也是一个Crate,因此我们需要引用我们的
Libraay Crate
,才能调用里面的代码
上面提到我们的lib.rs
对应的Crate的名字是在Cargo.toml
里面定义的,而Tauri默认的是app
use app;
#[test]
fn greet_empty_name_correct() {
assert_eq!(app::greet(""), "Please input your name!");
}
运行cargo test
: 可以看到用例失败了,这和我们TDD开发模式是一样的,下一步就是修改代码,让用例通过
Running tests\test_greeting.rs (target\debug\deps\test_greeting-437d74c127a81ecb.exe)
running 1 test
test greet_empty_name_correct ... FAILED
修改lib.rs
代码为:
pub fn greet(name: &str) -> String {
if name.len() == 0 {
return "Please input your name!".to_string();
}
format!("Hello, {}!", name)
}
再次运行cargo test
, 没有失败的用例
3.1.3.3 编写第二个测试用例
我们再从使用者的角度考虑,或者从测试的角度考虑,如果输入的都是空格呢,我们应该希望和什么都不输入的时候,返回的结果一样,因此我们再增加两个测试用例:
#[test]
fn greet_single_space_name_correct() {
assert_eq!(app::greet(" "), "Please input your name!");
}
#[test]
fn greet_multi_spaces_name_correct() {
assert_eq!(app::greet(" "), "Please input your name!");
}
再次运行用例,结果为:新增的两个用例失败
failures:
greet_multi_spaces_name_correct
greet_single_space_name_correct
test result: FAILED. 1 passed; 2 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
error: test failed, to rerun pass `--test test_greeting`
再次修改代码,并运行用例,测试用例全部通过
pub fn greet(name: &str) -> String {
if name.len() == 0 || name.trim().len() == 0 {
return "Please input your name!".to_string();
}
format!("Hello, {}!", name)
}
上面的的过程我们可以一直继续下去,尽量覆盖用户的常用场景:
- 超长输入
- 特殊字符
- ……..
这个就是TDD的开发流程,这样开发,有如下好处:
- 能让我们专注于用户的需求,而不是中间为了各种架构而跑偏
- 保证老的功能一直是可用的,我们总是在前进,没有因为完成了一个新的功能,把前面的都搞坏,搞来搞去,挫折感大增
4. 后续
后续我们会使用上面的流程,来开发一款类似于Adobe Lightroom的照片管理软件,来实现我们特殊的需求,因为Adobe Lightroom更加倾向于摄影爱好者,我们的软件会更加倾向于普通用户,比如以下功能:
- 照片重复导入
- 相似照片的归类显示
- 支持制作抖音视频
同时在过程中会探索Tauri的各种能力: fs events window …
To Be Continued….