每天一个板条箱:has-flag
#javascript #测试 #rust #nodemodules

Pixel art image of a Crab reading a book

欢迎来到“每天一个板条箱” ,我的日记是我深入探讨Rust编程世界的日记。作为具有开源背景的JavaScript开发人员,我决定承担为Rust Community重新编写流行的JavaScript包的挑战。

这是我为这个项目实现的一些TL; DR目标:

  • ð - 学习生锈和流行的JavaScript模块
  • ð�通过出版新的板条
  • ð�ð«与您分享我的学习,技巧和最佳实践

如果您来这里学习如何安装,从头开始启动Rust或为IDE/环境设置工具链,请参见the Rust Handbook!否则,让我们潜入!

板条箱#1:koude0

原始软件包:sindresorhus/has-flag

Sindre的JavaScript软件包体现了单一目的和可重复性的概念。通常,时间仅作为单个导出功能实现。 has-flag在这方面没有什么不同。

审查

此单个功能模块允许开发人员快速检测ARGV中特定标志的存在(参数传递到运行的脚本/二进制文件)。该模块的代码如下:

import process from 'process'; // eslint-disable-line node/prefer-global/process

export default function hasFlag(flag, argv = process.argv) {
  const prefix = flag.startsWith('-') ? '' : (flag.length === 1 ? '-' : '--');
  const position = argv.indexOf(prefix + flag);
  const terminatorPosition = argv.indexOf('--');
  return position !== -1 && (terminatorPosition === -1 || position < terminatorPosition);
}

使用此模块,我们可以根据传递到脚本中的内容轻松检查特定参数(“ flag”)。这是README的一个例子:

// foo.js
import hasFlag from 'has-flag';

hasFlag('unicorn');
//=> true

hasFlag('--unicorn');
//=> true

hasFlag('f');
//=> true

hasFlag('-f');
//=> true

hasFlag('foo=bar');
//=> true

hasFlag('foo');
//=> false

hasFlag('rainbow');
//=> false
$ node foo.js -f --unicorn --foo=bar -- --rainbow

方法

为了开始我的旅程,我决定首先制作单位测试。这样,我可以向后工作,直到有解决方案。

学习#1:货物使用约定

Cargo是用于运行测试,安装软件包/依赖项的开箱即用工具,以及更多的公约用于构建库与二进制文件。

默认情况下,货物在工作空间中寻找两个约定之一:

  • 库:如果您要构建锈库(“板条箱”),则货物将在工作区中强制执行src/lib.rs的存在。

  • 二进制文件:如果您要建造生锈的二进制文件,则货物将强制执行src/main.rs的存在。

我喜欢这个!每次我打开一个生锈项目时,我都会知道 从哪里开始/开始读取代码。

学习#2:如何编写测试

接下来,我学会了如何在Rust中编写测试。从我查看的所有软件包中,根据Rust Handbook,单位测试均在同一文件中编写 !此外,您会在锈测试中看到宏的用法。 宏是在编译时间创建代码扩展的强大工具,从编写样板代码的行中为您节省了。

以我在模块中写的以下测试为例:

// Macro for setting up a test module
#[cfg(test)]
mod tests {
    // Gives access to outer scope (not just the `pub fn`'s)
    // great to test functions used in Dependency Injection
    use super::*;

    // Macro that turns a function into a unit test
    #[test]
    fn args_with_value_not_matching_double_dash() {
        let args = vec!["--foo", "--unicorn=rainbow", "--bar"];
        let expected_value = "unicorn=rainbow";

        assert!(_has_flag(
            args.into_iter().map(ToString::to_string),
            expected_value
        ))
    }
}
  • koude4 macro:这是我们设置测试模块的宏。它只有在运行cargo test

  • 时才指示Rust编译和运行测试代码
  • koude6 macro:此宏将功能转换为单位测试!您将使用assert!()assert_eq!()之类的宏来验证要测试的代码结果。

  • use super::*:将外部范围暴露于您的inner test module。地球允许在测试功能中使用的外部范围中定义的任何内容(pub fn未定义的任何内容)!

学习3:与std::env合作

实施测试后,我决定在编写此模块时做一个破解。非常感谢Steve Klabnik帮助我获得了功能和设置依赖注入的类型,这是 First 迭代:

pub fn has_flag(flag: &str) -> bool {
    _has_flag(std::env::args(), flag)
}

fn _has_flag<I: Iterator<Item = String>>(mut args: I, flag: &str) -> bool {
    let prefix = if flag.starts_with('-') {
        ""
    } else {
        if flag.len() == 1 {
            "-"
        } else {
            "--"
        }
    };

    let position = args.position(|arg| arg == format!("{}{}", prefix, flag));
    let terminator_position = args.position(|arg| arg == "--");
    position.is_some() && (!terminator_position.is_some() || position < terminator_position)
}
  • koude12:这是您可以访问argv或该程序开始的论点的方式。 stdRust standard library,默认情况下可用于所有Rust板条。

  • koude15:在迭代器中搜索元素,并返回其索引。我认为这与JavaScript中的.indexOf相当。 但是,我在代码中犯了一些错误,我将解释为什么

  • koude17 & koude18:Rust中没有null的概念。相反,选项是一种旨在处理返回的无值的类型。在我们的代码的一些部分中,我们不在乎索引是什么,而不是索引存在.is_some()是这里的理想用途。

学习4:可变性和错误!

我最初发布和发布的此代码实际上有一些错误!,但是,我的初始测试正在通过。我创建了一个GitHub Issue,当我有时间并实现has-flag的测试套件时回来。

我的PR introducing the full tests失败了,所以我知道代码本身必须有一个错误。在The Rust Programming Language Discord上的“初学者”频道获得了一些反馈之后,我开始意识到自己犯了一些错误。这是通过测试的更新代码:

pub fn has_flag(flag: &str) -> bool {
    _has_flag(std::env::args(), flag)
}

fn _has_flag<I: Iterator<Item = String>>(args: I, flag: &str) -> bool {
    let prefix = if flag.starts_with('-') {
        ""
    } else if flag.len() == 1 {
        "-"
    } else {
        "--"
    };

    let formatted_flag = format!("{}{}", prefix, flag);
    args.take_while(|arg| arg != "--")
        .any(|arg| arg == formatted_flag)
}
  • mut args => args:第一个代码气味我应该在写第一次迭代时注意到,Rust Compiler迫使我在我的args参数旁边添加mut,以便功能。这对我没有意义,因为仅当我正在突变args时才需要mut

  • .position()突变:这导致我注意到.position() 我想使用的功能。 .position() 在迭代器中消耗项目,直到匹配谓词为止。因此,确定terminator_position的代码实际上是访问了突变的args。我的所有涉及-- Args终结器的测试都失败了!

  • koude34:采用一个迭代器,并通过它运行 谓词为真,然后返回 是这些项目的迭代器,直到谓词为止。 我想到的类似于迭代器 filter 。这是我们在这里使用的完美选择,因为我们不想匹配--终结者之后通过的ARGS。它也大大简化了我们的代码!

运送ITð

这种迭代通过了我们的所有测试,我能够愉快地合并并发布new release of koude0。当我继续进行work on this journal时,我将遇到许多有关Rust的挑战和学习,我迫不及待地想与您分享它们。

资源