 Guys, we're getting started. Welcome. This is the Rails 3, RSpec 2, and Cucumber code immersion. I've taught a couple classes about learning Rails and RSpec and Cucumber. But for this, we're trying a new format, which is basically, we're just going to code an application and have you guys follow along. And by the process of osmosis at the end of the day, you will be Epic Rails 3 developers. So it takes, it's automatic. Where does it come from for you? It's over here. Now I'm like, can you guys still hear me? Yeah. Now I'm like one of those motivational speakers. OK, so just quickly show hands here. Raise your hand if you've coded Rails 2 or 3. OK? Raise your hand if you have not, because that'll be simpler. OK, how many Cucumber or RSpec newbies here? OK, about half. So, well, oh shoot, we forgot the power. Do you want to get the power? All right. So we're going to start the whole thing from scratch. And we, today, we will be writing a Twitter clone in Ruby, because Twitter originally started in Ruby, and then they had trouble scaling. So they rewrote in Scala and had got the benefit of the knowledge of a rewrite. So given Twitter's trouble making money at this point, we will rewrite again in Ruby and hopefully solve some of those problems. So, and BJ is in charge of scaling. Right, BJ? He is our scaling engineer. So to get started, just to make things easy, I have already created a gem set, an RVM gem set, of the different gems that we're going to be using in the immersion today. So if you go to this URL, can you guys see that? If you go to this URL, that will redirect you to a pasty that contains the list of gems that we're going to use. You should be able to just copy that to a local file and then do an RVM gem set import if you're using RVM. But I'm not going to show the instructions for that, since every version of RVM is slightly different on this. And I always have trouble, but there you go. That will kind of help get started. We're also going to use Bundler built into Rails. So that will install automatically, but this will just kind of get you started faster. So first thing, I've got my- So you're going to use RVM? Oh yeah, have you guys all used RVM? Has anybody here not used RVM? We've got a Windows guy. We have a Windows guy. It's never done any Ruby at all. Really? This is going to be awesome. Oh, here's the other thing I wanted to say. I'm starting, we're pair programming. Who here has pair programmed before? All right, we got some fans back there. I love it. So BJ and I are going to start off pairing on this thing, but I want to do sort of a fishbowl format and fishbowl thing is where basically somebody can come up and kick one of us off and pair with the other one. So if you're feeling bolder, if you want to really immerse yourself in this stuff at any point, come on up. The first person who stands over here when we get to a good stopping point, one of us will get up and hang out for a while while the other two guys code and then we'll swap out a little bit. So first thing, BJ has our gem set installed, so he's going to create a new Rails application with Rails new and then the name of the application. So in this case, what do we want to call it? Twitter, Twitter, Twitter 2.0 for Twitter 2.0. Yes, I like it. What was the Rails new thing? So this is Rails three. Rails new creates a new Rails application. In Rails version two, there was a whole set of scripts, script generate, script runner, script server, all that stuff. Now there's a single binary, well, single script called Rails, and then it takes arguments to it. So new creates a new application, server will kick off a server, console will kick off a console. Nope, sorry, that was just because he, you would say Rails new Ruby web. No, it was when we just did Rails new, it was just showing the documentation for it. Okay, so now let's go into the Ruby WebConf directory. Twitter 2. Twitter 2, right. And now we are going, let's open that in TextMate because we need to set up our gems. And I don't know if you guys have all seen Bundler yet, Bundler is the new gem management thing, use gem file. There's a gem file here, which you see this configures all the gems for the different environments you're gonna use, and it can, it takes versions, it can actually hook into Git, as you see an example on line six, kind of cool. So this is where you set up the gems, and we specify the gems in here and then run a single command and it'll install all the gems that we need. So we're gonna need, we're gonna need Cucumber Rails, command ship plus. Is that good in the back? Can you guys read that? Or is it still a little bigger? One more. No more. How's that? Back corner, you're farthest. Cool. Okay, so we also need RSpec Rails. And RSpec is in still in development 2.0. Yeah, here's the version number. It's in beta, 2.0 is in beta, so it hasn't been officially released yet and it's only compatible with RSpec, we're sorry, with Rails 3.0, so we need to specify the version there to get RSpec 2.0 beta. We also want FigShore, which is our test data builder. You will see it is incredibly superior to Factory Girl, and Capybara, which have you guys used WebRat before for kind of like clicking around and submitting forms and stuff? WebRat unfortunately does not work for Rails 3.0 yet, so we're using Capybara, although Capybara is pretty cool because it hooks into things like Celerity and stuff better. Okay, so once we've got that saved, we will need to have bundler installed, and so that's just a simple gem install bundler, which I think we already have at this point. But so if you don't have bundler installed, you'll need to install bundler, and then finally we say bundle install, and that looks at the gem file, figures out all the gems, including the dependencies, apparently figures out versioning problems, and installs all the gems that we need. This takes a long time. Yep. Hey, John, okay. Yeah, and why don't we paste you this gem file for anybody who's not a, ooh, and Twitter that? There we go, using the RubyConf hashtag. I'll throw that in the back channel. Yeah. Go to IRC, go to Ruby WebConf, and we'll make sure it's there as well. Okay, cool. That means this is just a way to get everybody following Pat. Yes, I am Pat Maddox in RealLife and GitHub and Twitter. Okay, so once we've got our gems installed, next up we are going to want to generate RSpec and Cucumber. That is Rails 3 has some, Rails 3 actually lets, is more test framework agnostic than Rails 2 was, so basically all the generators have hooks in them now, so if you set up RSpec, then when you do, when you generate a model or you generate a controller, go ahead and install RSpec, it will actually generate RSpec files for you instead of test unit, and whatever test framework you want to use. So we also want to install Cucumber, but don't hit enter because when we do it, we need to set it up with Capybara and RSpec. You can, if you don't want to use RSpec, you can do test unit or test unit as part of the Cucumber generation. This is basically just for the assertions that you use in your Cucumber step definitions. You can say that something should include something or you can use assert if you want to use test unit, so because we're using RSpec for the unit testing, we will use it for our, for Cucumber as well. So we tell it, we tell Cucumber to generate and install with Capybara and RSpec, and you'll see it created this features directory for us along with an empty step definitions, so it actually gave us web steps, which is Capybara's default steps in there, and then it also set up a database, something in database sample for us. So let's take a look at features. We see the environment file, this is what's generated. You pretty much don't edit this file because it'll get generated by Cucumber every time you wanna, so if you upgrade Cucumber, you wanna regenerate, then it'll overwrite this file, so you can either spend a lot of time looking at diffs or you can just not edit it and put a file under support. Anything in support gets required. Actually, any Ruby files under features get automatically required, so if it's under there, the step definitions get loaded, support gets loaded, this is all, and you can set up Cucumber profiles, so you can write tests that will go kind of through rails at the controller level, I guess, and then you can set up another profile to actually run that in the browser using something like Selenium, so that way you can write one set of acceptance tests using Cucumber and run them relatively quickly at the app level, but then a lot more slowly at the browser level, and that can actually hook into Sularity 2, which lets you run JavaScript stuff headless, so. This is the basic setup that you're gonna do when you start a new Rails 3 app, if you wanna use Cucumber, and if you wanna use Cucumber and RSpec, if you don't want to use RSpec, you take out the RSpec install step and same thing with Cucumber, but nifty tools, so we're gonna use them to drive our stuff. I think, let's start off with just making sure that our Cucumber stuff is set up well by writing a really simple feature that hits the home page, and allows us to, we'll just view some text on the home page to make sure everything's hooked up. You can, you can do Rails G Cucumber colon feature. So Cucumber gives you some generators. We'll just call this view home page. I'm okay with it. I have no clue. I would never use the feature generator. Okay, see it's called manage view home pages. How hideous is that? Oh my God. Oh my God. Or I mean, oh my gosh. Delete that, okay there we go. Okay, so let's just say visiting the home page and get rid of the whole in order to stuff. So for those not familiar with Cucumber, the structure is basically you define, there are individual feature files. This is plain text. It gets mapped to Ruby code. You define step definitions that basically map it to your application code. So it's cool. Why don't we call it something that makes sense like visiting the home page? Okay, and we'll say when I go to the home page then I should see yay it works. So we can go ahead and run that. I'm gonna do auto test here. I don't use auto test for Cucumber really. Okay. So I would just, I don't know if that works. Run it from there. Just run Cucumber, just Cucumber. Cucumber features, where that works too. Okay, so we see that let's, I wanna show something. Why don't you change one of the steps like given I go to the fancy home page, something that won't match. Okay, actually I wanna say when I, that doesn't make any sense, but. Okay, so Cucumber, when you write these different steps, these plain text steps, like I said, it maps it to your Ruby code and it does it by using these step definitions. When we generated it with Capybara, some of the step definitions were created for us automatically and those step definitions are held under step definitions slash web steps. And you see there's some things like given I am on and I go to and I press and I follow, I fill in, I click, I choose, I should see some text. These are basic kinda lowish level step definitions that you're gonna use that say like I'm on a certain page, I fill out this form, I submit the form, then I should see this text. So the way we wrote this initial feature, we don't have to do any, we don't have to write any of the step definitions yet because Cucumber just kinda already knows about those. So what we do have is it's visiting the home page and it should have this content on it, yeah, it works, but it doesn't, right? So all we're gonna do is hook up a home page so that we can make sure that kind of our full stack of Cucumber and RSpec are working. So first thing we gotta do is remove the index file, the public index file and then we'll want a new controller for our home page. All right, and I usually call it like main controller but I know that you don't like that. What do you wanna call it? Oh, don't put controller on it because it'll append controller to it. It'll do main controller controller. It's main controller spec, let's start off with that. Cool. So we're gonna start off writing a spec that just says that the main controller works so we're just gonna get the home action and assert that it includes the text that we want. So it shows, yeah, it works on the home page. Get home, response, that's it. Say response body should include. All right, cool. Oh yeah, and let's get autotest but actually I wanna set up autotest grow real quick. Have you guys used autotests? It's a little tool that basically looks at the files that you're changing and automatically runs your test for you every time they change, so that's really cool. And that's done by using the gem install autotest which I think I included in the RVM gem bundle dump and then if you wanna hook it into growl, there's autotest growl gem and then you have to edit this autotest file to require autotest growl. So now when I run autotest, all right, cool. So you see that every time, well, we'll see what happens but when we run a test, it's gonna show up in here. So problem is no route matches. So okay, let's get started and put that in routes. So we just wanna say like get main home, main hash. You wanna hash? I do, yes. No, that's a hash rocket. It's not even, why is that called a hash rocket? There's no hash in there. Isn't that a hash? It's a hash, right? That's a pound. It's a pound. Well, they call it hash in England because pound's mean something else. Sharp, sharp we have. Could not run a test, all right, we totally broke it. That means I got the formatting, something wrong. Default controller and action, missing controller. Oh, you know what? So you do a hash rocket to the root? Oh, you're right. So I wanna say, yeah, well, routes to, right? Can I do this? Main home, how are we doing? The action home could not be found, okay, cool. So now if we go to our main controller, we'll say home. Now I take really small steps when I do test-driven development. So when it tells me that the error is the action home could not be found for main controller, instead of trying to make this pass, even though it's gonna be a one-liner and stupid simple, I like to take these baby steps and follow Brian Lyle's thing and change the message or make it pass, right? So all you need to do is, so missing, so when I get a test error, then I can look at that to figure out what the next step is. And that basically gives me really small problems to work with at any given point. And I can figure out small problems. Like it says, there's no home action. Well, all I need to do is to find that. So you're gonna wanna set up an RVMRC if you're going to open new directories like this. So every time you go in. We'll put you to the other terminal. That is the terminal. Cool, we've got our gem set up. What do you do? You only wanna make the home action. No, I don't. We just wanna render some text straight from the home controller. So it said that the home template didn't exist, but all I'm trying to see is that the text is there. So we can render it straight from the controller. And hey, look, we're passing. So now we're just doing a really, really simple example here. So you probably wouldn't need to start off with cucumber and then go down into our spec to test the lower level stuff and then go back up to cucumber to check that it all works. But that's the pattern we're gonna be following. So I wanted to introduce it on something simple. So if we run cucumber, we should see, okay, we've got our home page set up. So what we've figured out now is that we've been able to install rails, get cucumber and our spec all set up. So basically our environment is all set up to do some hardcore TDD as we build out the new features. Yeah. Can you get to GitHub so people kind of catch up? It's exactly what we're gonna be doing right now. Get init. Let's you guys figure out the database stuff. Okay. I'm gonna go and get it. Yeah. It's not like a GitHub gem that will automatically create it for me. All right, so the little bit of work that we've done so far. Oh wait, I did not push that out, obviously. Are you tweeting that? I did just tweet that. So all the stuff that we're gonna be doing, like if I've got information to share like that, we will be sure to tweet it using the Ruby Web Comp hashtag. And then that way you can just follow that. Or as BJ said, you can follow my Twitter account, Pat Maddox, and you will find everything too. Okay, so if we go check this out. All right, cool. Should be all set up there. If you wanna get clone, if you want to go to that URL, clone it, then it has all the stuff that we've done. So, now that we're ready to start building this, BJ's more of the- You got like two minutes. Two minutes. Well in that case, I think we should just be at a stopping point and field any questions, although, you know, I don't know, questions. We basically just installed this setup environment. So now in the next session, we'll be able to build our features and do it really quickly and at a high level of quality since we'll have all our testing in place. Can you show the gem file again? Yeah, the gem file is, right here. And I think that should be under the, we twittered that too, right? Tweeted that. Cool, so any questions here? Next session, we'll get right into building up all the features. We're probably gonna start off with users and messages since that's sort of the core thing of QCumber, or sorry, of Twitter. But at this point, any questions about the setup or how we did the QCumber and RSpec stuff? Yeah. If you show the github URL again, please. Yes, github.com slash patmatic slash ruby webcom. Tweeted to already on Twitter. Now we can have both of these. Now we have it all there. Oh, and we can just get, check out when we're done, huh? Anything else at this point? To the test environment in your auto test, show that again, I get it. Oh yeah, okay. So to get auto test running, you want to do gem install, auto test. And that's it. Actually, if you have auto test installed before you generate RSpec and QCumber, then it will sort of, it'll set everything up for you automatically. If you install it after the fact, then you need to create and edit this auto test slash discover.rb file and tell it that it knows about Rails and it knows about RSpec too. And this way auto test knows how to handle these. So once you install, I'll show that in a minute. So this is auto test slash discover rb. And then in my dot auto test file, this was so that I could get a growl notification working. And in this case, you just require auto test growl. That requires you to gem install auto test growl. And now when we kick off auto test, instead of having to look at the window every time for feedback, it'll pop up a growl notification for us. Cool, so that's it for now. I guess in two hours, we are back on session two here in which we will begin developing the actual features for our Twitter clone. Thanks. Okay, we're gonna get started again with the Twitter 2.0 app. As soon as Pat closes all of his applications. Is everybody, did anybody have any problems getting the app running that tried to? Has anybody tried to get it running yet that we pushed? Maybe, maybe not, okay. Well hit it, come up to us and ask if you have some issues, if you wanna try to get this running on your machine or whatever. It should be pretty easy with Bundler, but there's always edge cases and stuff. Weird setups people have, so. Okay, so basically we don't have, we have our environment set up and we have sort of the flow of Cucumber to our spec to feature going, but we don't have any features yet. So where do you wanna start, Pat? Well you're the customer. Right, so probably, I mean the biggest feature of Twitter is that I can write a message. So I think that we should start there. So and I'll just write message and I can view my own Twitter stream basically. Right, so this would be, we're just going to do the simple micro blog. So, right, so we'll start with a feature called my Twitter stream. And so the first, you wanna go with the scenario? You wanna do in order two? Yeah, we'll say maybe a scenario when I go in first my Twitter stream's empty. Right. And then after I post one thing, then I see that one thing and then after I post two things I should see them in the chronological order. Reverse chronological order. Okay, so what Pat was saying is we're going to, we're going to basically, we'll have three scenarios here. The first scenario, do you wanna just type as I say the scenarios? So the first scenario is if I haven't ever tweeted before there shouldn't be any messages. So this would be a blank stream. So we call the scenario blank stream and so there's no given. So when I go to my... Well, don't I need anything? I think, I don't think at this, we don't even need an account yet. You think we need an account? Okay. There's just one user in our system. We don't need, we don't even need a given for this. So. Should we say given an empty stream just to kind of clarify? Yeah, we could do that. Given an empty stream, when I view my stream. Should I say my home page? Yeah, when I view my home page, sure. We'll say that. I should see zero tweets. Right. So, I should see zero tweets and should it say anything? Say what? Tweets? No. I don't think it is. Pat will ask. And then do you wanna do multiple scenarios here and then it'll just work through them? Yeah, sure. I like that. So the next scenario is that I've tweeted once. So. And I don't think it will even do the tweeting interface. We'll just render tweets out. So, given I have one tweet, given I have the tweet and then, right, and then I am very smart. It's true. When I view my home page then I should see BJ is so smart. Should I say I should see one tweet? Yeah, one tweet and I should see BJ is so smart. And then the final scenario will say I have multiple tweets. Okay, so given I, can you guys, are you guys seeing that on screen? Okay. Yes. Okay. Given I have tweeted BJ is so smart and I have tweeted, can we say in five minutes later I tweeted? Sure. That is also smart. Okay, that's fair. When I view my home page then I should see two tweets and I should see Pat is a lot smarter and I should see BJ is smart. So do we care about specifying that Adel's lot smarter comes above? Like, shows higher in the stream? Yeah. So what if we say and I should see the following tweets in order? That sounds good. Can you just do that, right? Yeah. You don't need quotes and tables. I think the should, the word should, you can say and I see the following tweets coming. Oh my gosh. We're hanging out with hash rock and people aren't we? I like should. Yes. Okay. The question is should you use should and the answer is you absolutely do not need to. You can put whatever you want in there. I know people that say that should is just duplication in them but also it's not so much a statement of whether it should or should not do this if that's kind of iffy but rather it's a factual declaration and if this fails then, you know, I mean and if it doesn't do that then it's absolutely wrong. My personal thinking on it is that if it fails I'm gonna get a test failure so I know it's wrong and by putting should in there that gives me a very, you know, tiny subconscious reminder that I can always question whether it should actually do this or not. So, you know, yes I do see the following tweets in order but I think personally and from a business standpoint over the next several months I have the option of saying well should it actually do that or should it behave a little bit differently and can we change it? So, you know, go with whatever you like. I assume that argument did not sway anybody because it's total BS but that's, you know, this is my personal preference, right? Right, right. As opposed to I don't see or I do not see or whatever. The only thing that's actually required here is the words scenario given and when and then. That's all that's actually required in a cucumber feature like this. All the other words, obviously there are some pre-generated steps that have like should in them and you could say that that's a style guide not that you have to agree with it but you could say that but you don't have to have any of the, anything else in there you don't have to have so you don't have to have to shoot if you don't want it. Yeah. Given, when and then are just aliases, right? You could use all the givens, you could use all when, you could use all and if you want to. I think ands might blow up when you print it out or it might default to given and basically just as look at the previous step and when we run the output, use the same one but as far as cucumber is concerned, given, when and then are all aliases. They don't have any semantic meaning. It's just a communication tool to make it read nicer. The other thing with respect to should is that sometimes on a couple projects I've used, we'll want to do things like say, given there are zero tweets in the system and I might implement that with tweet.destroyall, right? But then I want to say given there should be zero then there should be zero tweets in the system and that might be my tweet.count should equals zero, something like that. So I've got a little bit of a distinction there between my setup and my expectations whereas if I just say and there are, then there are zero tweets in the system, then it's ambiguous to me as to whether it should be, is that setting up any state like this first step is or is that performing an expectation? That said, I've worked with people who don't use should at all and say and there are zero tweets in the system and I like the way it looks. So like I said, it's kind of a preference thing and if you can certainly write your cucumber features in a way that makes a lot of sense either way, I think. Let's move on. Okay, so we're going to get started A. A. Okay, that's Dr. Strangelove, which we don't want. So we'll commit our feature first. So Mr. Customer, does that look good for our Twitter stream? It does, yes. I guess since I'm the customer, I get to go get a latte now and you will do all the work. Yep, okay. Now you're my pair. Oh, bummer. Now take off your customer hat, put on your programmer hat. Does that look like a dense cap? Is that the same style hat? So my Twitter stream, we've got a whole lot in here. So given an empty tweet stream. So basically very few of the steps we just wrote have web rat steps. Which means that we are going to now start by writing matching step definitions for all the given wins and thens that we have. And that's sort of the first, you kind of have to have those before you can even write the code. So this is. Yeah, and the way I like to organize my step definitions is around the model that they're gonna be affecting. So in this case, we're interacting with the tweet model. So I create step definitions, tweet steps. If you look on the cucumber wiki, it talks about like organization, step organization. Right, and it, because you know, you might write like 400 different step definitions in your entire application. So you end up with a lot of stuff. So there's some information on here about how best to organize your step definitions. That said, it's still relatively flat. You basically have a whole bunch of flat step definitions. And so far, the community has not come up with a beautifully elegant way of handling that. So you'll just kind of deal with massive step definition libraries. But somebody really smart will come along and whip that into shape. More, I usually go with the mantra more files are better. So the more sort of contexts that you can break it into your steps into, the better. So you might, if you have, say, you know, you might have 50 or 100 steps in your user steps file. If your user does lots of things, well, you might start breaking that into the user profile steps and user, you know, user registration steps or something like that. And the other thing is keeping them in the file in order of given when and then you kind of can start to keep them somewhat logically ordered. So. Okay, so I ran it, so we have no model. So we don't have a tweet model. So we'll generate that. Just that, right? Right. Just a body. Just a body. And do we want to put any kind of a database limit on this? I think 140. Is it like this? Did the mic die? Yes. Is there no battery or what? The battery's dead. Well, glad we could be the guinea pigs for whoever comes up next. Quick, does anybody know the syntax for setting a limit on the string and a migration? Limit or max? I think it's size, I don't know. You know, we'll go with size and see what happens. Limit. Limit. Limit? Limit. Okay, just like that. So Pat ran DB test repair right after the migrate so that it's it. Just hold it in front. Okay. So, doing the DB test repair, I don't know how new some people are, but that sets up your test database to have the same schema as your dev database in all in the same rate task. You will very quickly learn that every time you run a migration, if you don't do that, you'll run your specs and they'll break and you go, oh, that was just working. What's going on? And then you realize that your schema has changed in, but not your test database schema. So that passed? Yes, the step where we destroyed everything did in fact pass. So we're gonna change. I mean, it passed, but it doesn't actually test anything. So we don't, we still don't have a passing test really. Well, yeah. I mean, it was just a test. What'd you change it to? Go to, so it uses paths that RV. Okay. So you show them paths. So Cucumber has, Cucumber doesn't know anything about your routes file. And it takes the approach that your routes should, that you probably don't want to specify your routes in your Cucumber steps the same way you would in your routes file. You want to like name them or you want to use plain English. So you want to just say, I go to my homepage instead of I go to slash home or something like that. You can do slash home and it will figure it'll route to that, but that starts to get unwieldy quickly. So it has this paths file which it automatically loads and parses. And basically whenever you do when I go to, whatever you put after that it does a huge case statement on it. And when you get into a big app this case statement gets really big and you just start putting in human readable names for the different paths that you will have in your app. So we put in my homepage and we just set that up as tweets path. And now Pat's generating a tweets controller and he's going to hook up the, he's going to change the, don't you want to change the route? I don't because we're going to like my homepage which can be after I log in. Okay. I guess. Yeah, so, okay. So this is going to be later down the road this will be something, this will be what you go to after you log in. So we just do the resources tweets sets up a restful route to the tweets controller. So yeah, we just generated the controller around our cucumber again we find out that we have no index on tweets controller. And you notice every single time we make a change we run the cucumber thing and it tells us what the next change to do is. So first there wasn't a controller so now we have a controller now it doesn't have an action so we'll put in an action and then probably the error will be that there's no template so we'll put in a view and it seems slow but it's just kind of slow because we're narrating it and in your daily flow this ends up being a really good way especially if you're ADD like both of us are to stay on task and always do the next thing and not get ahead of yourself and not forget where you are and it ends up being faster. Yeah and just as a final thing here we're going to see as I write my RSpec examples I never use the word should in the description. I cannot explain that yes I'm being inconsistent. Sorry. I still use should. If you notice he passed in there's a second thing on the describe block and that's just basically a context so we'll have multiple describe blocks. The first thing is what you're actually describing and the second thing that you passed in is the context you're in. You could achieve the same thing by using the context method which is actually just an alias of describe so we could have nested describes one just being tweets controller and the next thing being get index. This is just sort of a shortcut for that. So he ran the specs and there's no there's still no index action so now we'll go ahead and add an index action and run that and we get an error which is missing template for the index action and we'll add the, we'll go in and add the view. We have 15 minutes left, by the way. Okay. Next, I just want to hear from you. Right. We just add a blank file, don't need to put anything in it and run the specs again. Those, that passes. Well we got passed the next step. I'm writing the controller specs using I'll be using mocks and stubs and that basically gets me isolation from the database so I know that the controller works the way I expect it to and I don't have to rely on the model behavior at this point. Cucumber is gonna go through the full stack and let me know if everything's hooked up properly and if there's any problems in the integration part but the other thing is realistically if, wait does it, you know when you're writing these sorts of controllers that are highly restful, you're probably not going to need to write your own controller specs anyway because you're gonna use something like resource controller or simply resourceful. Inherited resource. Inherited resources. But my question is, is that built into Rails 3.0? I don't think so. Okay, then we're gonna do it all long hand on this one just for the sake of example. I almost never write controller actions. They mostly come in handy when you're working on filters for your actions and so you want to know that this set of conditions, you know whether somebody is logged in or their role on the project or whatever, you can just write a few controller examples to cover those actions and those filters. Totally, so they're generators for the RSpec examples and stuff. I personally, not a big fan of the generators but mostly that's because when I'm not talking on a microphone and explaining everything, what we just did would take like six seconds to do. So and then I only write the examples that I care about which in this case, I'm just making sure that we're calling the right method, assigning something to the view. From my perspective those are the things that I care about the controller doing at this point. I'll rely on the framework to handle the rest of hooking up the right view, rendering it, et cetera, et cetera. Okay, so we actually have two steps passing now and we are, which is because we put in that view and we're now to the point of actually checking that there isn't any tweets in the view and really we're not gonna, this is gonna be really simple to do and our steps gonna be really basic right now because we're just gonna check that it's empty because we don't, we don't, we're actually gonna put markup in it. This thing we shouldn't see, I mean. Right. Oh, I guess it should not do anything, right? Yeah. At this point we don't care. Right, at this point we don't care. So Pat, I figured Pat would start there. When we're testing that there isn't anything there and we know there isn't anything there, writing a test to check that there isn't anything there isn't, we can never make that fail. So we just don't test for anything and that will start failing once we actually do start putting things in, if that makes sense. So we'll actually go on to the next step now and have a tweet in the app. One scenario is passing. Right, because we now have one scenario passing and we haven't actually done anything. So it's that test for, if we haven't tweeted there aren't any tweets, isn't actually testing anything yet, but it will start testing things when we get on to the next step. So we're actually gonna kind of skip ahead to the next scenario and then go back once we have tweets and we can assert that there shouldn't be tweets when there aren't any tweets. Well, I just added a step for fun. Okay, so Pat added a step for fun. So, and one thing is you pretty much always want to specify like that you do see something that you expect because one thing that I've run into a couple of times if I say I should not see this text then if you've got the Rails error handling turned on to where it raises a 500 and it'll capture it and show you an error page and you say then I should not see this text. If you have an error somewhere and Rails does the error handling then obviously that text doesn't show up on the page but then now you have, but you actually have an error there and so you had. You're still not getting what you expected. Right, exactly. So you have a, you've got, you know, your test is green even though your app's broken and there's a configuration that you can do in Cucumber to basically rescue from stuff or not but you know that's just something that tripped me up and so the basic rule is for any test always the final step you should be expecting to see something that you really do expect. So in this case I said if a user has not tweeted then we're gonna say that, hey this user hasn't tweeted yet. I'm doing the H1, should be a two. No, that's fine. All right. So obviously that passes. But that's, I mean, it's basically, we're basically just doing the simplest thing that could possibly work for this one scenario and as we get deeper in the other scenarios then obviously we'll have to come back and refactor that stuff. Just a side note, if you're not good with Ruby regexes and you want to do Cucumber, you're A, going to have to get pretty good with them or at least somewhat good and B, there's a site called rubular.com, R-U-B-U-L-A-R and it's been around forever and it's just a little Ajax script that you put in some text that you wanna match against and you put in the pattern that you're trying to use so you would put in what comes after given or when or then in there and the step that you have written and it'll tell you if it's matching and what's matching and how many things match and then you can, it helps you get it all right. I think I spelled that right. So just as a little note here, when I'm setting up state, I said, so I've given, I've tweeted and in this case, like we said, we're not going through the UI to tweet something, we're just setting up the state. So when I do that, you'll notice I use create with the exclamation point here and the whole point of that is I expect this step to pass all the time so if for some reason, and I expect it to create a record in the database and so if it doesn't for whatever reason, like maybe I add a validation that I need to include here later on then I wanna be alerted of that immediately and so I want a noisy error saying, couldn't create the tweet in the database because otherwise if I do, you know, if I use this and it doesn't create the tweet silently, then when I go on to my next step, it's gonna say, hey, I didn't see it on the screen and now I'm gonna spend a lot of time debugging, wondering like, well, okay, I'm showing everything on screen, why is it not working? So when you're doing setup stuff, you want to use the bang version of create or save so that if there's any errors then you're alerted right away and noisily. The first thing is because of the, because we changed this. All right, because we made, because we gave that first step definition a blank implementation, it's passing through, but now I wanna specify that it actually has something so I wanna say, show me that there are at least two divs or spans or whatever HTML element bj tells me is the proper one to use here and I wanna say that there are that many and cucumber, the step definitions because it's a regular expression, everything that gets yielded is a string. So that's one gotcha. If you are going to be comparing numbers, then whatever gets yield to the block here you're gonna need to convert to an integer. By the way, does that step definition syntax make sense to everybody what happens here? There's a regular expression that gets matched against I should see five tweets and then when that matches that step and we get into this block, it takes that chunk of text, extracts it from the regular expression and passes it to the block. So when I've got, I should see one tweet here, then whatever's matched by this first set of parentheses then gets yielded to num tweets. So in this case, I'm gonna have the string one, right? Does that make sense? I think that should. Ah, cause we're using capybara. We don't use capybara much. BSC is web-rat. I hate XPath because I can't read it. But it might. Invalid expression. Okay, so we're getting closer. Have content or? Well, I want to say that we've- Yeah, yeah, yeah, yeah. Hascss. Makes sense, should have css. Just put up that much for- Beautiful. Css. Okay. And can I pass count to that? Does, do you see that anywhere? We might just parse it with nukagiri, but I mean. So the API has changed between RSpec one and RSpec two and some of the matchers because they wrap rails stuff and so some of that has changed. So it's, you know, I forget basically. Old habits die hard. Old habits die hard, right. Okay. So back to my index. So we now have the first scenario and the second scenario passing. And we're now going to go to the third scenario which has a step which is unusual. Just given some number of minutes later, I tweet. And has anybody used time cop? Really? Couple people? Time cop. Yeah. You don't want to use time cop? How else are you going to do it? How else are you going to do it? How else are you going to do it? Yeah. We should commit. Oh, because we got two scenarios passing. Right. See, we're going to show you exploratory testing and why this doesn't cover everything. Because it's going to sound like this. So I'm a big fan of time cop. It basically overrides time and lets you stop time and manipulate time in your tests. So you can say things like, you know, freeze time and then assert that five minutes later, I do something else and it really, it will be five minutes after when you have stopped time. So if you do lots of things that end up being time sensitive, time cop is amazing. You could just call our other tweet, our other step. I'm waiting for a gem to install because we didn't have it. Yeah, so we're going to see if this works right now to tell you the truth. In fact, let's just test something real quick and say I should see three tweets. All right, cool. I see here that in this case, when I specified the count was zero, it knows that I shouldn't see any of them. So, you know, it wasn't looking for that one. And then if I specify three tweets where there's only one, I expect that this will say, you know, yeah, it should have this. That error message isn't particularly helpful because it's not saying expected to find three of them and only, but only got one. But, you know, hey, it fails. And it's open source. So go ahead and commit a fix to make it friendlier. You guys could do that while we're up here and then that'd be great. Right, you can do some kind of cool stuff. Like Pat Nakajima has a project for kind of parsing out the HTML in tests. And so you could basically say something like this where you go like, you know, tweets is a, you know, not a project. I'm just, so it looks sort of like Nukagiri. And then you say, actually, you know what you, I mean, you could just very easily do this with Nukagiri and say tweets is Nukagiri page dot body divided by, you know, dot tweet. And then using our spec, you say tweets dot should have num tweets items. And that way you're saying, okay, so we've got the list of tweets and it should have three items. So let's see what happens if I do this. Just for fun. And I want to change that to three, right? Not a time. Yeah, so in this case, expected zero items got zero. So, see, there we go. There's that whole thing with strings and numbers. Right, so expected three items got one. So you can do stuff like, if I've got, you know, if this is failing for, if I get a failure and it doesn't give me much information, then I like to add a different expectation that will give me some info. So, can we write this next step definition in two minutes? Probably not. No. You want to try it? Yeah, let's do it. Okay, let's do it? Okay, we'll probably get two minutes over. So we're going to blaze through this without much explanation because we kind of have to. I'll try to follow along. Great. So I'm going to keep, yeah, you know what? Let's do that. Let's keep that. Let's just do it like that. Right. So we still have this one step and it uses a cucumber table, which are pretty cool. Little ASCII, you can put in an ASCII table and it'll grab the head of row and turn that into the hash name, basically. The variable that comes in. And this is basic, it just basically comes in as a big hash for you. It's an array of hashes. So in fact, let's look at that real quick. We'll say P table dot, well, P table. Actually, that's going to be some cucumber object. So we can say table values and we'll see. Oh, it's not values. No. Is it entries? Yeah. Find method each for cucumber ASCII table. Well, that's hideous. Student spec, no, no, no. Fable dot rows hash, rows on the score hash. Right. Actually, I think we can just do rows, because I don't want a hash. I want the array of hashes, right? So, you're totally right. Rows hash? Yeah, right. I know what I was looking for. It's table hashes is what I'm personally trying to get in this one. Right, so there we go. There's that array of hashes that I'm looking for. So now once I've done that, we're gonna do something that's sort of an ugly kind of step definition to basically let me say this one comes before this one, comes before this one. And in this case, what I'm gonna do is get the list of tweets like we did before using Nokogiri and then I'm going to get the IDs in the array and make sure that those show up the way that I expect them to. And make sure that those match up with the ID, the order from this. Actually, we don't have to do that in this case. We can just collect the content of the tweets and compare them against that. So that should return an array of the actual body of the tweets. And now we're grabbing what's in our plaintext table. I don't know if we can do this, but I think this should just be tweets. I'm gonna find method tweet because I can't do it like that, I have to say map to tweet. All right, so we see in this case that they are reversed. They're not the order that we want them to be in. So I am just going to go into tweet. Can we do a default scope? Yeah, we're just adding a default scope so that they come in like on Twitter in reverse chronological order and they pass. So now we can tweet and they come out in the right order and if we haven't tweeted, there's a message for that. Yep, and next session we will start off with a little bit of exploratory testing because as we'll see there's just a little UI problem right now besides it looking completely ugly, but we'll start off with a little exploratory testing session to see catch any problems that our TDD process didn't catch and then we'll move on with doing users and following users. Questions about this at this point as we went through anything look weird or really bad or good or what? Okay, that was loud. Twitter, 2.0, code immersion, round three. Is everybody ready to rumble? So I'm gonna pass it over back to BJ, but in this session we're, like I said, if we verify that we got through our first set of cucumber scenarios, which was my very basic, when I tweet, it shows up. We see those run, so now we would say, like okay, this feature is ready to deliver to our customer, but before we do that, we want to do a little bit of exploratory testing, hammer the application a little bit, which is gonna take us five minutes because it's a stupid, simple, I mean it's a stupid application, but it's a stupid, simple application too, so it's not gonna take any time to test, but we'll do that, write any test for problems that we find, and then move on with the whole next set of features. Cool? Cool. So Mr. Customer, let's look at... If you'll notice, this is the first time we've actually booted a web server, so it tends to go a lot faster, development does. Yay! Ta-da, this user is not tweeted yet. It goes a lot faster if you're not constantly writing code and then going to your web server and hitting refresh every time. If you have tests, you don't have to do that. It's pretty amazing. If also you'll realize, or you might notice that we don't actually have a UI for creating tweets yet, so we're gonna create one via the console, and now if we go to the web browser, hey, it says we have not tweeted yet, that's a problem. We have tweeted, we've tweeted twice. We've tweeted twice. Two tweets. So, now that we've... So we basically, I would check this in the browser before handing it off to the tester, but you also, if you've got testers on the team, their role's not really just to find little bugs or run through scripts, because if they have a script to follow, then you're wasting a person's time. And frankly, they're life because nobody likes following a script. That's not a good existence. So when they do find bugs and like automated, when they have scripted checks, then you wanna write automated tests for that. So in this case, we see that there is a bug here that it's telling us the user has not tweeted yet when in fact we have. So at this point, we would write or modify a new test and to catch this problem and then use that to test drive our solution. So in this case, I'm very simply going to modify one of the features. And so after I say I have tweeted once, after that I don't really care and I should not see this user has not tweeted yet. Tweeted yet. That was my server, huh? That's my console, so. All right, so we see that one failed. So we've got our automated test in place that checks that same bug that we found from exploratory testing. Go ahead and change our code to make it work. Ta-da, passing and by the powers of Rails, we have no need to restart the server and we refresh and there we go, right? So if we go back into our console, delete everything, we see exactly what we expect that user has not tweeted yet. And like BJ said, we were able to make progress on this like a whole Twitter list and some business logic without ever once looking at the browser. So the whole original Java model was write code, start up your app server, find a bug, take down your app server, change it, change the code, start the app server again, blah, blah, blah, blah, really, really slow. Rails sped that up a lot by saying you don't have to restart the app server, you can just kind of change code on the fly, but then test-driven development brings that even tighter. So you'll notice what we're doing is we're getting feedback loops here that as soon as we make a change, like once we make a change, how quickly do we get feedback as to whether that change is good or bad or did it break anything? And so one of the things that we're trying to do is get very, very tight feedback loops. And so that's one reason you'll see that, like we used Autotest, right? And Autotest automatically runs everything for us as soon as we change a file and then we use Growl to tell us the Autotest results. So basically as soon as I save a file, I know within a few seconds whether my changes were good or bad or the next steps that I need to take. So that concludes our exploratory testing session unless there's other stuff you wanna look for. No. Well, I did notice that we don't have any times on tweets. I think it'd be really nice. Like we might get really good ROI if we had times. Okay. Isn't that what the customer always says? I don't know. Well, I have good ROI. So how does that rank in priority to other stuff that we want to do? Okay, we can go on. I'm just asking customer. I say, let's do that later. And let's do following users. Okay, well let's do users first. Let's actually have a user. And then we can follow them. You know, and I'm going to start committing and pushing more frequently so that if you get like five minutes behind and you want to catch up, then it'll be easier to do. All right, so now we need a feature for following a user. Well, we need users first, right? We don't, I mean, we need to write the feature that drives out the, okay. So, you know, it'd be natural to say, okay, at this point we need a user model, but we're going to write a feature that drives that first. And says, we, you know, like it turns out that we can get the notion of a Twitter stream, a very basic generic Twitter stream without having to write any users. As soon as we get into following users, we anticipate that we will need users, but hey, maybe we can always just attach a username to a tweet right now and we don't actually care about the, we'll see, that's kind of the point, right? The design is incremental and iterative, and so we make improvements to it as we see fit. But instead of, you know, we might do some brainstorming to say, okay, well, we'll probably end up with a user model and the relationships might look like this, but we don't want to commit to anything until our tests show a kind of guide what we do. And that's the whole part of the driven part of test-driven design or test-driven development. I think the first feature is going to be looking at someone else's tweets. So let's do that first. So this is going to look, I assume it'll look similar to the one that we had done before, but instead in this case, we'll specify a user and then view their stream instead. Yeah, I think that we might say that there might be a scenario if the user does not exist, if we don't have any tweets from that user, they probably don't exist yet. And then, why are you working on those? I'm not, I'm copying and pasting. Coping and pasting. Well, I think the blank stream is given, yeah, given I have zero tweets. Right? Cool. So given bj, should it say the user has not tweeted yet or there is no user? Well, does bj exist at this point or not? Well, not if he hasn't tweeted. Unless he hasn't signed up. We'll do a different scenario for somebody else. Okay. So in this case, we're just writing two scenarios and it looks pretty much the same, but basically we're going to be going to a different URL, so a slightly different action, different controller, there's gonna be some stuff different to it. And I guess we also wanna, do we wanna say that when I visit my Twitter stream that his information shows up, but maybe with the username that did it? Well, I think we say that when we view. Yeah, we haven't followed a user yet. Right. We have no notion of following. Right. Yeah, totally. We will get, when we do following, we will use a background there. I think at this point, we're looking at more or less the same thing, which is just saying I can view a user's page. And honestly, I don't know that I care about even the blank stream. We just wanna know that I can view their page and I see the tweets that they made. And maybe it doesn't show tweets that I made. Right. Exactly. The other thing is, you just put this in the wrong feature file. Yeah, you're in the manage view home page as one. View other users at the bottom. No, it is. Scenario shows that users do it's best, right? Right. Given. So as you can see, sometimes it takes a little bit of refactoring on your cucumber stories to get the right story that sets up the right scenario that you wanna test. It's not always as simple as just writing out a bunch of plain text, which is sort of, this ends up being what people say is the bad thing about cucumber that you can't just turn this over to non-technical people because they won't know how to set up the right state to test and to drive out the particular feature that they want. With that said, the idea that you will have a business person or a non-technical end user write your acceptance test for you is a total pipe dream. And it's probably more of a nightmare than it is a dream. So don't even try with that. My preferred structure for writing these tests, like you always wanna pair on everything, including acceptance test writing. And so I like to do one of two things. Ideally, I can get a developer, a tester and the customer or product designer or user experience person together and have them re-ing, pairing, tricing, I don't know, whatever three people pairing is. And just have them work together because then you get good knowledge transfer among those three people. And they're gonna be looking at it from different points of view. Other than that you, if you can get the product owner and the developer pairing or maybe you have a conversation between the product owner, the tester and the developer and then the tester and developer go off and pair. Usually testers have a better idea of what the product owner wants than developers do because we just kind of like to write the code that we wanna write. And so, I don't know, most of the time you're like, well, the customer doesn't actually know what's gonna be good for this. But for some reason, testers are more empathetic, I guess. And so they've got, they develop a really good understanding of the customer's expectations. And so you want them involved in the testing process. One thing you don't wanna do is just write your acceptance test independently of everybody else and then email it to them and have them look at it. That is roughly 20% as useful as actually pairing with them. If that, because what happens is they check their email in between going to meetings and they say, yeah, yeah, yeah, that looks good. And then when you actually demo it to them, they're like, what the heck, this is not what I asked for at all. So you want to establish an actual communication between them, a face-to-face interaction with them, high fidelity interaction when you do this so that you avoid some of those problems. Okay, so question is, how do you actually get people to do this when they're not interested? And there are innumerable strategies for doing this, I guess. But one of my favorite things when introducing any technique or new bit of process is saying let's try it for a week or let's try it for two weeks. Basically saying I'm asking for an honest commitment from them for a very short window of time. And if it doesn't work, then, like we haven't committed to anything. It's just a little experiment. We'll see what happens. And if we like what we did, we move on with it. If we don't like it, then we just ignore it and forget it ever happened. So that's one good thing. I have also, when I've been team lead on stuff, I've said that nobody writes code until we've got the acceptance test in place. And at first it was just kind of like, well, how are we actually gonna get anything done? And believe me, when you tell your project manager or your product designer that you are not going to write any code, like I'm just gonna fix bugs and refactor and do whatever I want until you make the time to tell me what you want and express it in a way that is useful to both of us. Somehow they figure out how to make the time. So that takes, you need one person on the team to have the balls to stand up to whoever else is not gonna go along with this. Dave, you have some suggestions? Yeah, and then you write the steps and run it and they go, holy shit. And then it's an easy sale after that. All three times, I've had complete buy-in just by demoing it. And that's their acceptance, and then you can, there's a lot of tools that you can use, Tempo Pro Pickler, which will bring the story from to go back right into a feature and then it'll show it at the top of your feature. It'll say, you know, where it's going to and then you can close it or whatever. But that's been, you know, that the main point, when the client goes through that process of story, then they are totally buy-in because by that point they've seen a demo, you know, a innovation test, and they've seen, you know, so I need to test for the browser, probably nothing. That's like the wow factor for them to see that stuff. That's been my experience. Yeah, you know, another thing is instead of trying to say, hey, will you write acceptance tests with me, you say, can you give me some examples? Because people understand examples very well and communicate in examples. And so you can basically say, like, every time that you tell me what to do, I forget what it is and then we go through this whole round of back and forth where we're trying to figure out what to build and, you know, maybe, maybe like a week later we figure out that it was wrong. So if you spend 10 minutes with me to go over some examples, I'm gonna write them down just so we're, so I'm clear on what it is that you're expecting of this, then, you know, and then it goes to exactly what Dave said. You've captured examples, they look at it, they say, yeah, okay, that's exactly what I meant. And then you run it against the code and that's kind of the epiphany. And it goes really, really fast once you get into it. And I mean, it takes like a week of training them and like training by osmosis to get your business people really up to speed with this to the point where, you know, we had done this for like two or three weeks and I could sit down with our product owner and before lunch, on Monday, we would have all of our acceptance tests for the entire week's iteration done. So we're able to crank through those examples really, really quickly, yeah. Yeah, and the important thing is that you're opening up a communication channel that probably isn't really there before and one of the things that I find with any of the agile practices and stuff is once you get going with it hardcore, a lot of the resistance is because the organization is so messed up that everybody realizes all of a sudden that they don't have a clue what's going on and the communication channels between the teams are no good and yada, yada, yada. And so like you do test-driven development and you find out that the whole organization is a mess and yeah, that's a really, really scary thing but it gives you a starting point to fix stuff. So enough soap boxing, time to code. Right. Let's write some code. Okay, so I think we'll get a user model, yeah. Yeah, I think we probably need a user model too. I think you need to. Okay, try it. One of the interesting things about cucumber step definitions I found is that sometimes you end up defining what you want sort of the API to look like or how you're interacting with things to look like in your step definition and then you have to go back and figure out how to actually make it work. So we don't have a user, they don't have names yet and they don't have tweets yet but this is how we kind of want to create some tweets is by finding or creating a user by their name and then creating a tweet off that and so now we have an idea of what the next sort of, what we want the code to look like in the end or be able to do. And this is kind of a boring example but as you get into it, you get more examples of that. It's a very exciting example and you guys are all very, very, very happy to be here is what he meant to say. Sure, if you notice because we made the app aware of our spec, we no longer get test unit tests when we generate models, that's nice. Rails three, yay, finally. So many deleted tests until Rails three. Test unit, test files. Okay, so we migrated, we prepared Cucumber, Cucumber that action. So we run the Cucumber again and we get kind of a, oh, the bang doesn't work. So we see that there's no tweets, users don't know about tweets, obviously we haven't edited the user file yet but in one line, run the Cucumber feature again. Tweets don't have user IDs, we'll add a migration for that. If we got an email here and we had to respond so it distracted us, we could come back and the error message is right there. We know exactly what we were doing. Not that any project manager would ever interrupt programmers when they're working. You guys must work in better companies than I do. We migrate, now we should have a different message. Okay, so now we get that there's no mapping here. This is the paths issue, paths.rb. So we have to set up a new win basically. Is that right for non-white space or string stuff I guess? Take out that first quote. And escape the apostrophe, I think. So now we want to say user ID. So the nice thing about this paths file is that in your case, it's a Ruby file and you can do Ruby. So now we can just find the user using what we just matched out of that path and then return the route that we want. I don't think Pat thinks we're gonna match here. Now we get an undefined method, user tweets path and that's because we don't have a route. So we go to routes, we hook this up. I wanna say namespace, right? I don't know, Rails, three routes, very well. Can't you just do routes, users, do? Well, we don't have a users controller. Or resources users, oh, that's right. Yeah, so do namespace. But is that gonna have the user ID? No. Let's just do this. Let's say match users slash tweets. I know you want a name. I want it to be a name. So we're just doing a real basic path here because we don't have a users controller yet. We can't do, if we had a users controller we could just say resources users do and then resources tweets inside of there and it would make the nested route that we want but we don't wanna do the users controller yet. We don't need that. So we'll do the simplest thing that'll work here which is just matching a basic route and I think we have to give that a name or some kind of, he did. I missed that, I was talking. We can run this. We should get, we get a routing error. This controller tweets still wants to control. No, it's saying, no, it's saying it's going to the tweets. Wait, we didn't have a tweets controller. No, yeah, we do. It's gonna run. It doesn't look like that. Oh, it doesn't need to go. Oh, I bet that's not. You've got name in the controllers field, I think. Yeah, you know, I bet that's not taking the, I bet that's not taking the user in there. So we're going to just kind of go through this really quickly and create a user's controller if we need to so that we can do the nested routes. Our route matches controller tweets. Action create. How do we get to that? Line 19 and pass. Why make it nested? Sorry, what's that? Why make it a nested route? Just make it a regular route. Well, actually, wait, no, we want to say tweet slash and the user. That would make more sense, right? No. Yeah, let's, no, let's do that. Trust me. Yeah, so I'm going to change this to say tweets to pass and now I'm gonna say tweet path user dot name. And so that's basically, we're gonna go to slash tweet slash to user. Why don't we just, we don't need the find now. Huh? We don't need the find. Oh, you're right. Totally. Not that that's readable. Okay, so the action show cannot be found, so that's pretty good. If we go to our tweets controller, you know, now I would probably want to, I want to write my controller specs for this. So I'm going to say describe tweets controller, get show. And keep in mind, we're not following the rest to a T here. I am sort of glossing over the fact that there are things that I don't know about Rails 3 routing and don't feel like figuring out 100% entirely in this session. You promised us to look it up tonight and tell you all the right way to do it tomorrow. Yeah. Okay, so I'll say user.tweet. So this is the other place where you will sort of figure out what you want the code to look like. What code you're going to use is when you're writing your specs, usually controller specs, you're deciding what methods on models you're going to call and things like that. And this is sort of where the design starts to come out that comes with doing BDD. A lot of people like to say that our spec is a really good design tool. And it's this type of stuff that I think they're talking about usually. So basically Pat's setting up a stub user. And a stub is, if you're not familiar, basically just a dumb object. It doesn't know anything. It doesn't know that it's a user. It doesn't know that there's a table there. It doesn't know that it has any fields. It's not going to respond to much of anything more than what object would respond to. And if you look at line 15, where we say user stub tweets, we're adding, oh, as he changes it as I speak, by adding that hash into the stub method there, he's just saying if Ruby sends me a message called tweets, this is what I'm going to return. It doesn't know if that's a variable. It doesn't know if that's a method. It doesn't know anything other than if you pass me this message called tweets, I'm going to spit out this stuff back to you. And so by using this stub, we're not going to hit the database. We don't even need a database there. It's going to be fast. And there are sort of bad things that could happen such as if we remove the tweets method, this spec wouldn't break, but our app might break. But hopefully we would have other specs that would catch that too. In our spec too, absolutely nothing there aliases for each other. So there's a whole bunch of different terminology when it comes to using fake objects in testing. You'll hear mocks, stubs, and doubles. And there are endless mailing lists debates over what terminology to use where. What I'm going to use for these sessions when we use stuff is we're actually just going to talk about stubbed methods and expected methods. So when we do should receive, that means that the test will fail if that particular object does not receive that method at some point during the test run. So if we look at in my tweets controller spec where I said user should receive find with BJ, that's saying that when I run that test, user must receive that message and it should have that one argument. If it never receives find or it receives it with a different argument, then that test is actually going to fail. Whereas if we just said stub, then it doesn't care. So it's basically whether the object automatically verifies the message calls that it got or not. In terms of the actual terminology, practically you can be relaxed with it because it doesn't actually have an effect on anything. And the terminology at this point is so muddled that I wouldn't bother trying to be strict. I usually mock objects and stub methods just out of habit. I don't even know where I got that. So I would have changed line 13 to say mock, but it really doesn't matter. Which happens to be an alias in our spec so it really, really doesn't matter. Did you run those tests? Oh yeah, and you know what? And we're gonna... So I already noticed an error here but we're gonna find out what happens when you kind of disconnect between mocking and stubbing and the actual application. So we'll see how we get this test to pass. And then when we run Cucumber, it's going to fail horribly, miserably. Which is why we write both tests. All right, so that's passing. Next up, I don't wanna just copy the show page over to index. So we're gonna say that when I actually get this, instead of trying to render show, it's going to render our the index template for us. Okay, we'll see something here. We, it's actually doing a find, so we didn't stub out, find in this particular example. And so now it's flowing through to the active record behavior of looking up a record with that ID, which obviously doesn't exist. So this is where we start setting stuff up. And in fact, then there's also some helper methods to define particular objects that you can use in your tests. So I'm gonna do that and also do a before to stub the user thing. So just showing that. If you've been using RSpec for a while, the let syntax is sort of replacing before blocks, before each blocks, so that you don't have to set up instance variables. You're setting up local variables when they get cleared and you get less problems with shared state between examples. Okay, yeah, so. Well, you said that before. Right, so actually, so let, so when you call let, it's lazy initialized, so as soon as I call user, that's when it comes into existence. So that means if you're setting up any database state and you haven't called the method, the helper method to define that fixture for you, basically, then it won't be created and you'll get a weird test failure. Whereas I'm guessing, because this is brand new to me, that let bang initializes it right then upon test run, so it's not lazily initialized. Oh, it doesn't? Yeah, and you know, David's been very much like, no changes to RSpec one because it's like, development's done. So if you want stuff like this, it's kind of gonna have to be in your branch. Although you are working on getting RSpec two, working on Rails two, but that is still in the works. Got RSpec two? Yes, it works only with Rails three, in terms of Rails. Beta? Huh? Did they release it from beta? No. No. Okay, so it's beta 10. Yes, so. We're using 2.0.0. Beta.19 right now. And there's actually a newer one, but it had a problem with WebRat that I didn't wanna deal with in here, but it turns out we're not using WebRat anyway, so it was avoided. But okay, so if you look at what's going on, well let's see, we should have, we should get past that one error, right? Expecting index, but it got rendered with tweets show. So now I go into my tweets controller, and I wanna say render action is index instead of show. Cool. So that way, when I go to a particular user's Twitter stream, then I'm actually using that same file that I was using before. So if we look at the test that we just wrote, we did a little bit of a refactoring here because we would have to set up the user twice. We would have to stub the user twice. So we said, okay, let's just define it once using let, and then stub it out in a before. And if you look online, 19, when we do the expectation, saying that it should receive find with BJ, then that basically overrides the stub that existed before. So you can initially stub it, but then constrain the method call further if you want. Correct. Because you're calling it in that before. Right. Like this is okay that you just explain. Right. More than like. Yeah, usually what I do is I stub things out in my before blocker and let or whatever, and then I'll have one test that has a should receive in there that tests out whatever I'm stubbing. So I know that each thing I've stubbed out is actually going to get called the way I expect it to be called. And then I don't care in other places. But you'll notice that like on online five, you know, I said that when we want to expect a call, then you should receive. But like if you look at line five and line seven, there is no way at all that assigns tweets would ever equal that hash or would ever equal that array of symbols unless you went through the method that I stubbed. Right. So I know implicitly that yes, I didn't set an explicit method expectation, but the fact that I got back the data that I wanted means that I know that I hit the part that I wanted to. So I can write a looser test expectation. Like I don't care, you know, what was called or how many times it was called. I know implicitly that I hit the method that I set up because there's no other way to get that data anyway. Right, we could write another test that has the should receive, but it wouldn't be testing anything new. So now we run cucumber and get, oh, couldn't find user with ID BJ. So we realize we've got a little problem here, which is that we're using find, which looks up by the ID when we actually wanna look up by the name. So we found that our unit test was not perfect in the sense that the subs and mocks are disconnected from the actual underlying objects. And what we're trying to do is just design the controller. Basically we're saying we find the user, then we get their tweets, assign that to the view and render this particular template. So I don't care that the rest of the stuff works, but I do need a test in place that lets me know if there are any integration problems. And so that's where cucumber comes in really nicely. So I would start off saying, okay, this needs to be, you know, this needs to be by the name. So I'll say user stub find by name, and we'll do a similar thing. Yeah, I do. Because you know, if we do find by name and it returns and that user doesn't exist and it will raise an active record, record not found, which Rails will automatically do a 404 for you. So it's kind of nice. So we look at this. It says, I couldn't find that user. That means we're going through the model that we didn't want before. Change that out. Run cucumber, we should be good. I hope. Oh no, I thought we did. Oh, couldn't find user with name as BJ. How about that? So in view other users, TwitterStream and BJ has tweeted. Is it a case problem? Is it a case problem? Well, let's see. We had lowercase BJ. We've got lowercase BJ. So that shouldn't be a problem. What are we getting here? Oh yeah, and cucumber has like the failure output. So I can actually run just that one particular scenario by copying and pasting the line that it gives me. Right, so I've got user with an ID of name. ID of one, his name is BJ. It says it's in line seven of tweets controller. Tweets controller RB show. Isn't it prams name? Yeah, wouldn't it be prams name? No, we're getting the right pram. No, we created the user before we say anything out. We said, so he's tweeted it, then I go to his TwitterStream. So honestly, I don't know. Gonna stop here? No, because we're almost done. So now I wanna do this. So now we're going into stupid print debug mode. So look, we have, so he exists. With that name, so it's our, maybe our prams ID. Oh, you know what? I know what the problem is. Or no, I don't have any idea what the problem is. Yeah, but then we're just gonna get nil, right? No, because we're passing it the ID because this is using like the rails, restful stuff. Right, so now we're getting nil here. That's why I wanted to do a bang here. So, I don't know, p prams. Look at that. Okay, so we're getting, ah, so it says with name is bj and that bj actually includes the quotation marks. So if I go into pass and I say, right, because we wrapped this in quotes, right? So now should be, if we go to tweets bj, this'll fail because the user doesn't exist, which is what I want. Now I'll do rails console. We'll create a user. I see he hasn't tweeted yet. So user tweets create body equals, this is bj's first. Hey, there we go. So now I can view my tweets by going to slash, actually wait, no we can't because we said it's tweets.all. So this would be now that we've introduced users, we're going to have to start to kind of, we're going to have to refactor and, you know, filter out some stuff in our homepage. But at least now we've introduced a new user there and we are using the same, you know, the same view logic but now filtering it based on the user. So before we stop, can we get some feedback? Are we going too fast, too slow, too high level, too low level, anything? All right, everybody, good morning. And welcome to this fourth and final session of our code immersion. We will be adding the last blisteringly amazing features of Twitter version 2.0. I believe we will be adding the ability to follow users today. So once you follow certain users, their tweets will show up in your homepage. Does that sound right, Mr. Customer? Yes. Yes, okay. We're going to try to go a little bit faster and not get off on tangents as we've done. Right, we will code for the first 15 minutes or 20 or 30, get it all out of the way and then all the tangents can start then once we get follows working. But if we have no follows, we have no business model or something. And nothing to scale. So on this feature, we're going to set up some background which are steps that we'll run before we run every scenario. This is usually used for things like authentication or say your app has the concept of projects and everything happens inside a project, you might set up just a project in here and get that out of the way and then go on to actually do scenarios inside of whatever that context is. This is Cucumber, not RSpec, and this has been in Cucumber for a long time. And that background will apply to every scenario. Yes, exactly. It's like a setup in X unit or before in RSpec. And if you're not careful, your background steps will slow down your test suite because if you set up a lot of data, it'll really, really slow down things because it runs every single scenario in this feature or whatever. Oh, PS, all the code that we did yesterday, we committed and pushed out to GitHub. So, you know, if you haven't seen it, or I mean, if you don't have the code yet, you can pull and be up to date. We don't really have a concept of logged in yet, so we have to write this step first. This implies we'll probably have to do some kind of authentication type thing. I don't think we'll go, we won't go into doing full authentication, we'll just have some sort of fake thing set up. Can we access this session right in here? No, do the cookies. Would anyone else like to pair, anybody? I've been up here the whole time. I get tired of fast. Yeah, I get tired of them fast. You wanna do it, Rob? Sure, why not? Come on. What's up? So, where were we? So, we had just run, we had just set up a step that says I'm logged in as whoever, and so, by doing that, we created the user and then put that user's name in the session, well, in cookies so that we can look them up and go to our homepage. Then, next step, we're just gonna be setting up follows, saying that, you know, BJ follows Pat, and then that Pat's tweet show up on BJ's homepage. Oh, Mary. So, right now we're watching Pat type. Thanks. Cool. So, as you can see, you know, this is just regular expression stuff. And then, are we gonna introduce the factories? Yes, let's do that now. All right. Actory girl. Oh, wait, you're right. We don't have a picture of stuff. Picture, oh, no worries. And this gives you an easy way of creating stuff. It's a very technical term. Oh, really? Because it's so simple. Because we only have one attribute of everything. Yeah. So, I don't even care. So, never mind. So, follow it on, you know, how to associate it. I missed that part. Oh, go for it. Let it break. Do we, do you write unit tests at the model level? To, you know, for instance, in this, you know, association ensure that both exist and stuff like that? I don't typically write, I don't typically write unit tests for associations unless I'm putting some kind of criteria on them because they get tested implicitly through everything else. So, you know, I know that if I can create a follower and I'm able to in the view somewhere I say, you know, look up the tweets through the followers, then that's good enough for me. I've got test coverage there somewhere. I just assume that Rails works. So, I can say, you know, has many followers, has many tweets, has many tweets through followers, and, or through, yeah, through follow-ease, I guess at that point. So, yeah, so I don't worry about it until I put some kind of conditions on or constraints, you know, like I say that there's a limit to the number of followers or they're ordered in a certain way, then I would start to test it. I mean, it can be as easy as just bringing in pseudo macros and you can just throw in a couple of, you know, it should belong to follower, it should belong to follow-ease, and then it'll ensure that those two lines of code are in there, lines two and three. How about this, we will do a unit test to validate uniqueness of followers saying that, you know, you can't follow the same person twice, obviously, right? You shouldn't be able to do that. You don't want their- Or follow yourself. Right, so we'll get a couple of those unit tests in there. Let's. Do you have rake cucumber? Or do you just run one by one? Rake cucumber will be test prepared as well. And initialize constant follower or follow-ease. That should make that pass. Cool. So apparently, I have tweeted, so you should not be single folks. I would have been done by now and Vim, by the way, made that clear. Cool. All right, so now. So actually, now what we wanna do is we've got, since we've got our follow-relationship set up, we haven't set up any notion of followed tweets. So that's what we're gonna wanna do is just do a basic unit test right now that says any, you know, any tweets that from somebody that I follow, show, you know, I've got access to those. I was gonna say, what about, instead of doing that, instead of, so we will have a describe, then another describe that describes a method without them. It's, you know, it's a style of 16. You'll notice, I was already done with that in TextMate. Yeah, I know, it's because TextMate is silly. So if you haven't, if you're not familiar with our specs nested describes, we're using a nested describe here. And it's actually kind of nice because if you set up anything in the outside describe, like if you do any sort of context or set up in there or befores or lets or anything like that, then those cascade down to the nested describes as well. So you can, I typically use these a lot in controller examples where I will say, I'll set up some basic context and then I'll say, you know what, I'll have one nested describe for a successful request and then one for a failed request and then any other conditions I deal with. And one thing to note is what I try to do, and there's nothing special about context in describe, but what I, as a general rule, I follow is we describe methods and then we, in the context is English, you know, so if you're trying to describe something, not describe it, if you're trying to, you know, show something, you use context. So context, some English, phrase, but you're describing a method. So if it would be, this is an instance method, but if it was a class method, it would be describe, dot, whatever, right? So just a convention that we use to identify stuff when you're looking at tests. So, so subject that follow tweets should, or this is the follow, sorry, yeah. Yep, should not include, or should include, yeah. Where is the difference here? You're following. Here I'm following him, here I'm not. Oh, gotcha. Yeah, because it's inside of it. All right, let's run it. So you'll notice it's failing, follow tweets, fails, and actually we've got another one that fails too, because so you'll notice like, we don't have a follow method yet, and we don't have a follow tweets method yet. So I wrote examples in the way that I want my code to look like. I wanna say that a natural API for saying that one user follows another one is instead of going through, instead of creating a relationship directly like I did in Cucumber, I'd rather say that I just tell this one user to follow the other one. And so by writing that in the example, now I've described, I've specified the API that I want, and now I can go ahead and implement that. That said, I've got this follow method and we're going to want to put some behavior around that, such as I can't follow myself, I can't have multiple follows. So as soon as we get done with this, we kinda make a mental note that okay, we've introduced this new method, that's gonna need some testing on its own to make sure that it's robust. So we'll go through, get these examples finished, and then we'll go ahead and spec out the follow method to include that behavior that we want. In the same way that we were just doing this one. Go ahead. You have anything in there to prevent you from following yourself? That's gonna be a unit test that is gonna be in the association level that we were talking about. So to prevent from following someone more than once and to prevent us from following ourselves, because that would just be narcissistic. And nobody liked that joke. Okay, so let's see. Does this make sense? Has many, nah, no, no, no, no. That's not what I'm trying to do. No, you're in the user, follow, please. So I need to say has many followers. No, has many followers. Or in P, I do need that. Yep. And I say, I guess this is on the follow. E, underscore ID, yep. Right, because I am, no, I'm the follower. I'm the follower. Okay, so we're trying to, okay, so I kind of named the follow stuff, what made sense when I originally created it. But now we're having some trouble figuring out what the actual keys are. So I would say that we'll get this working, but then it'd be a good opportunity to change it. But the other beautiful thing about testing it the way we're doing it is we can get it wrong here. I can say follow ID here. And if my tests are, say that the tweets are empty, I just change it to follower, and then that'll probably be right. So it's one or the other. I'm not entirely sure which, but my tests will let me know in a few seconds. Class name? No, it's not gonna be class name, because it has to go through the tweets association. It's, Josh Susser comes up first. Of course. Instead of rules, can I just take it through like that? Yeah. Little Rails API source and then the association. Yeah. Wait, no, follow us. No, we're trying to get the tweets through the follow us. And then it's saying that the source is the tweets association on it. Wow. Could not find the source association tweets. Oh, you know what? Because we need to do like a double has many through. We need to say has many. Something else. Followed users through follow us, right? And then I guess that source is, what? See, I'm a little, can we go see the, so this is a table that has a follower ID and a follow we ID, and they're both user IDs. So a user has many followers and a user has many follow us. New follow, so what are we going back? So, oh wait, yeah, so we just wanna say, we wanna say, well, yeah, that followed users is what I'm trying to get now, which is like the follow us, right? But I think it makes more sense. Like I'm trying to look at the people that I follow. So it's the follow users. Followed users, yeah. It's all tricky how you name these things. Okay. So it's a double many to many through on yourself. Yes, that's what we're gonna do. Right, that's what's tricky about it because it's, we're in the same. So we could very easily reach the limit. I mean, we could get into that kind of black hole of self-referential has many through times two. We'll find out. Okay. See, I still don't think we need that source there because if we are, if we say, if we change number three. Follow these. So if we change has many follows, right? If we add two has many follows and then we do two has many throughs, right? One that says the follow ID, follow we ID and then the next one will be whatever we call it with the foreign key of follower ID. Oh, well, I don't care about finding the people that follow me yet. Yeah, but if it's gonna work for both, it's gonna, I think it just has many follows. Yeah, so right now, like I don't care about who follows me, I'm just trying to figure out who my followed users are, like who I'm following. Who I'm following. Right, so I think what we do is we say this is getting a little bit complex for doing the tweets, we realize that there's a whole second join in there. So let's mark those first examples pending and just spec out followed users and get that working and then we can go through the next one. I'm with you. You'll notice I mark those as pending so now they're just kind of ignored for the time being. So we said, hey, that's getting too tough. Let's take something a little simpler at this point. Let's just do this. I remember doing this a long time ago, trying to figure out the naming conventions and these things and follow up users and who you follow, who follows you. It's always kind of confusing. Naming something. Who is it? Ken Begg that said, name it something horrible so you come back and actually think about it. Yeah, ridiculous stuff. I am too stupid to figure out the actual name of this method and then at some point you will be so upset about the name of that method that you'll come back and figure out what to name it. Subject, all right. All right. Now let's go and add that expected something to include nothing. So I'm gonna guess it was the other way. Just get rid of the source for now. Through something source, yeah, you were right. Is it one to follow we or follow were? Should be. Follow we, right? Did, yeah, I don't think. So follow.create, follow as a subject, follow as a user. Yeah, that's right. Let's put a debugger on where? I guess in the spec. There's only two fricking sources for this to do. Okay, so I don't know. What do you mean? Wait a minute, do we not have any follows? Did I just do P follow all here? Oh, there we go, okay. So my follow ID. Follow. My follow EID. Yeah. So that's interesting. The follow or should have a user. Oh, because something's not true at that point. Yeah, let's make it a lit bang and then just set the, it's one of the beauties of our spec too. So let's say, or call it me or something, you know. Ta-da. Okay, so the follower is one. The follow is two. The follow users, the source would be follow EID. User ID two, expected empty array to include something. Let's go back to the test. Can you print out? I'm one, you're two, I'm following two. You're following. Yeah, one follows two. Yeah, that looks right. That's what we want. What are we missing? Can you say me that reload maybe? Just, I mean, it's bad, but just see what happens. Let's see the original. Wait, do we have follows, right? Yeah, I don't even know. Oh wait a minute. Yeah, we, let's see what's going on here. As I say, p and me dot. Do row that follows. I bet row has a bunch of follows, right? So we look there, I've got no follows. So we're back to where we were, but because we went into the whole looking at tweets thing, we kind of forgot the whole this might be an R instead. And we don't need to reload. So give it all that jump. Sweet. Nice. Okay, so now. Can you just fix what you are? Say what? What did you do just now? Again, it was, it's, it's, you know, kind of wrapping your mind around the fact that the follow, if you go to the follow model, you'll see that there's, both of those are pointing back to a user, right? And so just getting it clear in your mind, which one you're trying to figure out when in that particular test was killing us. So in this case follows the foreign key was us, right? The instantiated objects, ID. Right, and so you'll remember, like, as we wrote the initial spec for the test, or for tweets saying like, what are the followed tweets? I was like, well, you know, this foreign key might be follower ID or it might be follow ID, I'm not sure. Then we started writing the tweets. We saw it was getting a little bit complex. So we stopped focusing on tweets and just went to followed users. And at that point, because we're getting into this so much, we had totally forgotten that, hey, we might want to change follow E to follower here. So I think it's obvious that over time, we would want to change the names and foreign keys of these to make it simpler. Something worse. But, you know, still took what? Somewhere between five and 10 minutes, which sucks actually for setting up an association. Yeah. Oh, it all does. So, okay, so what we're actually, this follows stuff is technically not a unit test at all and they're gonna be relatively slow because they do create stuff in the database. So in this followed users record, we originally create the me record. Then we create a second user to follow and then we create a third record in the database, which is the actual follow. And then finally we follow it up. I shouldn't say we follow it up. Finally, the next thing we do is do a query from the database. So all of this is hitting the database. This is just sort of how, this is how ActiveRecord works, right? It's based entirely on the database and SQL databases. So for certain parts of your app and your test suite, you can mock and stub the hell out of it and get a nice, fast, isolated test suite and other stuff you just have to test through the database because ActiveRecord is so coupled to it. You know, there's nothing wrong with that. That's just the way it is. Over time, it's best, I think, if you move this stuff into a separate directory or with RSpec2 you can use tags to filter it out and basically say these are database examples so they're slower. So you can run, you know, you can run your in-memory ones very, very quickly without slowing down your whole test suite because as you have 50, 100, 200 of these for all your models and your application, then your test suite's gonna slow down big time. Moving right along to the tweets. These are tweets, now. It should be a different error. So we have no method follow on the user, so let's start off with that. Wait, just do it on follow users, right? That create. So do we say here? Yeah, just like that. What do we have to do on follow users? That's it, because it already fills in, fills up the other stuff with your stuff. Cool. Because that's what we told it. Does that make sense to everybody? What we did there? We're gonna actually do it on follow you. Go to follow? No, you know what this? No, well, we can't do followed users because that actually creates a user, right? And we're just trying to create a follow. Users, yeah? I have no idea if this is gonna work because we are now saying, we're doing has many through, back to our table, and then going through that association onto the tweets table. So this is the part where it may all blow up. Now we call the example over. Double inner join. I think if you get rid of the following key, it might work. See, because now you're using the same follows. Yeah, we're still using the follower ID. I think we need to create a separate line three that with whatever we call it. Well, no. Are you using two instance variables? We're doing the simplest thing that could possibly work. Yes. There you go. Ta-da! We can come back and reflect there. Red, green, reflect there. Right, and on it, okay, so obviously, I mean, none of this would scale at Twitter level, obviously, right? So, but what we did here is we said, hey, doing an inner join and another inner join and on the same table is painful. I don't, we don't care about the performance of that query right this second. What we're trying to do is build something, build a feature that we can demo to our customer, and then we will, you know, over the next week or before we go to production, we will basically tell the database guy, hey, you figure out what the query should be for us, or, you know, we spend more time on it. But at this point, we can just say, you know, look up the, look up our followed users, get their tweets and join them. So, you know, we could do, you know, as the next bit, what we could do is order by the created at date so that we actually have, have everything in there. In fact, you know, why don't we do that? Yeah, and we can, yeah, let's go. Because followed tweets should include our own tweets too. We want to make our home page. Right, our stream, that's right. Right, so, and why don't we say this, so describe. So context is an alias for describe, doesn't matter which one you use. Sometimes, and I agree with Ro in this case, it's more readable to say like, now we're, we're kind of tightening the context here. Make sure we're still green. It's right in the before block, it's right another tweet. Oh, inside of that one, gotcha. Okay, so this might, should be guys out a little bit. What we're doing in this case is this operator here basically says we can check the contents of a collection without caring what the ordering is. So it's basically set comparison. So in this case, we haven't specified any kind of ordering. I could also say that it should include both of those, but in this case, you know, I know that it only includes those. So, yeah, I don't know. I mean that, let's, we'll change it to include because sometimes that can really mess with people. Right, and you can see it just, it doesn't, it's expecting something. So it expected both tweets, but it only got one, right? Because now we're only looking for our followed users. But now I can just append our own tweets to it. And again, something that we were red, we made it fast, and then we'll come back and we can't refactor it later. And this basically is the same. Yeah, now we're just checking ordering. So, and it's gonna be in reverse chronological order, right? So, so can you tell me why you didn't wanna put in the before? Because we didn't do it here, I mean. Okay. So honestly, like after we do this, after we write the second example and get it working, I would probably go back and delete that middle one that we did because it's testing more or less the same thing. You know, I know that it includes it by virtue of, of this has, I guess the only reason to keep it around is in case somebody comes through and changes how the sorting works, you know. And that's another aspect of the behavior, right? That it contains my own tweets, and then a separate aspect of the behavior is that they are in a particular order. And in this case, we're just gonna, yeah, make sure that it's an array and that it's exactly the right order. Okay, and it. Cool, so now they're flipped because we found the other tweets first, and then mine. Actacular, but always. All right, so I guess at this point, you know, there's two paths we can take. One is to spec out the follow example a little bit more and put those conditions on the associations. I think that first thing that we wanna do is just get that scenario passing and get that green, and then we can go back and tighten up any of the stuff that we have. We're gonna run out of time here, so I'd like to get that scenario done first, and then we'll leave the rest as an exercise to you guys. Yep, so let's go back to the feature. I go to my home page, which was a blue path done by tweets.com, user.com. Do we have that? Because we set that in here to cookie's user name. So in doing that, we broke one of our examples because now that we've introduced accounts, you can't really just go to your home page unless you have an account, so we have to update our tweets controller example to return a user. Actually, all we did was change the finder. We changed the finder on user, right? So. The controller passed in the distance. Subout user. Yeah, should receive something different, right? And normally, what you would do here is you would have some sort of plug-in like device or clearance or auth logic that would give you a current user, and then you would stubout controller.stub current on the score user, and then you would do whatever there that you need to do. Expected row. Don't you have to say controller? Text mic, that's what happened there. So. Say no. Okay, so I don't know if you guys, I moved kinda quickly, but I did a little bit of a trick here, which is that we are, we wanna look up the username via the cookies, and actually I'd prefer to do session, but just because of the way we set up Cucumber, session wasn't available at the very beginning, so we went through the cookies instead. And so when stuff gets, but then cookies aren't available at the beginning of an RSpec thing, or I don't know the exact syntax for it at that point and didn't wanna mess around with it. So what I did was I know the code that I need to look up a user by the cookie, so I just extracted that to a method on the controller, and then in my example, I stub out that method. So you'll hear a lot of the time that you don't want to stub methods on the object that you're testing, but in this case, I'm basically just setting up some login state, and like Ro said, I would be using clearance or something like that anyway. But so that's just kind of a little trick, to extract a bit of code, stub it out, and then let the integration test make sure that everything works together. Okay, you guys are totally gonna hate me for what I'm about to do, but like I said, we would be using some kind of authentication framework, and we could absolutely get this working with the cookies or the session, but I don't know what the method names are off the top of my head and how to set it up. So don't shoot me and keep in mind this is just an example. We are going to set up the username in a global variable. Oh no. Kill me now. No, it's a global variable. Oh my gosh. See, Ruby global variables are so unused in Ruby that Ruby programmers don't even know what the syntax for them is, which is a good thing. I'm proud of that. It's a good thing, be proud. So the other thing that we, well, I said that we're gonna include the tweets username in there. So that's why it's failing, and we could do some cool stuff on actually, let's just do it. We're gonna do some more nasty stuff, but I'm just showing you like cool Ruby stuff that we can do to get stuff passing at this point. And we're running out of time. Right. So. I see why BJ volunteered me for this. Ha ha. Me dot. Me dot followed tweets dot first body, should equal this is Rose Tweet. Aileen. All right, so let's see. When I look up somebody else's tweet, it doesn't include the name yet. So I'm going to go in and extend the tweets on the fly, which is something that you would not wanna do, but for purposes of this example, it's fun. So you're gonna go on. I'm like, Ailee's gonna have to tune this? Oh my gosh. We could, yeah, we're gonna reopen it, right? No, I just wanna do this. I don't wanna do this. Debt, tweet dot read. Oh, does this belong to a user? To that user. And soon it'll say. To that user. On the find methods that should be read and should be added. And it shouldn't have been user. Well, we're inside of tweet. Oh, user, yeah. I should just say it doesn't know what a user is. Right. So you got a tweet, it belongs to a user. It's from all of our examples. There we go. By the way, this is dirty. Ta-da. All right. We have a debug statement somewhere. Yeah. We'll live. Okay, so next up, run spec, cucumber, make sure that everything is good. I'm not gonna load this stuff in the browser because our tests are great. We know it works, except for all that. Except for when they don't look at the user. Yeah. You have to type it on the other ones too. Which one? Where are these? My Twitter stream. My Twitter stream feature. Oh, when I go to my own page. Okay, right. So my Twitter stream background. Do we not include my Twitter stream? So you will go back to that definition, that step definition? No, it's your. Okay, I'm gonna say I don't actually care about this feature for the time being. This is one that we could spend a few minutes to fix, but we're over our time now. Obviously we'd want to get that working, but we did add accounts. So that means that we're gonna need to change a little bit of the stuff around some of the existing setup. I'm not entirely sure what it is, and I'm really tired. But I hope that this has been instructive for you guys. Towards the end, we did some stuff that you would pretty much never do in a production application. And by pretty much never, I mean absolutely never. But it was fun, and it helped us get to our green scenario so that we could actually start to demo stuff. And then at that point, once we've got those tests in place, we can go back, we can add unit tests for some of the other behavior that we needed, like the follows, and we can refactor so that we are, you know, using a nice database query instead of doing all the stuff in memory, which wouldn't work for, I don't know, 10 million tweets or whatever. Or even a thousand. So, wrapping up here, you know, we got follows working and followed tweets, so I'm happy about that in the three hours of talking. Any questions about what we did so far, or comments or anything? Mine is the last 10 minutes.