Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Unit Testing Outside of Models

Unit Testing Outside of Models

We'll cover a quick introduction to model testing and the parallels with functional tests. What tools are available in rails to make testing our controllers easier, and how to drive new features using tests. Also, we'll talk about extracting controller functionality and pushing it into the model layer. This puts us back into the comfortable model area, and gives us the added benefit of simplifying our controllers!

csaunders

May 29, 2013
Tweet

More Decks by csaunders

Other Decks in Programming

Transcript

  1. Testing? Tuesday, 28 May, 13 When we think of testing,

    it’s the easiest to grasp when talking about things like models. This is just a single item to test, and all the side effects are locked within that one item.
  2. :message << Result >> Magic! Model Tuesday, 28 May, 13

    This is normally done by taking our model and sending it messages. Based on those messages, we’ll either get a result, or change the state of the object. From a consumer perspective we don’t really care about the innards of the function, just the result. As far as we are concerned, everything in the model is magic.
  3. Controller Tests? Tuesday, 28 May, 13 But when it comes

    to controller tests, we let stuff slide a little. Why is that? What makes testing controllers so much harder?
  4. ¯\(°_o)/¯ Tuesday, 28 May, 13 Sometimes it’s simply “I don’t

    know” or because we’ve never really done it before But, let’s take a minute and think about this.
  5. Break it down? Tuesday, 28 May, 13 How can we

    break down a controller into easy to test pieces? Let’s look at how a single request interacts with a controller
  6. :message << Result >> Magic! Model Controller HTTP Response HTTP

    Request Tuesday, 28 May, 13 If we think about it, our HTTP Request is just another message being sent to an object (our controller). The resulting object is the response, and there is a good chance there’ll be some state changed along the way too.
  7. Interrogation Tuesday, 28 May, 13 So we can really just

    consider our tests as a bunch of questions we are sending to our controller. What do you do when I send you this kind of HTTP request? What’s your response? Did you change anything on the server? That kinda stuff.
  8. Start Small Tuesday, 28 May, 13 But first, let’s start

    small. We aren’t going to dig into the codebase of some goliath of a codebase. We’ll do this in tiny bits.
  9. Pantry Tuesday, 28 May, 13 The app we are going

    to be building is a pretty simple one. We want to be able to add groceries and stuff to a list. We’ll call it Pantry.
  10. No Model Tests Tuesday, 28 May, 13 Now, let’s get

    this out of the way first. To start, we aren’t going to be writing any model tests!
  11. OH MY GOODNESS GRACIOUS!! Tuesday, 28 May, 13 Is probably

    what you’re thinking. But let me show you the model we have.
  12. Tuesday, 28 May, 13 So seriously, if we had to

    go in and test it, we’d be testing our ORM which in this case is ActiveRecord. A simple rule of thumb for testing is “Don’t test what you don’t own”. And as far as I’m concerned we don’t own AR.
  13. Spec? Tuesday, 28 May, 13 So what’s the specification of

    our application. For now, let’s just assume we have a list of ingredients and we really just want to be able provide that to the person using the application.
  14. Test First! Tuesday, 28 May, 13 No no no fine

    people. We’ve gotta start writing out our tests. It’s that kind of attitude that’ll get you stuck in the controller hell you’ve possibly experienced in the past.
  15. /ingredients Tuesday, 28 May, 13 So, let’s assume the route

    is all good to go and we have our templates all set up. All that is missing is the actual work on the controller side.
  16. Tuesday, 28 May, 13 So we write up our first

    test. It’s pretty simple stuff. We just want to get the index and verify that a variable named ingredients was set.
  17. FAIL! Tuesday, 28 May, 13 Of course, since we are

    doing this right, the test fails wonderfully since we are missing our implementation. Yay, that’s pretty awesome.
  18. Tuesday, 28 May, 13 Now for the implementation. That’s it.

    We assign the variable and respond with it. Done like dinner.
  19. /ingredients/:id Tuesday, 28 May, 13 So the same thing can

    pretty much be said about the show action for an ingredient. Because of that I’m going to skip it.
  20. BAM! Tuesday, 28 May, 13 So let’s step this up

    a notch. We have our read actions, and they are pretty simple. But why don’t we look into a create action? But not just a simple “an object was created action”
  21. Tuesday, 28 May, 13 Here’s our form that we are

    assuming some badass designer has written up. The user is going to fill in some form and when they submit it’s going to hit our controller. We do our Magic, then redirect the user back to the index along with a flash message letting the user know their item was added
  22. Tuesday, 28 May, 13 So here’s our test. It’s pretty

    straight forward. Now let’s get that controller action actually written up.
  23. Tuesday, 28 May, 13 As you saw in the test,

    we are able to get access to the session’s flash so we can verify what the output was. In here we make that new ingredient then set the :notice based on that. Finally redirect the user. Easy stuff.
  24. Tuesday, 28 May, 13 That message is pretty awesome, but

    will probably make someone angry. Let’s fix that up but where to put it?
  25. ingredient.rb Tuesday, 28 May, 13 After a bit of though

    I figured, let’s just put it on the model. There is a chance we might need to get the plural for an ingredient again, and this will help keep us from duplicating ourselves, which is an added bonus.
  26. Tuesday, 28 May, 13 So we throw some simple tests

    for our ingredient. We just want to be sure we cover those “edge cases”
  27. Ship It! Tuesday, 28 May, 13 And version 1 is

    done. It’s shipped. Awesome, let’s have celebrate.
  28. Case Insensitivity Tuesday, 28 May, 13 The first one is

    there is a bug/missing feature that if I add the same ingredient but spelled in a different case combination that a new item is made. That’s silly. Let’s fix that.
  29. Tuesday, 28 May, 13 So we go into our ingredient

    test and add some more tests for handling case sensitivity
  30. Tuesday, 28 May, 13 Then we add it as a

    scope to our model. If you are unfamiliar, scopes add a way of making it easier to do searches since it keeps duplication down and gives you a bit more context. Because it can take a closure, you can even make it require variables
  31. Merging Tuesday, 28 May, 13 Now the second feature is

    that we want to be able to merge items when we are creating them. So if I had 4 apples in the fridge and bought another 4, I should have 8 apples in the fridge. Not 2 sets of 4 apples.
  32. Tuesday, 28 May, 13 So here we have our functional

    tests for the merging of items and the merging of items with different case. Now let’s go to the implementation.
  33. Tuesday, 28 May, 13 So what we want to do

    is find the ingredient or initialize it based on the insensitive value of it’s name. Then we do a bunch of assignment and stuff. Increment the amount by the amount that was passed in. To keep our message correct, we create a temporary object and use it’s pluralized value for the flash. Then we respond_with our object.
  34. OMGWTFBBQ?!?!? Tuesday, 28 May, 13 Now you are probably looking

    at that and thinking. Woah that’s kinda gross and out of control. There’s so much going on there. Yeah there is, and.
  35. Tuesday, 28 May, 13 We’ll leave fixing that up as

    an exercise for the reader. In honesty, all that we really need is a kind of Service object that goes in and does all the gross bits for us. We can then pass it messages and all that stuff, for the things we actually care about. That object, would awesomely enough end up being a plain old ruby object, that we would also call a model.
  36. Minitest Tuesday, 28 May, 13 Well up until recently we

    were using Test::Unit but we’ve moved over to minitest. It’s built into the standard library of Ruby 1.9.3 (and 2.0 I believe) and is also amazingly fast. Some other nice features, that we aren’t using though are it’s spec DSL that you can use, as well as a Mocking and Stubbing framework. If you haven’t, definitely check out the source code for MT 5.0.0, it’s an easy read and shows you how simple a testing framework can be.
  37. Mocha Tuesday, 28 May, 13 For stubbing and mocking we

    use Mocha made by the chaps over at Freerange. I’ve used the MT mocking and stubbing framework, and haven’t been wholly impressed by it. Mocha makes stubs and expectations much easier and is an easy tool to use. It does have a bit of magic though, so if that’s not your thing, then you might want to avoid this one.
  38. Rails Parallel Tuesday, 28 May, 13 At Shopify we were

    having some serious problems running our Unit Tests in any meaningful timeframe. This is partly because our tests are slow, but also because our test suite has gotten massive. This tool helps bring down the runtime of our tests by a drastic amount and takes advantage of all those cores we have.
  39. Timecop Tuesday, 28 May, 13 A lot of what we

    do involves time. Placing orders, dealing with subscriptions, billing, credit card authorizations. All of this stuff is really time dependent, and of course, things break come the time changes. In an effort to help reduce that we’ve started using a tool called Timecop. It allows us to jump around in time, and freeze time such that we know exactly what to expect. It’s really helped out with our tests!
  40. Fakeweb Tuesday, 28 May, 13 Finally, we deal with a

    lot of 3rd party APIs. And being 3rd parties we can’t always rely on them, because that would cause our tests to break every once in a while when a Payment Provider decides to do maintenance on one of their integration servers or their service goes down entirely. In order to get around that, we use fakeweb to replay API responses from various 3rd parties, so we can keep our test suite running fast and stable. That doesn’t mean we don’t test our integrations. We still do, it’s just that we run them as “remote” tests so that when they fail it’s not such a big deal.
  41. Questions? Tuesday, 28 May, 13 And with that being said,

    I’d like to open the floor to questions