Больше никаких неудобных тестов. Вот одна тестовая среда на основе приспособлений, которая очистила мои модульные тесты 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
Попробуйте, и я буду рад услышать от вас!
Рекомендации
- Извлечение признаков — Википедия
- Будущее — Rust Doc
- Ржавчина — команда Rust
- rstest — GitHub
- Испытательное приспособление — Википедия
- Вой — GitHub
- WebAssembly — WebAssembly.org
- Встраивания — Google для разработчиков
Want to Connect? This article was originally published on Daw-Chih's website.
Повышение уровня кодирования
Спасибо, что являетесь частью нашего сообщества! Перед тем, как ты уйдешь:
- 👏 Хлопайте за историю и подписывайтесь на автора 👉
- 📰 Смотрите больше контента в публикации Level Up Coding
- 💰 Бесплатный курс собеседования по программированию ⇒ Просмотреть курс
- 🔔 Подписывайтесь на нас: Twitter | ЛинкедИн | "Новостная рассылка"
🚀👉 Присоединяйтесь к коллективу талантов Level Up и найдите прекрасную работу