Unlock the Power of Property-Based Testing in Rust
When it comes to software testing, methodologies and techniques vary greatly in their practicality and effectiveness. In this article, we’ll delve into the world of property-based testing (PBT) and explore how it can be applied in Rust.
What is Property-Based Testing?
To illustrate how PBT works, let’s consider a basic unit test. Imagine you’ve written a function, maybe_works
, that you want to compare against a function you know works properly, definitely_works
. A unit test comparing these two functions for some input would look like this:
But there’s a catch. You need to know exactly which inputs to use in your tests to alert you to the presence of bugs. This becomes tedious if you want to test maybe_works
against definitely_works
for a variety of inputs.
A More Efficient Approach
A more efficient way to test maybe_works
against definitely_works
would be to run the test several times with a variety of randomly generated inputs. With these randomly generated inputs, you don’t know the precise value being supplied to maybe_works
or definitely_works
, but you can often make a general statement, such as “the outputs of maybe_works
and definitely_works
should be the same given the same input.”
Getting Started with Proptest
Let’s create a new Cargo project called sentence-parser
. We’ll use the pest
crate to write the parser, so add pest
to your Cargo.toml
. Create a file called sentence.pest
and put it in the src
directory.
The pest
crate generates a parser from a user-defined grammar file, so we’ll define a grammar file that tells pest
how to parse a certain type of input.
Writing a Parser with Proptest
Now that we have our grammar file set up, let’s write a parser using proptest
. We’ll start by creating a strategy that produces a valid word.
Next, we’ll create a test that attempts to parse the generated word using the Rule::word
rule.
Testing the Parser
Now that we have our parser set up, let’s test it with some invalid inputs. We’ll create a test that feeds non-letter characters to the parser as well as strings of length zero.
Building on the Words Rule
Next, we’ll create a words
rule that matches sequences of words joined by spaces. We’ll follow the same pattern as before to test whether we’ve created a good rule.
Enclosed Rule
Now we’re going to build on the words
rule by creating an enclosed
rule that simply wraps a words
in some kind of delimiter, such as commas or parentheses.
Chunk Rule
The words
and enclosed
rules are similar in the sense that they both represent sequences of words. To encode this similarity, we’ll make a chunk
rule that matches either words
or enclosed
.
Punctuation Rule
Since sentences end with punctuation, let’s create a rule that matches punctuation.
Sentence Rule
It’s finally time to put it all together into a sentence
rule. The SOI
and EOI
in this rule make sure the entire input is parsed, so inputs like a b c.def
aren’t valid sentences.
Conclusion
At this point, you should have a passing understanding of how to write property-based tests. PBT isn’t always the answer, but the simple act of thinking about the abstract properties of your code can help you better understand it.
Want to learn more about LogRocket and how it can help you debug your Rust applications? Check out our article on Full Visibility into Web Frontends for Rust Apps.