Юнит-тесты в руст. Как сделать правильно?

Продолжаю изучать раст.
В настоящее время у меня код такой:

src/lib.rs:

#![allow(stable_features)]
#![allow(dead_code)]
#![feature(static_recursion)]
#![allow(non_upper_case_globals)]
use std::ffi::CStr;
use std::os::raw::c_char;

mod t;

struct Man {
    name: &'static str,
    car: &'static Car,
}

struct Car {
    owner: &'static Man,
    id: i32,
}

static vasya:Man = Man{car: &vaz, name: "Vasya"};
static vaz:Car = Car{owner: &vasya, id:30};

static vova:Man = Man{car: &mers, name: "Vova"};
static mers:Car = Car{owner: &vova, id:31};

static all:[&Man; 2] = [&vasya, &vova];

#[no_mangle]
pub extern fn get_id_by_name( name: *const c_char) -> i32{

    let need_name = unsafe{ CStr::from_ptr(name) }.to_str();
    match need_name {
        Ok(n) => {
                println!("query name = {}", n);
                for x in all.iter() {
                    if n == x.name {
                        return x.car.id;
                    }
                } 
                0
        }
        ,
        Err(e) => {
            println!("error {}", e);
            -1
        },
    }
}

src/t.rs:

#[cfg(test)]
mod tests {
    use get_id_by_name;
    use std::ffi::CString;

    #[test]
    fn test_get_id_by_name(){
        println!("тест начался!");
        let s = CString::new("Vova").unwrap();
        let id1 = get_id_by_name(s.as_ptr());
        println!("id={}", id1);
        assert_eq!(id1, 32);
        println!("тест завершен!")
    }

    #[test]
    fn test_get_id_by_name2(){
    }

}

Cargo.toml:

[package]
name = "name2id"
version = "0.1.0"

[lib]
name = "name2id"
path = "src/lib.rs"
test = true
crate-type = ["cdylib"]

main.c:

#include <stdio.h>
extern int get_id_by_name(const char * name);
int main(){
    int id1 = get_id_by_name("Vasya");
    int id2 = get_id_by_name("Vova");
    int id3 = get_id_by_name("zzz");
    printf("id1=%d id2=%d id3=%d\n", id1, id2, id3);
}

Сишник собираю командой: gcc main.c -L . -lname2id (предварительно руками подложив к сишнику собранный so-шник)

Значит, что мне тут не нравится.

  1. Строчка mod t в фале lib.rs, которой я загружаю модуль юнит тестов. Получается, что как бы либа должна знать о своих тестах. Но ведь должно быть наоборот: это надо в тесте ссылаться на либу. А либа о тестах, которыми ее тестируют, не должна знать ничего! Как такое сделать? В идеале хотелось бы как-то сказать об этом в Cargo.toml, что мол вот у тебя тесты, а вот тут - либа. Но сколько я ни читал документации, не нашел такого.

  2. Есть подозрение насчет строчки:
    let need_name = unsafe{ CStr::from_ptr(name) }.to_str();
    Собственно подозрение - а правильно ли я делаю? У меня такое подозрение, что компилятор не ругается потому, что стоит unsafe, но вообще так делать нельзя, т.к. все уничтожается при выходе из { }, а потом я беру to_str() от уже уничтоженного объекта.

  3. Чем в понимании руста отличаются юнит-тесты от интеграционных тестов? Для интеграицонных руст выполняет какие-то предварительные действия? Или с этой точки зрения они вообще ничем не отличаются?

  4. На самом деле, данный вопрос идет уже не конктексте сравнения rust и си, а в контексте сравнения с java, но тем не менее. Есть ли возможность подложить на время теста mock-обьект? Впринципе, я понимаю, что раз я могу либу утянуть и использовать вне растового окружения, то такая возможность, очевидно, есть. Для этого функции, которые нужно при тестах подменять, надо будет обьявить как extern и их можно линковать с разными либами при тестах и для релиза. Это понятно. Вопрос в том, а можно ли такое делать средствами rust?

s/руст/раст/ :slight_smile:

  1. Gexon уже кинул ссылку, где про директорию tests написано. Так как раз библиотека о тестах знать не будет.

  2. Вроде все ок, никакого уничтожения CStr до to_str() не должно происходить.

  3. Если я ничего не путаю, то технически они ничем не отличаются.

  4. Кмк, идиоматичным считается все сущности, которые может потребовтаься подменить, использовать в коде через типажи и реализовывать эти типажи для подделок.

Хотя я натыкался на разной степени готовности штуки типа https://github.com/kriomant/mockers, но хз насколько они полезны на практике.

Кстати, если сишный код ожидает int, то опасно на стороне ржавчины использовать i32 (мало ли на какой архитектуре код будет собираться и какого размера там будет int). Лучше взять std::os::raw::c_int.

Почему его сообщение скрыли?

Да, это я думал, что тесты в tests и в src технически чем-то отличаются.
А как сослаться из теста, который в tests на либу? Перенес свой t.rs в tests, и вот такую ошибку получаю:

10 |         let id1 = get_id_by_name(s.as_ptr());
   |                   ^^^^^^^^^^^^^^ not found in this scope

Перепобовал множество вариантов, ничего не получается. Видимо потому, что это so-шник, и он просто не живет в экосистеме rust,

просто use того что тебе надо, по-идее

да… Это потому, что я собираю внешнюю либу. cargo так не умеет.
Я выкачал этот проект cgmath, ввел cargo test - все отработало. Тогда я поправил toml чтобы он делал внешнюю либу - и снова сделал cargo test - и на этот раз оно выдало ошибки.

Вообще, наверное, если хочется тестить so-шник, то логично его рассматривать не как часть проекта, а как внешнюю нативную либу. Соответственно, и линковаться с ней как будто это вообще какой-то внешний so-шник.

можно вот так: gist

Да, так работает. Спасибо.
Собственно, строчкой crate-type = [“rlib”, “cdylib”] мы говорим, что хотим оба типа либ, и тогда все прокатывает.