Rust `tests/`folder's external scope vs public interface

Is there any way to allow the tests in the tests/ folder to access pub(crate) functions?

One of the major problems I run into with Rust is that I am not able to test internal functions from the tests/ folder without adding the function to the library's public API, which I do not want.

Now the problem is that I have about 500 lines of code split up into 2 files. The functions in these two files are responsible to parse a regex and return an ast for the regex. However, I also have about 780 lines of tests, which test a single function, the function responsible to orchestrate the regex parsing, and it's result, the ast.

Since the libraries overall goal is to write a regex engine I do not want to add the parsing function to the public api. But the current alternative to adding the function to the public interface, is to have the file with the parsing function be more than 1000 lines of code. Which I find to be even worse (- and it is a problem, which I face in most of my crates).

What is the correct way to deal with these kind of tests? I would just love to make these functions pub(crate) and then be able test them from within the test/ folder, is there a way to do that? (And why is Rust not allowing that?)

I would put tests in a submodule to what you are testing.

Submodule tests usually look something like this, if you're not familiar:

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn my_test() {
        ...
    }
}

The #[cfg(test)] ensures that your tests don't get compiled as part of a normal build.

3 Likes

@RustyYato Sure, I could, but that has its own drawbacks:

  • My tests are now mangled in the source code. So if you want to check out the tests, you will have to hunt for them.
  • When compiling for production, the compiler still has to read and discard the tests.
  • If other people want to contribute to the library, they need to be explained where the tests are (Different files in order to keep the file size down). That is not developer friendly.

This is not ideal either... (adding unneeded functions to the public interface still seems to be the better deal.)

There still is the possibility to move the code into another library, but creating a library for a small piece of code, which does not need to be shared, does not really make sense to me. Maybe that is just the previous experience which I have to unlearn, but unfortunately, I do not see the benefit of this handling.

@17cupsofcoffee It still keeps the test in the wrong place in my opinion.

Actually, there is one more possibility: adding a small wrapper library around the tested library, which just exposes the functions needed. Maybe that is the best way of doing it.

Thereby, the wrapper is the official library and has no logic, but exposes the functions of the logic library where the tests can be kept away from the production code.

mod tests { ... } inline is the same as having a file tests.rs and mod tests; in its parent, so you can just put the tests in an external file.
It's not mangled anymore than having it in tests/, you can #[cfg(test)] it out, therefore the compiler will not need to do extra work for normal builds, and it is in a file clearly named for what it contains.

1 Like

If you do it like @17cupsofcoffee and @jer has shown, then your tests won't even be compile if you build for production, and the code will be in a separate file, thus you can search for tests by looking for files named test.

This pattern is rather common in Rust, so I don't see this as a problem. I find it more problematic to have users of your api try and use internal functions that they shouldn't touch.

side note: If you are dealing with even remotely unsafe code, you will have to use this way if you want to provide a safe interface. Because of this, the pattern of embeding tests in submodules is common. Also with a decent editor you can search for all submodules named test rather easily.

This still puts code that shouldn't be in the public interface in the public interface, so is sub-optimal.


Another benefit of putting tests near what they test is it allows easy checking that the test code is up to date and it visually shows what the code is testing. Where as if the code was in a separate module, it wouldn't always be clear what a test is testing without further investigation and tests may go stale because of other changes in the code-base.

2 Likes

Sometimes the easiest solution is the best add playing hide and seek. Thank you very much, you are a genius.

So to sum the answer up instead of putting the tests in a tests/ folder, I can just put the tests in a src/tests/ folder and everything will work as expected.

One thing I forgot is that you can mark all of your functions that you need to test as pub(crate) that way you can access them anywhere in the crate. Then in the crate root you can make a test module that is separate from the production code, but still test all of the "private" functions. There, no private code in public interface and a separate tests file.


This still feels icky to me, I would just put your tests in a sub-module near the code it is testing, that way I can make things as private as I need them to be. But this does solve your problem nicely.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.