Tauri开发二: 调用后端Rust接口

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.rsmain.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/apinpm install @tauri-apps/api
我们创建一个文件src/lib/Greet.sveltesrc是前端代码的目录), 添加下面的代码:

<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,查看功能是否可用
Image

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 CrateBinay 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

  1. 切换命令行到src-tauri: cd src-tauri
  2. 运行rust test的命令为cargo test
  3. 运行结果: 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….

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYGNy64L' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片