Check out a free preview of the full Polyglot Programming: TypeScript, Go, & Rust course

The "Testing in Rust" Lesson is part of the full, Polyglot Programming: TypeScript, Go, & Rust course featured in this preview video. Here's what you'd learn in this lesson:

ThePrimeagen demonstrates testing the projector project using Rust. Rust provides features specifically for writing tests, including the test attribute, a few macros, and the should_panic attribute.


Transcript from the "Testing in Rust" Lesson

>> Time for the greatest, time to do rust. You wanna guess how I feel about rust testing?
>> Terrible.
>> I feel terrible. Wait, fantastic, I feel fantastic, that's how I feel. All right, so I'm not gonna let you see the code quite yet. Let's jump on to rust, I feel like we're all gonna have a great time.

Rust or bust is the phrase we like to say. So let's do this, I'm definitely not a rustacian at heart. The funny thing is that I hated rust. So before we go on, I would like to say that I was the biggest hater of rust, I have great memes.

The Spider Man crying meme, where you have to say something tough and then she cries, right? She's ready to hear it, I had one with Peter Parker saying, rust is not a feature, cuz every single library that uses rust listed as a feature. It's built in rust so it's fast, right?

And I just thought it was just such a stupid language. Now look at me, now I think it's the greatest language. So you can, too, trust me. It's a pretty good time, I think you'll enjoy it. So let's do that, let's jump over to config and do exactly what I said we're going to do which is, test.

So one thing that I do find a little bit, wait did we not finish this yesterday? There was a bug right there, no one even noticed. Guys, come on. I feel like we should have been there saying things, why didn't we read our warnings, right? This is why you read warnings because they're very very positive for your life.

Also really good thing is that if you come from a C++ background, how many warnings do you get, right? You get all sorts of warnings for a lot of things or you get no warnings at all or there's actual warnings that should really be errors. Such as if you have a function that returns an int and you return nothing.

It's just like, hey, I hate to bother you but your function is not returning anything. But don't worry, I compiled your program here, you can run it anyways. Right, I've had plenty of problems like that where it's Rust. Every time you see a warning, besides for import statements, it's actually usually a pretty good thing you should look at, and make sure when you compile you have no warnings.

So and the nice part about the import statement warnings you can just execute cargo cleanup or cargo clean. And it will just clean it all for you and you don't have to worry about that, so very nice. All right, so let's do some testing. So to start off a test, we're gonna be using one of these proc macros, and to do cfg(test).

A config is effectively, you can go look it up, it's just this thing that exists that helps you have some key values and do some other stuff. And obviously this macro does a lot of things in which I have no idea what it does. And I'm sure if we go look at that macro, it will be disgusting which my jump to definition did not work.

So let's just go mod test so we're creating a new module, mod stands for module, it does not stand for modulo which is a math operator. And in here we can create functions that as long as we have the proc macro test on him, they will be executed as a test.

So I can just go in here and go test, right? And this is a test, right, there you go. So anything that runs in here, will be ran. If we jump over here and go cargo test, it will go through, run all the files, looking for everything. And, ultimately, just say hey there was one test it was called, this is a test, and it passed.

I do really like the output here, I feel like it's nice and clean, right? You have exact knowledge of where everything was, nothing happened in this file because there's nothing to happen, it just gives you the full printout, I like it a lot. All right, so let's go back in here and let's create the add, and the remove, and all that fun stuff.

So let's go test print, right, test print all, let's do a little test print all. And so we gotta do the exact same thing that we did before. We need to be able to create an options and convert it into a config, and then ensure that it's actually doing what it's supposed to be doing.

Now, remember that conversion does throw an error. So if that's the case, what can we do here? Well, we can definitely return an error here. And then we can use all those nice question mark operators if we want to use it, right? We don't expect an error. Well, I don't need to test if there's an error, I simply throw in that question mark.

If there's an error, it'll surface it to me, else I'll just get the value out. So kind of a nicer way to go about it and make sure you use the anyhow result, cuz that's the one that's gonna have all of our errors, our anyhow errors. It'll make this error a lot easier to use.

And of course, we're gonna return the empty nothing. So at the bottom, you can do this, and if you're super functional, erase the semicolon and do this and then you can laugh and elixir. Everything's great and you're just much better than me, but I'm gonna keep doing this, there we go.

So let's create our ops object ops. Yeah, lets, opts =, we need a opt, right? So, where are you? You're right here, and we just need to create this thing, right? So, we can do something like this, and we just need to add in all the fields, it's still having this really weird problem over here, but we'll just ignore it.

args, config, present working directory. So, those are the three things we need. So, args, of course, is gonna be a vector, so I can just go vec bang. It knows its type, therefore it can kinda fill itself in, we don't have to think about it, it just does the right thing.

We have present working directory, I'll just put none because it's going to go and do the right thing for me. It's the non type, it's the equivalent of null if you will. The only difference is that you get beautiful methods on your null versus nothing, right? Look at that, it can be an iterator, it can be a map, it has all these great things.

There we go, so now we have our opts object, so now we can convert it into a config. So now I can just do a quick into and call it a config. Now you'll notice something, I don't know why it's not doing that, there you go. You'll notice that we actually have to import it in.

So despite having a submodule inside of here, we still have to import the thing in, that's just part of how Rust kind of works. So there we go, so now we've imported it in, into is not the one we should be using, we should be using try into.

That's right because ours was an error, and then of course throw nice one of these out. Now we have our unwrapped error, if there was an error, it'll just automatically surface it to us, we'll know what happened, and we have these options. So now we can do a nice little assert_eql, and we can do something like opts.operation, and we can do some sort of equals on it.

So I would expect this thing to be an Operation, Print, with a None type, right? That is my expectation, but it's saying hey, I don't know this thing, so we got to import that thing. And again, what's going on here? We have a new error. So what is this error?

Effectively it just says hey, we cannot compare. So a beautiful part about Rust is there are ways you could actually do operator overloading in Rust. And most of it is just kind of supported out of the box. You can even overload the plus sign and other things. But for us, we need to overload the double equal sign.

Well, how do you do that? We can jump to our operation, and we can just automatically have a derive how to do that because if you look at all of our types, what is it? It's an option string. Those things are very obviously easy comparable you already know that the system has built it for you.

The other one's two strings, should be very easy. The last one is just a string, should be very easy, none of our custom types. So I should just be able to go PartialEq, and that's it, by doing a PartialEq on it, that just means it can compare it, it can jump over to this.

And now our search just works out of the box, and this is pretty nice, right? I like that, because there's a lot of complexity going on here, but it's actually testing the right thing at the right moment. Copy, paste it, do args, isn't it args? Why is it saying arc?

What did we call it? Yeah, we only have an operation. We don't even have to test the args because it is a part of the operation, right? We use the enum type, boom, it's beautiful. Let's grab that again, do it again. We're not gonna abstract this one out because I always just find abstracting things out often can lead to some amount of pain.

So let's go print key. Let's have a nice vector of foo, which means we should be getting an operation print with some foo, right? Only a single key should be happening. Of course, whenever you do this, notice what it's telling us to do here, is that we aren't providing a string, remember that.

What are we providing, does anyone remember the exact type? It is an ampersand static, it's a static lifetime string, it's a static lifetime reference to a string, right? So we can just do a nice little to_string, all right, if you wanna get rid of that. There we go, it's now a string, I've made a copy, it's beautiful, it's now ours.

We can also do something as wonderful as into that should also work. And we can also use a constructor string from, look at that, all of it, wonderful, they all work out. You can kinda choose the level in which you want to do this. We can just use the constructor for this one cuz hey, why not, right?

Let's yank that thing, and you'll notice down here exact same problem, right? We have a ampersand reference. It's a reference to a string, not the string itself, so I can do that. And we can paste in this nice yarn command for a quick second, ignore it. Since I overrode my yank, I'm gonna have to go register.

Where are you at? You're at zero. Okay, so I can go zero paste, there we go, beautiful. One more, yes, and of course, a comma. Now what did I do here? I've done something wrong here, let's see. Yeah, look at that, there's even one more parenthesis, a lot of parentheses there, it's almost turning into Lisp at this point, but we got it.

Look at that and now notice we get a warning, why are we getting the warning? What did we forget to do?
>> The test tree.
>> Boom, yes, the test. That's why it was unused is because no one's calling this function, therefore it's not done. This testing ensures that it's gonna get called.

And so it will warn you. So it gives you some nice like warnings along the way. This is a very nice one because honestly, I would have just personally missed it myself. That may have looked like it was planned, but it was unplanned, and it's part of it, key value, there we go.

We're gonna add a key value, and do the exact same thing here. I'm gonna do bar, and of course, add. So as long as I do these things I should be able to now go down low, change my operation to add, have two strings, see how it goes.

I'm gonna keep on using the string constructor for now, it's very, very clear for everybody. And you can see that you are creating something. So when you do into or to_string, you're creating something, right, you're not just simply getting it for free. So let's yank that. I'm gonna jump over here and so we're no longer in print land, so let's jump over to add, and what did add take?

Well add took two strings, right? So let's jump up here, I'll grab those two strings, paste them right here, and that is the end of add. That should be the end of opts. That should be the semicolon it's always wanted. There we go. That is our add operation.

And it's also really convenient because now we're not working with this weird list of strings, we actually have a very concrete type for each one of them. And all of our methods can just take in this operation, so it's actually pretty clean right here. Awesome, let's do the exact same thing for remove, don't forget to test.

There we go fa, do a little remove. For whatever reason, I did all that, remove that. Classic remove that one, jump over here, fa remove. There we go, remove is just a single string, it doesn't have a key value, awesome. We've now done all the testing we need to do.

And if I've done everything beautifully, it should just run. But I did not do everything beautifully, so what happened here? Well we should be able to find out, right? So we can actually tell right here that this happened. So let's go look at what happened here. I look right up here and I'll see, it's an add.

But let's look at the stack trace cuz I do actually wanna see did we actually get a good one. Well notice that we only get a very small one, right? It doesn't give us a huge amount of information, but there is a nice little back trace we can add..

So if you were to execute this, you could also add RUST_BACKTRACE=1, and do that. And you're just gonna get a much larger back trace. As you can see, there's quite a bit of things happening here. But for you, it probably doesn't matter all that much, because your errors right here, and it's pretty clear what's happening at that point.

Since we're doing an inline asserted error, you're gonna get just a huge stack of just macro calls and things that are happening underneath the hood. That shouldn't be too concerning for you. So there we go, that should be fixed at this point, I believe. I'm just gonna have to jump up here and do rm foo.

That should turn it into that beautiful one, I'll keep the back trace on. Hey, look at that, is that a first try, that's pretty much a first try. I don't know who's counting, but I feel like that is one.

Learn Straight from the Experts Who Shape the Modern Web

  • In-depth Courses
  • Industry Leading Experts
  • Learning Paths
  • Live Interactive Workshops
Get Unlimited Access Now