 Thanks for coming. This is my talk entitled an introduction to spies in our spec And I think we'll get started. So I'm Sam Fiffin I'm Sam Fiffin on Twitter and Sam Fiffin on GitHub You can have a look at my various profiles on those sites if you want to and if you do have a look at my GitHub profile You'll probably notice that I spend most of my time on GitHub working as a member of the aspect core team and that's sort of why I'm here giving this talk today because to me, it's really important that our spec is sort of represented in community events like this and also that we give introductions to beginners that enable them to more powerfully and quickly Use the testing framework. So I hope that everyone goes away having learned something about our spec today I work for a company called fun and plausible solutions. We're a sort of consulting agency for data science problems, which means that we tend to work with Companies that know how to build really great web and mobile applications, but don't necessarily know how to do things like machine learning or recommender systems or a be testing and things of this ilk and If you're trying to do those in your own work and you're struggling for whatever reason Please do come and have a chat with me After we're done because I love talking about this stuff So I wanted to sort of Preface this talk by saying that My sort of conceit for this talk is not for me to drop some grand position on software engineering or Present my ideas for what we should be doing ten years down the line, but instead sort of present an interesting slice of facts about testing and how aspect works and I'd much rather everyone in the room Learn something then I get to the end of my talk So if I say something that you find confusing or you'd like me to expand upon I would ask you to interrupt me and ask a question because More likely than not if you have a question about something someone else in the room will as well so this talk is really about Testing and how we actually go about testing our software in Ruby and the tools that we use to do it and One thing that I find amazing when I work with people That use different programming languages like I work with a lot of people that do Python and Java and build apps for iOS and Android is that The Ruby community is the community that has just embraced testing they've really sort of engulfed it and accepted it into their everyday Practice and that's not the case with many of the other programming languages and communities and To me it's amazing that we do as much testing as we do Even at the beginner level because even if those tests aren't perfect even if for whatever reason they have problems It still means that I can walk up to Someone's app that I've never seen before and begin confidently making changes Without having to worry about what's gonna happen as I do those changes and I'm gonna break something and I wanted to sort of provide my thoughts on Why writing tests why actually building automated testing for software is a really useful thing to do or at least one perspective that you could take and to me this is really to do with Mental models of how we write our software So in the beginning when you're working on an application I would argue that it's entirely possible for you to hold nearly everything that your software is doing in your brain and that means that it's really easy for you to make changes with confidence and Adapt to the software that you're writing but as time goes on and our product managers and our users come to us with feature requests or Bug reports and we you know make changes and grow our software that becomes more and more complex and our software begins to get sort of Bent out of shape and it becomes very difficult to hold everything that your software is doing in your brain And to me this is where the tests come in they literally allow us to serialize knowledge about our application Into an executable form and I think this is one of the things that I Often see beginners struggle with is actually a good impetus for why writing tests is useful They understand sort of behavior verification and so on but that doesn't seem to be a long-term goal Once you've got the behavior written That's it right well Yes, and no, and I think the just sort of test suites soaking up knowledge is Really useful to have in the applications that we write And I think that that knowledge as it grows over time allows your team to expand and allows you to continue to work with your software It's also true that when you're writing tests alongside software together So growing your software in your tests at the same time You're able to find bugs in new features as you're developing them And what that means is that you can build your feature and deliver it knowing that more likely than not It's going to work when it's integrated with all of the others And also that all of those bugs that you encountered whilst you are developing your software are less likely to actually be present ever again in the future if you just refresh a web browser Every time you make a change the software that you're writing. It's more likely that those things are going to come back and perhaps more than this when a Bug in our software does make it all the way from us through our managers through our QA And all the way out to our users If you write a test that demonstrates that bug and fails when that bug is present and Then you implement the fix to your software that allows you to actually verify that the bug is gone You can be pretty certain that that bug is never Going to come back and I think that that's a really useful property to have when writing software It's also true that we can write tests that actually help us improve the design of the software that we're writing Some kinds of tests when you write them allow you to focus so deep on a single piece of code in your application That the natural result is that the actual design the software architecture of the system that you're writing improves and This is a really useful property to have when writing tests but To talk about that in any detail We need to talk about the kinds of tests that it's possible to write and I wanted to start with the sort of test that I think Most beginners write when they're thinking about how to test applications And that's an integrated test and the idea behind an integrated test is that you're going to take your entire Application your database things that talk to the internet email systems Amazon access whatever and just box it up and then you're going to sort of interrogate that entire system as one piece effectively interacting with your application as a user would and Faking no part of the world in which your application Lifts and this kind of testing this integrated testing I think is really useful For certain kinds of behavior that we expect when writing systems So it's generally true when you're writing integrated tests that if an integrated test fails Your application is definitely broken and if it's passing that means that your application Might be working and those sort of information keywords there are really important the opposite end of the testing spectrum is an isolated test and An isolated test takes a single piece of your application a class or perhaps even an individual method and Isolates it from all of its dependencies and all of its collaborators and forces you to focus on the very specific Implementation of just that piece of functionality and in order to achieve Isolating testing you necessarily have to fake part of the world that that test is going to touch a way to think about it is that when you're writing an isolated test you're basically Hiding as much of your application as you can from that piece of code in order to be able to test it on its own Isolated tests due to their extremely focused nature are what give us our ability to Exert design pressure on the software that we're writing because if you find that it's difficult to write an isolated test You'll generally find that the design of your system has some problem The software architecture that you're working on is not as flexible as it might be and the sort of Intuitive explanation here uses the idea of coupling if your Component in your system is highly decoupled from the rest of your application It's very easy to isolate it and if it's highly coupled to random parts of your system That's not the case and that's sort of why isolated tests are useful It's also true that you know a spectrum of tests exist in between Integrated and isolated tests For example, if you're building an application that uses a service oriented to architecture You could take a single service out of your application and test that on its own By faking the other services that it's going to talk to but not faking any of the objects that are internal To that service and that would be like a partially integrated and partially Isolated test, but it's just to sort of demonstrate that there are a spectrum of Isolations that you can apply to tests and that's an example, but I Wanted to sort of talk as well about The use of actually faking different components of our system and to do so I wanted to use An analogy that I've actually borrowed from Justin Searle's who's giving the closing keynote of this conference he gave a talk about Isolation in testing that I found quite useful and the idea here is that let's imagine that we're building a GPS system for a new Boeing 747 Well, we could do an integrated test as we were building our GPS system Literally put it in a plane and fly it in the plane like crashes because we wrote our GPS wrong We do this again and again and again until our system works but that's obviously going to be very Expensive and slow and destroy a lot of planes and Like if we take that GPS unit and we isolate it You know, we can test and get fairly confident that it's going to work before we put it in any plane whatsoever And that's going to be a lot faster and a lot cheaper and a lot more useful and we can sort of draw a parallel in building computer systems right where You know talking to a database server and an email server and Amazon is Really kind of going to be expensive right and it's not going to work all of the time If your network is down for whatever reason or you're in a foreign country and your Wi-Fi isn't working and you can't have roaming Anyway So yeah Let's talk about ways that we can actually fake Different parts of our application and talk about how those are useful and this is where we're going to deviate from sort of talking about testing in general and start talking about our spec in specifics so In our spec and in fact some other testing frameworks. We have a concept of a stub and the job of a stub is to take some object that our System collaborates with are sorry a component that we're isolating collaborates with and Fake out a response to one of that objects method calls So what you're going to do with a stub is going to pick an arbitrary object that your object collaborates with and replace one of Its methods with the stubbed implementation and the idea with a stubbed implementation is that it's so Simple that it allows you to not have to worry about the implementation of that collaborating object But instead just allows you to focus on the implementation of the object that you actually care about whilst ignoring that particular collaborator Stubs are really useful for taking an object and isolating it, but they don't allow you to verify that any collaborations between objects actually happen and it can be a desirable property to actually Test that our objects are collaborating with each other right if my object depends on something else I may want to verify that I'm actually calling that other objects methods and to do that you use what's called a mock and Mox are very similar to stubs They take the implementation of some method on another object and they replace it with what's called a mocked Implementation whose job is to actually check that a call occurs and then cause the test to fail If no call is made and so where stubs just merely allow you to isolate yourself from a dependency mox allow you to verify that you're actually interacting with that dependency and so now We should talk about spies. That's sort of what the title of this talk is about and spies are different to stubs and Mox in that their job is not actually to replace the implementation of any individual method But they are objects in your own in their own right So when you're using a spy in your tests You're actually creating a new object and then pushing that object into your test in a way that allows you to sort of isolate with your collaborators and If all of those words didn't make all that much sense Don't worry because now I'm going to do some live coding and hopefully it's gonna go fine so And let me just mirror my displays here. I Think everyone should be able to read that I ran to the back of the room and checked But please holler if you can't so has anyone here never used our spec before Cool. Oh that that guy So This is a really simple just like template our spec test file And all we're doing here is we're loading a file called spec helper via require And the spec helper just sort of sets up some common defaults for our aspect test suite And then we're using the describe method here to actually set up a group of tests And we're going to write all of our tests inside this described block to actually do some things So let's have a look at how spies in our spec actually operate Then I'm going to move on to an example using an existing piece of code And then I'm sort of going to wrap up and take questions and we can play with the technology. So So it records method calls So in our spec the way that you get a handle to a spy object is you just invoke the spy method Anywhere in the body of your test when you're writing tests in our spec Use this it methods to create a new test and then everything in here is actually the sort of body of our test So I've got this handle to a spy object, which is called my spy and the way that I Set expectations that methods actually get called is with the some sort of normal aspect syntax where we say expect my spy to have received foo and What this line of code does is it will check All of the method calls that have been sent to the my spy object and see if any of them match the method name foo And so if I run this test It's gonna fail and the reason that the test has failed is that it says right here Double dot foo any args expected one times with any arguments and received zero times with any arguments So all I need to do to make this test pass is invoke my spy dot foo now if I run the test again It's passing So what's actually happened here is that when I've invoked the foo method on the spy object It's recorded that that call has been made and then I'm actually checking which calls have been made here on This final line of the test You can also match arguments When you're writing tests with spies, so if I copy the body of this test and drop it in here I Can add this with Call to the have received call which will actually validate the arguments get passed So what I'm expecting now is that there will be an invocation of the foo method with the arguments one two and three And if I execute this test It'll fail because it expected one two three and it got no arguments if I delete the call all together It will go back and say that it was expecting one times with that one two three argument list But it was received zero times But if I just add the call back And actually provide the correct argument list that test will now pass Finally You can actually also check that method calls happen a specific number of times So what I'm going to do is check that the method was received four times and then do it Times the method was called Let's just copy the body of this And the way that you do that is with this slightly sort of funky syntax where you say Exactly and then a number and then dot times this this is all just method chaining So what's actually happening there is have to received creates an object and then all of these calls are just calling back Onto that same object And so this test as you would expect it's gonna fail because it was expecting to receive four times And I only got it once so if I copy this and paste it out three four times and then run the test and Everything is now Passing so this is the basic things that you can do with spies you can check which methods are being Called you can match against arguments and you can also validate that calls are being made a certain number of times so I'm now going to move on to looking at a test for an actual piece of ruby code and so What I've done here is I've written an object called counter client and the job of counter client is to provide an API Rapper around extremely simple HTTP service that I've written which stores counts that are provided to string keys And so what this is really doing is it's making HTTP requests out to like some external service and then sort of providing responses to those and so Basically what I've got here is a set of integrated tests for my counter client Right and so the behavior that it has is that if I don't increment a key at all And I call the get method on that key. I get the integer zero back If I call increment once and then I call the get method on that key I get the integer one back and Finally if I do this a random number of times I get that random number back and so these three tests sort of provide all of the coverage that you need To actually validate that this object is correctly counting string keys Just to prove to you that the implementation actually works If I run my tests They all pass for the counter client and you can see here that the runtime is Significantly higher and it is actually making HTTP requests so One thing to note here though is that nothing about these tests Actually dictates that HTTP requests are getting made They're all just doing simple interactions with the counter client Object and we don't actually have any proof That any kind of talking to the network is occurring If we look at the actual implementation of the counter client object We can see here that it's using this L HTTP thing to actually make HTTP requests To the service base URL, which is just a hard-coded localhost 4567 string under the key and then when we make that HTTP request because the API returns the count as a string and like an HTTP response body We need to convert that back to an integer to have the behavior that we actually want so We sort of established that our existing set of tests Don't actually validate that we're making HTTP requests So let's do that for one of the methods using mox And then we'll do it with spies and see what the difference sort of is So I'm actually just going to do describe Get here and this is a kind of aspect idiom when you're describing instance methods on Individual objects so when you're just testing an instance method on a particular object in the string that you provide with the Describe block you typically do like the hashtag symbol and then the word get oh And then the word that is the same as the method so It calls the get method on the L HTTP Client right that is what the behavior actually does You can see here So let's implement that and This is how you do a mock in aspect you say expect and then the thing that you want to mock To receive and then the method name And I'm going to do the argument here, which is HP local host four five six seven key One thing we do need to do is lift key up to the top level of the test So our spec has this mechanism called let which allows you to take common pieces of the test that you're writing and Extract them so that you can reuse them without having to repeat yourself inside the individual tests And I've currently caught this let inside the describe for the integration tests as opposed to describe The entire class so I'm just going to move this up a level So that becomes available to all of the tests in this file And so now that key reference will be the same as the value that I'm I've got up here and then just going to call counter client get key and That should make the request right because we're calling the method and that method collaborates with that object We should be fine Let's do that Cool, and so the new test that we've just added is passing That's useful, but I always think that it's a good idea to see a test that fails as well as a test that passes and so what I'm going to do is I'm actually just going to Comment this line out and then go back to my tests and run them And we see here now that all of our tests are failing But the one that uses the mock the one that sets this expect to receive is also failing So we know that it's possible for that test to fail We've seen it in both states and so I'm sort of happy with that test now, and I'm going to move on by fixing the implementation so We sort of now verified using mocks that our object is collaborating With the HTTP client correctly by making the get request But there's a couple of problems with this test that we've written The first one is that the order of operations in this test is different to the order of operations in all of the other tests in this file So if you look at this one above there are three distinct steps, which I'll highlight here by pushing them apart We have this sort of setup step where we actually generate the random number of calls that we're going to make We have this action step. We're actually doing the calls And then we have what's called the expectation step. We're actually checking that the state of our system is correct And this is a sort of idiomatic test design pattern called a range act assert And the idea is that if you cause all of your tests to follow this pattern It becomes extremely obvious for other people to work out what your tests are describing and how they actually work If you have those operations in any order, it can become much harder to understand What the test is actually doing and so whilst this test above does follow that pattern this test below doesn't There's no sort of setup step, but that's fine because our setup step is basically to do nothing But then we've got action and assertion the wrong way round Right. We have this assert step coming straight before our action step And it's really common to see that when you're using mocks Because mocks set an expectation at the beginning of the test that an interaction will occur Somewhere in the duration of the test And the reason that that's kind of problematic is it breaks this arrange act assert pattern In a way that means it can be slightly harder to work out what your tests mean And this is a trivial example because we've only really got two lines in our test and our client is really really simple But as you build more complex systems and your test becomes more complicated This can cause a real pain and so we can fix that using spies The other problem this test has is that it's running away and it's monkey patching a library That we don't have any control over so LH TTP is a random constant that is provided to us by that library And it's not something that we you know really wrote or have any ownership of the API of and generally speaking It's a bad idea to sort of change the implementation of things that you don't control so This sort of leads me to want to make some changes to design of both the counter client and the tests that we're writing And so let's go ahead and do that So what I'm actually going to do Is I'm going to create a new let called HTTP which up here will just reference the LH TTP constant We're actually going to pass that into the counter client And then in the implementation of counter client. We're going to change the constructor To actually take the past HTTP client in and then we're going to change the implementation of this HTTP client method To just return the instance variable of the past in HTTP client If we go back to our tests and run them all they should all still pass And this is an example of an extremely small refactoring. We've made will allow us to improve the design of our tests in just a second so What I'll thank you typing is hard Great all of our tests are passing. Thank you pair programmers in the audience so That's great because now what we can do is we can go down to this test and override the definition of HTTP To just be a new object And now when I do this That just went ding Object is not implement get oh Let's use a double sorry I screwed up so doubles in our spec are Objects that allow you to take just Simply give them a dictionary of keys to values and they will just implement Stubs on themselves that implement those methods so what we've got here is A simple double object which just implements get returns nil and then we should be able to expect on that and again All of our tests are passing But now because we're at a state where we can actually provide anything in place of the HTTP client and Write our tests in a order we can use a spy to get back to that a range act a Model for our tests and so I'm going to replace the use of a double here with a spy and Then I'm going to move this down here To say expect HTTP to have received get with that argument Now this is going to work because spies respond to all methods when you pass them Into your test and then you set expectations afterwards. So if I run this They like it will pass and so now what we've got in our test is Much better, right because we're not reaching on to the L HTTP object And replacing the implementation of one of its methods And we haven't got our tests out of order. We're following this a range act a certain pattern that allows us to Simply and obviously have a structure for our tests and so that's sort of All of the code that I had to write directly. I'm going to tab Back to my slides for just a moment and then I'll take sort of finishing Questions so as You might have been able to pick up From my accent. I'm not really from these parts I am in fact Actually British or as my friend that lives in Boston like to say really British It's quite a ways to come Really really tired woke up at 5 a.m. This morning and I have One small Rant that I have to deliver as a British person to a room full of Americans And it's to do with the quality of the tea in this country So I really like tea. I think tea is a really good way to relax calm down, etc. But I Cannot for the life of me get a good cup of tea in this country and and like So some of my friends who I was living in Atlanta before I came to this conference took me to a tea place there and Like it's a professional tea place and it gave me bad tea So so the reason I have I actually I understand the reason It's to do with how you prepare water for your hot beverages in this country because nearly everyone drinks coffee And the ideal temperature to prepare coffee at is about 92 to 95 degrees centigrade For tea the water has to be boiling when it hits the tea back And so Americans if you do nothing else if you have learned nothing from this talk Learn to boil your water properly when you put it in your tea Thanks very much for listening Um Someone some people have questions. I don't care The question was what about tea at a higher elevation. Sorry. Yes What is that a real question? Right. Give me a second, and then I'll be back aspect three was released about two or three months ago It's the new major version of aspect and that means it has Breaking changes and I know that sounds scary because like your test suites are the lifebloods of your applications, right? I think everyone would be fair to say that they couldn't confidently delete their test suite today and Be happy to continue working on their applications and similarly doing a major aversion upgrade is scary There's a really good upgrade process for aspect three, but many people don't know about because it's not very obnoxious and in your face I Would highly encourage you to look for the aspect upgrade guide on the internet because it will make your life easy and there are automated tools to help you Okay, now I now I really am honestly done I'm Sam Fippin on Twitter and github my email address is Sam at fun and plausible.com if you want to talk more and you can't Find me at the conference Thank you very much for listening to me rant at you Let's have oh you guys