Больше никаких неудобных тестов. Вот одна тестовая среда на основе приспособлений, которая очистила мои модульные тесты Rust.

В этой статье

  • 🔬 Изучим ограничения #[test] в Rust.
  • 🪄 Мы рассмотрим альтернативу написанию более чистых модульных тестов с помощью фикстур.
  • 🏗 Мы проведем рефакторинг модульных тестов в одном из моих проектов с открытым исходным кодом!

Пойдем.

Проблема приспособления

Испытательные приспособления очень эффективны для проведения воспроизводимых тестов. Приспособление теста может быть константой или функцией, которая инкапсулирует зависимость теста.

Я хотел создать несколько тестовых фикстур для своих модульных тестов в одном из моих проектов с открытым исходным кодом под названием Вой. Это семантический движок Webassembly, написанный на Rust. Что он делает, так это извлекает функции с использованием моделей машинного обучения, создает индекс и предоставляет функцию запроса, которая позволяет пользователям выполнять поиск в индексе на основе значения и семантики. Вот краткая демонстрация:

Вы можете найти репозиторий Voy на GitHub! Не стесняйтесь попробовать это. Репозиторий включает примеры, на которых вы можете увидеть, как использовать Voy в разных фреймворках.

Пока я все еще работал над извлечением функций, мне понадобились предварительно сгенерированные вложения для тестирования индексной и запрашивающей части движка. Крепление для встраивания выглядит так:

pub static EMBEDDING: [[f32; 768]; 6] = [
    [
        0.01960003957247733,
        -0.03651725347725505,
        0.03361894761373059,
        // ...
    ]

Я создал статическую переменную для 6 вложений в массив. Каждый элемент представляет собой 768-мерный вектор, представляющий предложение.

Чтобы использовать прибор, я просто импортировал его в свой тестовый модуль. Мой модульный тест выглядит так:

use super::engine_fixtures::{EMBEDDING, CONTENT, QUESTION};
use crate::engine::{add, index, remove, search, Query};
use crate::{EmbeddedResource, Resource};

fn get_resource(k: usize) -> Resource {
     let embeddings = EMBEDDING
         .iter()
         .take(k)
         .enumerate()
         .map(|(i, x)| EmbeddedResource {
             id: i.to_string(),
             title: CONTENT.get(i).unwrap().to_string(),
             url: "".to_owned(),
             embeddings: x.to_vec(),
         })
         .collect();
     Resource { embeddings }
 }
#[test]
 fn it_returns_vector_search_result() {
     let resource: Resource = get_resource(6);
     let index = index(resource).unwrap();
     let query = Query::Embeddings(QUESTION.to_vec());
     let result = search(&index, &query, 1).unwrap();
     assert_eq!(result.get(0).unwrap().title, CONTENT[0]);
     assert_eq!(result.get(1).unwrap().title, CONTENT[1]);
     assert_eq!(result.get(2).unwrap().title, CONTENT[2]);
     assert_eq!(result.get(3).unwrap().title, CONTENT[4]);
     assert_eq!(result.get(4).unwrap().title, CONTENT[5]);
     assert_eq!(result.get(5).unwrap().title, CONTENT[3]);
 }

Я создал еще 2 приспособления для предложений, сгенерированных встраиваниями, и встраивание вопроса для выполнения запроса.

Тестовый случай сработал. Приспособление дало повторяемые результаты. Тем не менее, это кажется немного не так. Не очень чисто:

  • Мне нужна была вспомогательная функция для «get_resource» в тестовом случае, чтобы инициировать фикстуру.
  • Приспособления не были инкапсулированы в модульном тесте, и их использование было разбросано.

Поэтому я начал искать лучший способ написать тест.

Представляем первый ящик

rstest позволяет очень легко писать модульные тесты с фикстурами. Все, что вам нужно сделать, это:

Определение приборов

#[fixture]
pub fn fixture() -> u32 { 42 }

Замена #[test] макросом #[rstest].

#[rstest]
fn should_success(fixture: u32) {
    assert_eq!(fixture, 42);
}

Вы можете проверить репозиторий rstest, чтобы найти другие способы написания тестовых фикстур.

Теперь давайте воспользуемся rstest для рефакторинга приведенных выше фикстур. Я просто создам функцию для возврата статической переменной. Так:

src/engine/tests/fixtures.rs

use rstest::fixture;

#[fixture]
pub fn embedding_fixture() -> [[f32; 768]; 6] {
    EMBEDDING
}

Мы также можем реорганизовать вспомогательную функцию «get_resource» как фикстуру:

src/engine/tests/fixtures.rs

#[fixture]
pub fn resource_fixture() -> Resource {
    let content = content_fixture();
    let embeddings = embedding_fixture()
        .iter()
        .enumerate()
        .map(|(i, x)| EmbeddedResource {
            id: i.to_string(),
            title: content.get(i).unwrap().to_string(),
            url: "".to_owned(),
            embeddings: x.to_vec(),
        })
        .collect();
    Resource { embeddings }
}

Поскольку каждая функция фикстуры — это просто функция, мы можем использовать их внутри другой функции фикстуры, как мы видели в функции «resource_fixture» выше.

Чтобы использовать фикстуры в тестовом примере, нам нужно импортировать фикстуры и внедрить их в качестве параметров:

src/двигатель/тесты/mod.rs

use fixtures::*;
use rstest::*;

#[rstest]
fn it_returns_vector_search_result(
    resource_fixture: Resource,
    question_fixture: [f32; 768],
    content_fixture: [&'static str; 6],
) {
    let index = index(resource_fixture).unwrap();
    let query = Query::Embeddings(question_fixture.to_vec());
    let result = search(&index, &query, 6).unwrap();

    assert_eq!(result.get(0).unwrap().title, content_fixture[0]);
    assert_eq!(result.get(1).unwrap().title, content_fixture[1]);
    assert_eq!(result.get(2).unwrap().title, content_fixture[2]);
    assert_eq!(result.get(3).unwrap().title, content_fixture[4]);
    assert_eq!(result.get(4).unwrap().title, content_fixture[5]);
    assert_eq!(result.get(5).unwrap().title, content_fixture[3]);
}

Убедитесь, что параметры имеют те же имена, что и приборы. rstest использует имена для внедрения и инициирования фикстур в тестовых функциях.

Давайте запустим «грузовой тест», чтобы увидеть, работает ли он.

Оно делает!

Последние мысли

rstest имеет более удобные функции. Например, вы можете написать несколько тестовых случаев с помощью #[case]:

use rstest::rstest;

#[rstest]
#[case(0, 0)]
#[case(1, 1)]
#[case(2, 1)]
#[case(3, 2)]
#[case(4, 3)]
fn fibonacci_test(#[case] input: u32, #[case] expected: u32) {
    assert_eq!(expected, fibonacci(input))
}

Вы также можете испытать Будущее!

use rstest::*;
#[fixture]
async fn base() -> u32 { 42 }

#[rstest]
#[case(21, async { 2 })]
#[case(6, async { 7 })]
async fn my_async_test(#[future] base: u32, #[case] expected: u32, #[future] #[case] div: u32) {
    assert_eq!(expected, base.await / div.await);
}

Это всего лишь несколько примеров, которые я взял из rstest. Он имеет больше функций, основанных на фикстурах, которые помогают нам структурировать более чистые модульные тесты с помощью тестовых фикстур. Мне на самом деле понравилось.

Вой — семантическая поисковая система с открытым исходным кодом в WebAssembly. Я создал его, чтобы дать возможность большему количеству проектов создавать семантические функции и улучшать пользовательский опыт для людей по всему миру. Вой следует нескольким принципам дизайна:

  • 🤏 Крошечный. Сократите накладные расходы для ограниченных устройств, таких как мобильные браузеры с медленной сетью или IoT.
  • 🚀 Быстро: создайте лучший поиск для пользователей.
  • 🌳 Tree Shakable: оптимизируйте размер пакета и включите асинхронные возможности для современных веб-API, таких как Web Workers.
  • 🔋 Resumable: создавайте переносимый индекс встраивания в любом месте и в любое время.
  • ☁️ По всему миру: запустите семантический поиск на пограничных серверах CDN.

Он доступен на npm. Вы можете просто установить его с помощью своего любимого менеджера пакетов, и вы готовы к работе.

# with npm
npm i voy-search

# with Yarn
yarn add voy-search

# with pnpm
pnpm add voy-search

Попробуйте, и я буду рад услышать от вас!

Рекомендации

Want to Connect?

This article was originally published on Daw-Chih's website.

Повышение уровня кодирования

Спасибо, что являетесь частью нашего сообщества! Перед тем, как ты уйдешь:

  • 👏 Хлопайте за историю и подписывайтесь на автора 👉
  • 📰 Смотрите больше контента в публикации Level Up Coding
  • 💰 Бесплатный курс собеседования по программированию ⇒ Просмотреть курс
  • 🔔 Подписывайтесь на нас: Twitter | ЛинкедИн | "Новостная рассылка"

🚀👉 Присоединяйтесь к коллективу талантов Level Up и найдите прекрасную работу