Check out a free preview of the full Polyglot Programming: TypeScript, Go, & Rust course
The "Projector CLI App 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 creating a HashMap testing suite using the collection_macros package for the Rust version of the Projector CLI application. A demonstration of correcting thrown errors is also covered in this segment.
Transcript from the "Projector CLI App Testing in Rust" Lesson
[00:00:00]
>> So let's go on and let's do some testing, so standard we'll do the whole config (test), and do a mod test. There we go we have our module down here, and now we're gonna try to test out these values. But again, testing is gonna be pretty annoying, and why is that?
[00:00:17]
It's because we're gonna have to create all these HashMaps. And as you saw those HashMaps, well you can guess it'd be a lot of types and code and putting everything everywhere. So of course, what's likely gonna be the solution to it, to removing all this boilerplate code? Starts with an M, It's awful in C++, Macros.
[00:00:43]
I bet you there's a macro out there that's fantastic, right? It's just gonna be beautiful, thank you. Trust me, there are people here and they did say things such as macros, but not limited to macros all right, yeah, of course macros, right? So, if you look right here, blah, blah, blah, blah, blah, Forgot the anniversary?
[00:01:02]
Testing Time, right? So here we go, cargo add collection_macros, this thing is actually pretty incredible because it has a lot of useful stuff. I'm not sure if any of the newer versions I haven't checked of recent if they've included any of this into the core. But, I've just been using this for the last little bit collection_macros is amazing, right?
[00:01:23]
Because in the core you have something like here I'm just gonna create this really quickly, it does have vec, right? And so that's just shorthand, for creating a vector I can put in elements into it and it creates the vector. But it doesn't have something like map![], right?
[00:01:36]
That's just not gonna happen it'd be sweet if it did, but collection_macros, does that for us. So, cargo add collection_macros, boom it's beautiful, So, that is beautiful, I love that, I'm very, very, very happy about that. I guess the nice part about Rust is cuz it provides this really rich macro system which we won't be using today.
[00:02:03]
It allows you to be able to create these really incredible pieces, right? So the fact that we're just about to go from really handwritten boilerplate code. To something super simple and looks like what you would normally write, it's awesome. So, we can go check of course the docs for collections_macro, there we go collection_macros, it's gonna be beautiful.
[00:02:23]
It has these beautiful little hash trees, so look at that, look at how wonderful that is, right? You're pretty much just writing what you'd already be writing, so we're gonna be using that you can even see the rules right here. So this is effectively the rules they hide the implementation details here, Rust just awesome.
[00:02:38]
Once you get used to creating macros I'm not very good at of them yet but, you can do some pretty fancy stuff. Including parsing HTML and all that, which almost looks like a RegEx which almost looks like a Stack Overflow joke. Which is parsing HTML with RegEx is, anyways, so there we go.
[00:02:53]
So let's use this let's use hashmap!, bang, and create our HashMaps out for testing. So I'll do the exact same thing we did inside of typescript. If you remember inside of our projector, we had this beautiful little test data right here. We can just copy paste this thing over, jump into here, didn't copy it.
[00:03:11]
Actually only did it to the non-system clipboard, go back here boom, get in the clipboard. And now this is obviously not in Rust language but let's just kinda take this one by one and make it into the boot delicious Rust. So, get_data(), and we need to return out a HashMap of <PathBuf.
[00:03:30]
Oop, it wants us to use that there we go of HashMap keep going, String, String> holy cow, there we go. So we got that right so we have the return type correct. Probably be pretty nice to kind of alias that out but, we're gonna keep on typing it again, it's good for your health.
[00:03:49]
So now we just need to simply return a hashmap!, bang, right? That's what we saw, and it should do that hopefully of course Rust analyzer needs to restart. So I'm just gonna quit, relaunch vim, and look at that, we'll just go back to projector I think I actually yeah I have it on that there we go.
[00:04:09]
We're gonna take a second we're gonna wait for a moment sometimes Rust analyzer just needs, some breathing room. There we go, look at how beautiful that is, we're already halfway there. So, what does the doc say? All right, it's type fat arrow other type, right? So we can just go back and forth, create that pretty easily.
[00:04:28]
So let's start right here, we obviously are going to need this wonderful HashMap let's go like that. I'm gonna reduce this down a little bit, and I'm gonna wanna go, PathBuf:: from, we can do PathBuf:: from. And do this, awesome, but what do we do here? Well we just do it again hashmap!, right?
[00:04:53]
And so now we can do this one at a time and it'll create this value out, one at a time so this is pretty good, right? This is about what we'd expect it to be yeah, about where we expect it to be. So I can paste that in and do a little ("/foo"), and then do it again, and put a little ("/foo/bar") fantastic.
[00:05:11]
But of course what's the problem right here? Again, what are we using and what is it expecting? Well first, it's saying hey, there's no such thing as colons here, can you please get rid of those, so we'll get rid of the colons. That's gonna do a lot of replacing, let's just do this, let's get rid of the colon and replace it with a fat arrow.
[00:05:30]
Awesome, so, let's do it again, and let's do it again there we go, so what's the next problem here? Well, you can see right away it's complaining a whole bunch to us. I don't know why it doesn't wanna open right here, it doesn't want open right here. It's expecting HashMap of HashMap, standard string, right?
[00:05:52]
Instead it's receiving a HashMap of HashMap references string, that's a stir this is problematic, right? So let's fix that right now, so one thing we could do is before we would just call the constructor. We can technically call into, this should work in these locations, I wanna say this works.
[00:06:11]
Let's see it we're gonna have to do the whole, we might have to just call the constructor, I wanna say that does work. But I can't quite remember yeah there we go it does work. Awesome, we just called into on a bunch of them instead of doing string:: colon colon from.
[00:06:26]
Into just works because underneath the hood, it implements that into the from trait that we did earlier. How we did from and then morphed it over fantastic great, so this is looking really really good, we have our data. Now we just need to create our projector from something simple, which is just a present working directory, get_projector().
[00:06:47]
And so let's return out a -> Projector{, awesome, and so how are we gonna do this? Of course it's saying hey I don't even know what this is, we need to import it from the super, and now we can just return this. So if I remember correctly a projector which I don't remember correctly, takes in two items, a config, and a data, right?
[00:07:07]
My goodness, so what's the data? We can just use get_data(), right? That's more than good enough, correct? Awesome, And then, the hard part though, is what's the config, how are we gonna create the config? Well we can use config, we can import it in, and we're just gonna have to define it in line.
[00:07:27]
And what's the thing that I want to make sure that we did? We have to provide the present working directory, correct? Yeah, yeah, so let's go present working directory, and this thing is gonna be a PathBuf, fantastic. So present working directory is a PathBuf, a config, which needs to be another PathBuf.
[00:07:45]
We can just simply go PathBuf::, from empty, doesn't really matter, right? Cuz these ones aren't used inside these algorithms. Of course the operation, which is the last one, should be an operation, And let's make it a print of none, right. Again doesn't matter at that point and what is this thing?
[00:08:06]
That returns a hashmap, that is roid. So let's go like this. Let's go a data. I didn't include it. No wonder, grab the data object, grab the projector and go get data, awesome. So now we have that thing filled out. It's a lot like our previous test. Our previous test did the exact same thing.
[00:08:31]
It just grabbed out all this information, bing bing bing. Now we should be able to just simply create a couple quick tests. Let's create the quick test of course of get_value and add in the proc macro, correct. And now let's create the projector. And then let's just validate, we can get bar three and fem is great.
[00:08:53]
All right, so let's go .const, let projector=get_projector. Our present working directory will be PathBuf from foo bar, awesome. And so now we can just go assert equals bang projector get value. Why isn't it doing any autocomplete? Always worries me that I got some sort of error that I'm not realizing.
[00:09:15]
Let's do a get value and what value should we get? Well if you remember, my goodness, why is it not letting me jump? Remember how we did this? We did a capital S string, we don't need to do that. Because we don't need to hand it that official thing cuz we don't actually do anything other than using a reference to it.
[00:09:34]
So to make it a little bit easier on our test, just like I was talking about, always good to remember these things, make it a reference to a string. Beautiful, and then we can drop this reference. So now our testing is getting a little bit easier and we never needed it to be its own capital S string anyways, it's wasteful, likely will cause an extra clone so let's not do that.
[00:09:54]
So now let's get a value, we can just go foo. Now that does make it a little bit easy? So what are we comparing it to? Or remember, what comes out of get value? An option of string, correct? So we're gonna have to go like this, some and then we can go string from and we'll go bar3.
[00:10:13]
I think that's all correct, awesome. Well it's not all all correct apparently. Let's see, mismatched types, expected enum reference to string, what we got out was that. All right, well, we'll get, [SOUND] there we go. Now that we have that, fantastic, awesome, we can use this and let's just test, make sure everything's working.
[00:10:34]
Cargo test. If everything's great, it might take a moment to compile, my goodness, we got issues. Well, first off, probably we have a bad include. We should probably get rid of that include. We're not even using that include. I must have accidentally included it. And second off, it's just silly of me to have that in there.
[00:10:54]
So it's a knightly item or an unstable item. There we go, jump back in here, cargo test. So what happened here? We obviously have something went wrong. And we probably will see this inside of our analyzer. My goodness. My goodness, so many things are happening. Don't worry, don't panic, it's actually usually not as hard as you think it's gonna be.
[00:11:16]
So let's just start at the top line 29. We forgot to make it immutable or mutable. So, luckily all the errors are right in line now so let's just like that, let's make it mute. The next thing, consider changing this into mutable, awesome. That's not too bad, anything else?
[00:11:33]
I don't know, look at that, that wasn't too bad. No, what happened here? Cannot borrow projector as mutable. What happened here? Does anyone know what happened here? Look at how we're passing self in, we're passing a reference to self. Can we edit self if it's just a reference?
[00:12:00]
No, so it's a mutable self, correct? This makes much more sense but now we have a new problem. So this is like the classic Rust problems that you'll see, makes everybody super kind of upset, which is this right here. Okay, so what happened right here? Well, when you use the entry API you're actually creating an entry into the item.
[00:12:22]
And so, it's gonna need this value since our HashMap is of type path buff, this thing will move it out of our config. We can't move that value out of our config. So for the sake of this course and now reworking all of our Structs, we're simply gonna do the forbidden clone.
[00:12:38]
Look at that. We just cloned it out, [LAUGH] made a copy, I know and there we go. So we are now consuming that or instead of consuming it or using it or moving it, we are now just creating a copy to it, all right. So exact same problem here, of course, what's wrong with remove value?
[00:12:58]
Exactly, we don't have a mutable self. So let's go here and let's go clone again. I know, we're just trying to keep it nice and easy here. So we now have another borrow checker problem, holy cow. Why is Rust so noisy? Well, this one's pretty easy, it's saying, hey value moved here and value used here.
[00:13:23]
So we're using something that's already been moved. We moved it back up here. So we don't technically need to pass the thing in. We can just pass a reference to the thing, correct? Yeah, let's just pass a reference to the thing. Why are we giving it a string when we can just pass a string reference?
[00:13:40]
Or path reference. Awesome, so I think that has effectively taken care of all of them if I'm not mistaken. And now we have an official fail, which is awesome. We've finally done it. We finally fail the test. I believe Mark will now kick me out, but he's out of the room.
[00:13:55]
So let's fix this before he gets back. And we should be able to [SOUND] get this done. So let's go down to our tests. What happened here? So we have a path foo bar, should be three. What is our present working directory? Our present working directory is that.
[00:14:13]
So you know what that means? Our get value's wrong. Now, what did it get, did it get foo bar one? And where is foo bar1? It's at the top, you know what that means? Know 100% what that means? That means we're not using the keyword break. Guarantee you we're not using the keyword break.
[00:14:28]
Right here, forgot the keyword break. It's that simple. We're not breaking out of the while loop once we find the value, our bad. Now we got it, now we're happy. I think everyone's relieved. [SOUND] No one want a bar one, no one's liked bar one. My grandmother used to talk about bar one, never liked them.
[00:14:49]
There we go, get value, you can see it's okay, it passed. Let's add in the fall through case that we did talk about earlier which of course is going to be fem. We should be able to see is great, correct? Correct, yeah, yes, say yes. Thank you. By the way, the slouch right now is worrying me.
[00:15:09]
I like a nice good ergonomics sitting position. [LAUGH] There we go. It's still passing. This is fantastic. Yes, that did just happen. I did tell someone from the audience here that they need to sit up. I'm sorry, I shouldn't have said that to you, you can sit how you want to.
[00:15:25]
Perfect, so definitely first try, over here no problems, no errors. First tries all the way through for sure. Let's do a little set value. And let's do the exact same test. But let's do a set value on it. So let's go projector.set value and let's set [INAUDIBLE] foo and we could do technically call into I'm just trying to make it very explicit, so you know what's happening here.
[00:15:56]
Let's go bar 4, just like our previous test, right? All right fantastic, but what happened here? Borrow checker again, very angry, what happened, was not mutable. Remember, why is that? Cuz we require a mutable self reference. So therefore when we call this, it has to be initialized as being able to be changed, right?
[00:16:17]
Rust really makes you kind of identify when those things happen. So I personally think it's actually a pretty good mantra to have as part of the programming language. And then sense that it just forces you not to screw easy things up. So there we go, do that. And of course we could also do a nice fem here and we'll set both values just one after another and we'll go is better then great, yeah.
[00:16:47]
And do that put in one of those, and bar 4, awesome. Let's test this, if we've done everything correct, which we did, by the way. Awesome set value, again a first try. All right, so let's go remove value. And let's do the exact same thing, let's just remove foo and it should work out and we'll also remove fem which does not exist at this level and fem should still be there.
[00:17:12]
So luckily frontend masters is gonna make it longer than this remove. So let's delete, let's delete, jump back here and let's replace set with remove, enter, awesome. Let's see, my goodness, what happened? Remember, we were smart about this api or smarter, we can just simply pass in these, right?
[00:17:31]
Isn't it nice when you think about your API a little bit more? Doesn't that just feel way better to be able to do that, yeah. So take some time. You can avoid cloning, avoid creating new objects, and it looks beautiful like that and so there we go. So if we get foo, we should see bar two.
[00:17:49]
And if we get femme watched, it should still be there and it shouldn't be the default value of is great. And so we test this, my goodness, we've done it all. We are geniuses, okay, we've done everything. It's finished, I would be high fiving you guys if you weren't so far away.
[00:18:08]
All right, I wanted everyone to stew and just feel just frustrated by the fact that we are cloning out this present working directory and it should just bother you like why clone, right? We shouldn't have to do that. We're gonna do a quick little fix on there. So what can we do differently?
[00:18:26]
What does the entry API do? Actually creates the entry, right? So it needs this thing to be the same type as the HashMap, which means we can't be giving it a reference we need to give it the thing itself. So what we can do is use get. Now get takes a reference to the type.
[00:18:42]
All right, so are the references of the key type. So, I can paste this out do that. And now what do I get out of it get? It, of course is an option. What have I been telling you to want to use a ton of to use .map because what comes into the .map, of course, a reference to it, but we can't just have a reference to it.
[00:19:03]
We need a mutable reference to this. So of course we do a get_mut. What do we have here? We have a mutable reference to the HashMap. That means if the thing doesn't exist, we don't need to create it cuz there's nothing to be removed. But if it does exist, let's just remove it.
[00:19:21]
So as you can see right here, we just have ourselves a very, very, very beautiful thing right here. We've just created it without needing to clone that out. I think that's pretty dang fantastic, awesome, we could do something different with the set. Right now if I were to get rid of that, I would do have to do like a contains.
[00:19:41]
I don't know the API yet. How to do that without that, being able to have the or defaults. So I'd have to do a contains, if it doesn't contain any of the created, then I'd have to consume or clone out my present working directory. But if it doesn't exist, you do have to clone something out because it needs its own version or I could solve it at a higher level.
[00:19:57]
And that's when the things could get more tricky with some lifetimes where I have to create a struct that has references. And if a struct has references, you start getting into that
Learn Straight from the Experts Who Shape the Modern Web
- In-depth Courses
- Industry Leading Experts
- Learning Paths
- Live Interactive Workshops