Design Tech Talk Series Presents: OO Design for Testability

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hey welcome everyone this is the fourth talk in a series of design tech talks the design Tech Talk series we will be taking questions on go slash design portal and specifically today I'm going to talk about design for testability I guess we can ask questions on the design photo but also if you want to just ask question just come up to the microphone and interrupt me at any point it's cool with me anyways let's get started so how many of you guys are interested in testability how many try to write tests well most of you guys awesome and how easy do you find that not so easy all right here's I want to kind of try to this is my opinion why it's not so easy so here's how it kind of works now you got the software engineers you got the QA and you got the test engineers and here's what most people think about how the process should work so first you got the developers it's red because that's what they put the bugs in then you have a separate organization who tries to go and do manual tests and do exploratory tests and so on and at some point you get kind of tired of it all and you say well I want to automate this process so that I don't have to do it over and over again so you get test engineers and they try to write some automation tests and they try to write the tests and it just doesn't go it's really really difficult to do that so people start looking for all these kind of magical tools that you can you know run over your codebase and magically it becomes testable right and so use these third-party tool lenders come to you and they'll tell you oh if you only use mercurial or selenium or whatever like all of your testing these are going to be solved but we know better don't we so there is no testing magic on this stage the thing is and this is kind of hard for people to get used to is that all the testability magic happens at this software development portion of the whole development process so you as a software developer are responsible for not only making sure that the code is testable but in my opinion also you're responsible for the unit tests which is the lowest level test that we have and then the test engineers are more kind of responsible for the automation test for end-to-end testing where you are pretending to be a user right so as a software engineer you want to know that your individual methods work where as a test engineer you want to know that the overall system works properly so usually when we come to a team and we say hey we should do some testing people say well we don't test because and they give you lots and lots of excuses so I'm going to kind of quickly go over the excuses before we jump into the good fun stuff of actually talking about how you design for testability so I'm going to break them down into two parts which is the valid excuses and the common misconceptions now the valid excuse for not testing is that you have legacy code now I kind of used to think that that's a valid excuse but now I'm thinking now like you have a legacy code but it's probably you who wrote it and if it's not you now you inherit it and you got to deal with it so you got to clean up your act like I know the code is not testable but you can't continuously use that excuse forever and say well it's legacy and therefore I'll never test it right so you just got to suck it up and fix it and I'm really quickly going to go through all of these they're all common misconceptions and the talk is really about design not necessarily about the misconceptions I'm just going to pass through them the other end of the spectrum is that when you write UI tests are a lot more difficult to write they're not impossible they're a lot more difficult but here's the thing here is one valid excuse that you have for not writing tests and whenever I talk to people nobody ever says that anybody wants to take a guess with the valid excuses you don't know how right that's the valid excuse like hey I don't know how to do this because you know what testing is a skill it's a skill like any other skill you know you've been writing code for you know some of you probably for 20 years or so and so you're really really good at writing code and so when you first time come around and say hey when we write a test you actually discover that you don't know how to do it and you when you start writing it it goes really really slow and so you compare your new highly efficient writing code without this with you know I'm learning a new trick here writing with this and you say well clearly this is not a good idea because I'm really but it turns out that it's a it's a prank with practice you get much much better at it so today we're going to specifically talk about you know this don't know part which what is makes the code hard to test so here's another question how do you make your code hard to test anybody I know some of you have been listening to me earlier so you probably already know the answers to these questions ah but surprisingly we are really really good at making code hard to test anybody disagree with that statement there we're not so good at it I think we're fabulously good at it the trick is we are so good at it we don't even recognize when we do it it's kind of like innate ability that we have to make code impossible to test so there are four biggest untestable that I call them and this is what we're going to talk about today they are the places where you place your new operators will totally determine whether your code is testable or it's hard to test and how many of you guys actually actively think about you know where I'm going to place the new operator inside of my codebase okay some of you well you are probably the guys who do testing that what about the rest of the team that you work with do you think most of them think about the new operators which is like oh I need a new object let me do one up the other one is working constructors this is another problem that causes a lot of trouble from the stability point of view and we'll kind of go into that in a second ah global State how many of people think that global State should be abolished and you should never ever have global state in your codebase okay how many people think Singleton's are a good idea how many people realize that the two are the same all right awesome some of you a lot of times I asked this question and people say yeah global State not a good idea this was already proven a long time ago and I said you use Singleton's oh yeah we love them I'm likely realizing this is a global State and they look at you all weird like no it isn't it's hidden behind a private constructor and lastly it's law of Demeter violations so let's go through all of these kind of in detail and by the way if you have any questions you know feel free to stop me of taking can you actually get in the microphone though so the rest of you the wasps can hear it so what the Demeter violation me our law of Demeter we'll have lots of slides on it so just hold it we'll get to it so let's talk about the new operator why the new operator will kill you ah testing right you got a test driver this is your j-unit your your your javascript test unit or whatever you happen to be using for your test driver then you have your class under test this is this thing that I'm trying to prove that it works properly and so you apply some stimulus to the class under test and then you assert that the class did the right stuff and that's kind of it right I mean what is there to testing why are we man having this meeting why are we talking about this what am I missing here well what I'm missing is this is a really small portion of the whole problem really the problem is the class under test has dependencies and now you really the class under test just got bigger so if you try to test something that's a leaf like math dot absolute value piece of cake right nobody's gonna have a hard time but if you're trying to test something more complicated like the main method of your class of your application not so simple anymore right okay so class under test has dependencies but it gets worse those dependencies have other dependencies and they have other dependencies and pretty soon you know you have your whole application entangled inside of your your test and you can't seem to figure out what are you doing so what we're really trying to do is we're trying to make the dependencies go away just kind of disappear right fade into the background and we're trying to replace the dependencies with Friendly's now before we can do that I want to point out that I've Drew have drone three different kinds of arrows there is the arrow with the dot in the front which I call the object instantiation this is where your new operator lives right there's the arrow without dot which is that the class under test received its collaborator just from some other location and then there is the dashed line which is that the friendly has just kind of reached into the global space and just pull they singled them out of nowhere and those three things will make it difficult or two of those three things will make it difficult to test really those are the three ways you can get a hold of a collaborator right you're going to make it you can either be passed into it or you can reach into a global State to get a hold of it now when I say friendlies a lot of people think that I immediately mean oh you have to have an interface and you have to have multiple implementations and so on that's not necessarily the case because a friendly for example can be the real object that I've already tested somewhere else and already behaves the way I expected ah and so when my test fails I know not to look for the problem over there because that objects already tested but the most important part about the friendlies is that from a test I need to be able to control the instantiation process let me give you an example suppose a friendly is a cache and I want to simulate a cache hit in a cache miss now in production I'll probably have a cache with you know several gigabytes worth of RAM and so simulating a cache miss or cache hit is going to be really really difficult with caches that size not to mention that running that kind of a test is going to be difficult because you're going to try to allocate all this memory so if the class under test just simply says new cache size five million you're going to not have a good fun time testing this thing right but if the if you control the instantiation of a cache and you say well let me make a cache will make the cache size one and then let me pre populate some values into it and I can control you know when the cache hit happens when the cache miss miss happens so the key over there is differently could be a mock but more likely it's actually going to be the real class that's configured in a test friendly way something that brings out a scenario that you wanted to to point out and we'll talk about the global are reaching into the global state and getting hold of a singleton in the in US in a later point of the slides okay so in order to replace this diff Friendly's you need to have something called a seam and seam if you think about it all it is is a virtual method call so in Java all methods are virtual in C++ world right you have to have the virtual method call over there but really what it is is that you can control the the path flow of your software purely by the way the application is wired together right so it's not that you have an if statement inside of your class under test that says if you're running into test mode go to the fake cash instead of the real production cash right it's that you wired the application together with a fake cash or a fake Authenticator or a fake file system or a failed database or in-memory database or what-have-you so seems really mean that you control the instantiation not that you have virtual methods because for example I could have a cash interface and then the class under test can instantiate a concrete implementation of that interface and from a testability point of view it doesn't help me like yes I have a virtual method over there but the fact that it instantiated a concrete class negated all of the benefits of having an interface so really controlling the order of instantiation of the way you wire things together is the whole trick to testability and especially the way you have to think about the new operators so here's the crazy idea I'm proposing here that the object graph construction and lookup and the responsibility of the business logic business logic being your if statements your loops your conditionals those two pieces are really separate they have nothing to do with each other and you need to break them up right you should have classes that either have business logic you mean your loops etc or classes that have new operators which are what we call factories which wire the pieces together then it gives me a freedom from testability point of view because I can either decide to test the business logic by wiring it up in a special way from a test or I can test the factory to verify that the factory wire the objects together without actually running the code right I can verify that a proper large cash got instantiated without actually running through all the cash operations so those two pieces are really really important and if you use dependent because this is really what it's asking for you need to use what you don't have to use but it's highly recommended you need you use some kind of a dependency injection framework that does this for you this wiring it really makes life easier the one we like to use at Google here it's called juice how many of you guys use juice alright most of you guys awesome the rest of you guys about C++ Python C++ I'm not sure what is there for C++ Russ do you know alright we don't know what the story is for C++ but I heard that there is some kind of a magic stuff available for C++ a lot of people a lot of times people will argue that in dynamic languages that allow duck typing and monkey patching that dependency injection is not needed because you can kind of replace the dependencies but it turns out that monkey patching is just modifying global state because code is really global state and if you're modifying global state you're back to square one in terms of to all the problems that you have with global state all right so really what you want to get to is you want to place all of the new operators inside of the test and then push the whole wiring to the other side so that the class under test is is sandwiched between on one side the test driver which is your test and on the other side between your friendly thugs and between the two of you guys you can really be beat up the guy in the middle and make sure that he does the right stuff right so let's talk about some examples um suppose you see this let's say you're doing a code review for somebody and suppose you see this what would you comment what would you say in terms of testability like yeah good no no good what can I do well if you wanted to test this thing right you say well let's make a new house but like darn I'm stuck with the kitchen in the bedroom and everything else that comes with the house right and if you wanted to say well I want to test the house but really it's a one-bedroom condo no way for you to influence the way the objects are wired together so the suggestion is use dependency injection and say well actually house depends on kitchen and bedroom and it's really not the business of the house to know how to construct these things somebody just kind of hands them to you right if you think about it have you ever seen the house that constructs itself but I only seen the contractor that constructs the house for me right similarly like with dependency injection right constructors they kind of miss name because they mean like oh it's the thing that builds the object but really they're not in the business of building objects because they're really only in the business of kind of saying these are my dependencies you know this is what I need to get my job done so now if you have this the test becomes really nice because you can say well I want a house but I really want a dummy kitchen and dummy bedroom because I want to simulate what happens when the kitchens on fire so I can insert a kitchen on fire class in there and then make sure that the fire alarm goes off and in my house here's another example look at that I am stuck with a 12-hour workday ah that's great for production right but I don't really don't want my test to run for 12 hours right so what you really want is be able to say well actually I just really want the hours and in the boots but really you should be even take it a step further and say the garden shouldn't even ask for the pieces the garden should just ask for the objects directly so here's the case where the constructor is kind of doing work and it's kind of a mixed thing like the garden says well I need the the gardener but then it goes and configures the gardener in a specific way and again the problem becomes that from a testing point of view you have tested run really really slowly and so again solution is break this thing up say garden is only asking for the gardener there's a provider somewhere in Joost that knows how to configure the pieces together wire them up and then provides that to the garden that make sense alright and from a testing point of view right you can then say well I want to have a one minute workday to kind of simulate in a much simpler way what the the work is alright so let's talk about the cost of construction we kind of talked about the constructors right with new operators where you place them but now let's talk about the cost of construction so to test the method you first need to instantiate an object number I said that we want to be able to wire the object in a specific way and then apply some stimulus to it and then assert the output you can't assert the stimulus before the objects are wired together right and you can't assert the output before the stimulus is I mean they have a specific order and you can't change the order in which they happen like you don't have a choice from a testing you got it wire them assert the stimulus and then assert an assert that the right right stuff is happening so if your constructors get executed during the wiring phase of your test right when your wiring the pieces together your constructors are running so if you put any kind of code inside of the constructor such as code that goes into the file system and tries to read a configuration file or anything along those lines that code executes every single time you try to wire the objects together now maybe you can survive a single test where you can mock out the file system or something like that but if you look at the number of times you construct the object versus number of times you're actually asserting some behavior on it you will find that you are reusing the class not just inside of you know if it's a class X not just in the X test class but also you're reusing it as dependencies for other classes and so on so the class can easily be used in a lot more places than the number of tests that are available for that class and so for that reason if you have something in a constructor that is hard to execute in isolation a lot of people will be suffering that are depending on your class so when I look at other people's code the first thing I go for is I look at the constructor and I say does the constructor do anything if the answer is yes I shiver because I know it's going to be really hard to get this thing instantiated and wired up in the correct way so that I can apply some stimulus to the classes there's other problems with constructor and it is you cannot override constructors right so you cannot say well you know call the constructor but don't do this action over here like I can I can override a method and say well do everything else but don't call this method over here because that one is expensive or something but I can't do that with a constructor it's all-or-nothing proposition and if your class has a very deep inheritance hierarchy well then not only do you have to deal with your constructor but everybody else's constructor and so because the tests have to successfully navigate the Constructors over and over and over again anything you put over there is going to be greatly multiplied even the smallest amount of pain is going to become a huge monster for you to fight with so the solution that is you want to really do as little work in a constructor as possible in my personal opinion you should do nothing else but assign your dependencies to your fields nothing else just that much so let's look at an example of a constructor that does work here is a car and the car says well I need an engine right I mean as a field engine but instead of asking for the engine it says well give me the configuration file and I will go into the configuration file read it in there and from there I'm going to extract the model number and then I'm going to use to build the factory to build the engine that I'm interested in anybody see a problem with this well you might be laughing but I code like this actually exists right I mean it wouldn't be so funny if it wouldn't be so true but it also is kind of strange like have you maybe you've seen the movie transformers but like have you ever seen the car that can assemble a factory which then assembles the engine for the car like that's pretty crazy right cars don't usually assemble factories but somehow when we don't deal with real objects like cars and factories but we deal with like abstract classes it seems ok for an abstract class to be constructing another you know class inside of our code base so from testing point of view yeah maybe you can go and write a specific engine configuration file into your file system which is going to mean that the test is slow but really you really don't control the whole process right so what again what you want to do is you want to simply say give me the thing that I need which is the engine so the other trick I do when I look at classes in it is I say okay look in the constructor and make sure there's no work in there look at the constructor to make sure that all it's doing is assigning its dependencies to the fields and then I'm going to do one more thing and that is I'm going to check to make sure that the the dependencies that you actually save into your fields are actually the dependencies that you truly need and that's where we're going to get into law of Demeter violation in a second so there's one more piece to looking at the class right so if you have it rewritten in this particular way testing becomes really easy because you can just say make a new car and the cars is winding an engine and then you have a freedom to instantiate any kind of engine you want in in your tests now one thing I'd like to point out is that if you follow these kind of guidelines the thing you're going to discover is that new operators are really prevalent inside of your tests like tests are filled with them the other thing that the tests are filled with are null because null basically says you know suppose you're trying to instantiate a house well house says well I need a kitchen in the bedroom the kitchen says well I need a dishwasher I need the the refrigerator the stove the electricity the plumbing and so on and so forth and the refrigerator says well I need the plug in the water supply and the water filter and so on so if you were forced to instantiate every single object all the way down you might be instantiating a huge number of objects that you don't actually need what if you're trying to test the method on a house that simply says secure which means lock all the doors lock all the windows the kitchen really doesn't play come into play so it'd be really nice if I could just say make a new house and oh by the way when you're asking for a kitchen just here is an all right kits it's none of your business don't worry about it when the test breaks this is not the problem you're looking for right don't go to the kitchen because that has nothing to do with this so no this is a nice way to tell the reader that this is really not important for you right this is not what I'm testing so your your tests really are filled with new operators and you're they're filled with nulls whereas if you go look at a production code you should be really devoid of new operators and preferably you should have no nose because you shouldn't return the null value and then check for a null value because if you do reference it it'll blow up you should instead return null value objects right don't return a null for the number of windows return a empty array right don't return null for a don't have a lager instead return a no lager that if you like something it just goes into a void so production code you know no news up no new operators no nulls test code lots of news lots of nulls here's another example this kind of touches on a global state suppose I want to end also doing work on a constructor suppose I wanted to instantiate this and of course this automatically tries to instantiate a socket you know from a testing point of view this is going to be a real headache and a solution is always the same I feel like I'm sometimes selling snake oil what's your problem the solution is dependency injection right so we have a problem and again the solution becomes simply ask for the the socket and then I'll be able to provide you with a fake socket or in memory socket or socket that's already configured in a particular way that makes it easy for me to test it all right let's talk about global state and the best quote I have for global state that it's insanity and good definition of insanity is the repeating the same thing and expecting a different result and global state allows you to became insane specifically for this reason because you keep doing the same thing and it always behaves differently let me demonstrate suppose I have a code like this I have a class X and I have a method on class X that says do something and that method returns an int and I'm going to say new EX do something and UX do something again I'm going to assign them to a and B what do you think should a and B be equal or not how many people vote for a and B being equal okay how many people vote for a and B can be different okay can't be different and let me ask a different question is it a good idea for NB to be different it's not a good idea at all yes they can be different what it really means is that your code depends on a global state if they are different here what's really happening is this that you're insane shading in UX that might instantiate other objects it's perfectly okay and then you instantiate X again and that might intentionally it's the same set of objects in the same exact way because it's only one kind of code base and the only way that a and B are not different is if those two objects share some global States if that's the case then the two are not equal and this is very problematic from a testing point of view because what it means is that there is some stuff that you don't know about outside of the definition of your test and that stuff can be persisted and then when a subsequent test runs and it's doing something that global State can influence it and therefore you can get a different behavior why do you care you care because suppose you have a thousand tests and you are in a situation where when you run the test individually they all pass but when you run them together they don't pass it kind of makes it really hard to debug now to make the situation worse suppose your tests are slower I know we should write fastest and all that stuff but suppose your test suite takes two hours and the test that fails is on the end of the two hours and if you run the test in isolation it passes good luck debugging it because every time you hit your debugger and you put a breakpoint you got to wait two hours till it gets the right state so that you can simulate and you'll figure out what's going on so global state from a testing point of view is a real real problem if you think about it what this are are a small instantiation of your application over and over and over again right and when you instantiate a small chunk of application you want to make sure that the only thing that affects the execution of it is what the test told it to do right there should be no external factors that are affecting it so these external factors will basically cause flakiness will cause that the order of the test execution will matter and then it will also mean that you cannot run the test in parallel all of those things are very undesirable right kind of all agree on that there's a couple of hidden State inside of your JVM and that is system dot current time right that's a hidden state new date which by the way uses system that current time and of course math dot random and I'm going to argue that if you try to test code with any of these functions you're gonna have a real hard time testing it and that the code is probably going to be flaky even if you kind of make up something where you say oh well I'm we're going to execute this piece of code and it's going to call new day then I'm going to instantiate a new day and I'm going to assume that the date that the test executed will be later than the other one it's going to kind of work sometimes and then once in a while it's gonna be flaking it's not gonna work but there is a third problem with global State and this is my favorite problem this is the real problem that I really think is the biggest reason not to use any and that is that the API lies about what it needs so let me show you an example once upon a time there was a project I was on and I wanted to test to see how credit cards work so I said new credit card and I had to type in a real number so I typed in my credit card and I said charge 100 bucks and finally a month later I got my statement and guess what I was out a hundred bucks and I said well wait a minute how can that be how does the test know how to connect to a gateway how to talk to some real service over there I put a certificate you know exchange money it's not just like out of thin void the test can do these things right you have to tell the test on how to do all these things well turns out that these tests never actually passed in isolation it always would only pass in production code when you run it together the test when you try to run it by itself the test wouldn't work so let's have a look as to what the problem is here honor by itself and it throws a nullpointerexception and you have no idea why that is because I didn't pass any Knolls so why is it throwing null pointer exception in here so you go talk to the older and wiser people on a team and you say hey you wrote the credit card could you tell me why it's not working and they usually say something along the lines only if we had documentation this wouldn't be a problem right but it's really missing the point because the the API should be self documenting but really you chat with this person and the person says well the reason why this is failing is is as you know you need to actually initiate in initialize the credit card processor now I did not know this like this isn't written in the API anywhere it's just kind of something you have to know on a project because the credit card is secretly talking to the credit card processor so you said okay no problem I'm gonna initialize it you want to test again and then the same thing happens somewhere else so you go back to the first person you say okay it's still not kind of working and the guy says well yeah I don't know I wrote the credit card processor but I I mean I wrote the credit card but I didn't write the credit card processor so I have no idea you go talk to the other guy so you go to that person and you say okay I'm having an exception help me out over here so they kind of look at and go like yeah well the reason why it's not working because you forgot to do offline cue start and I said well how am I supposed to know that right it doesn't say so anywhere and you know the same story repeats like only if we had documentation the world would be a better place but it isn't and you don't need the documentation I'll show you why in a second ah so you run this and of course it doesn't work still and this story repeats over and over and over again for a long time until you finally figured out that you know you forgot to initialize some random objects all over the place and you don't know why these objects need to be there you just just have to be so what's happening is that the API is lying to you right it says all I need is a credit card number and I'm going to magically work when in reality the API actually needs that has these secret dependencies on all these other places so now I notice over here at some point you know you make the test work and maybe this isn't a test maybe this is production code and at some point you look at the red area and you look at the green area and you say okay I don't see how the two pieces are related I'm going to refactor them I'm going to move the red area over here I'm going to move the green area over there because I think there should be a part and then I'm going to change the order and I'm going to call the first half earlier before the second half because I think it should be able to rearrange this and guess what these two things are not interchangeable but it's not clear by looking at this code that these two pieces are not interchangeable so I'm going to ask you how is your initialization in your application that you're building is it some of the friendliest code you've ever seen or some of the scariest code you've ever seen I'm gonna say some of the scariest code you've ever seen there anybody like to disagree with me and not use it and they're not using Jews because that's not fair right the code is scary and the reason why it's scary is because usually the initialization code kind of grew organically there was no rhyme or reason why I have to initialize this piece before that one or just kind of organically grew to some particular way and then you want to add a new piece to it and you want to globally initialize it and you just randomly try like let me see if I can visualize it here no it's not gonna work it has dependencies all these other things so I gotta pray and play around with these things and experiment and then maybe finally it kind of works and you say don't touch it and you check it in and then the next guy comes along and he has to do the same thing again so what can we do I can't I already talked about the order of initialization let me what can we do is all right sorry what we can do is we can make the api's be truthful and we can make the API say look if you're gonna make a credit card I need a credit card processor I'm just coming out and saying it I'm not secretly gonna talk to some global state and get a hold of a credit card processor I'm gonna just come out and say I need a cracker processor now I have a choice first of all notice I don't need documentation I don't need to go somewhere and I don't ask anybody and say hey why is this not working I know why it's not working because I need to provide a credit card processor and now I have to have a choice I can either make a real credit card process sir or I can make a mock and put it in there or maybe I'm gonna try with the null see if that works sometimes that works too when I try to instantiate a credit card processor it says well I need a queue and again I have the same choice I can say well I can make a real queue I can make a fake queue I can maybe pass it and all maybe that's going to work I can I have choices over here I don't need to read documentation and similarly the queue says well I need a database and so on and you can kind of build up your whole objects that way notice that from a testing point of view now you have a lot of choices I can decide to mock the thing with a in-memory database maybe I want to mock it into a fake credit card processor maybe it's a quicker processor that doesn't really process anything and so I can take a null fake one right or maybe it's one that is a we use for testing which is a simulator of the real one you have choices here choices which you did not have when you had a global state in singleton and so this greatly adds into simplicity of the design of your application it also makes it a lot easier to maintain it also makes it that you made it your initialization code becomes a lot friendlier because you cannot initialize this in the wrong order right because if I try to change the order of initialization or instantiation here the compiler is going to complain and say look you can't make a credit card processor before you made an offline queue it just doesn't work the other way you get a compile time error so you get a lot of benefits over here by simply declaring dependencies now what usually happens is people hear about this they go back to their desk and they they take a class and say okay I'm going to declare my dependencies and next thing they know is they have a method they have a constructor that takes 20 arguments and they say that can't be good I think there's a whole dependence injection stuff is bogus doesn't really work well the reason why you have 20 dependencies is because your class actually has 20 dependencies and in it what happened in the past is that you were pretending that you had a clean API you were in denial you're saying it's actually quite clean look its constructor doesn't take anything in soul no problem but the dependencies were still there because you couldn't just call the constructor and executes anything you had to initialize all these magical variables all over the place now all I'm saying is look I just made it truthful I just make it not lie I just made it tell you the truth that this is what it is so deal with it now at that point people should really just say yeah a class should not have 20 dependencies and maybe they should go in and say maybe this class should be broken down into other pieces or maybe the the way the code is designed should be done something else with it right at that point you have an incentive to go back and say something's not quite right here I need to rearrange something right but when the api's are lying to you and everybody's pretending that the world is a wonderful place not so good so let's do some more examples of fun stuff suppose you want to test the account view and the account to you in your constructor goes and reaches in the global state right RPC client get instance that's what it is I mean you can call it a singleton you can call it a service locator you can call it whatever you want in your application it's still a global state and then funny thing about global State is that it's transitive right so not only you know some people say well I only have a single singleton it's called the service locator and the service locator knows about the whole world we'll talk about in a second it's still the same problem again testability not so good right what we're really after here is we want the user so from a compiler point of view when you try to compile the class all you really want is user but you have a dependency an RPC client and the RPC client probably has dependencies on a whole bunch of other stuff that you don't care for so not only are you asking for the wrong thing you're lying about your dependencies but the compile time dependencies are a lot bigger than they should be because the compiler dependencies are transitive right so we can test this again we'll do our magic dependency injection and voila now the account view depends on a user and guess what user is something I call the value object right something that probably doesn't have dependencies on much else and so user is probably something that's easy in state to instantiate whereas our PC client probably not so easy to instantiate right so if I'm gonna mark out an RPC client I'm gonna my hard time but if I wanted to mark out a user well why should I mark out a user I could just use the real thing right probably just as getters and setters on it I forgot what this example is about we'll just skip that because we're running out of time anyways okay let's talk about service locator and the law of Demeter violation somebody was asking what a law of Demeter violation is I looked it up on Wikipedia once and I forgot what exactly it meant but it apparently has to do with some God that matter or something no yes okay it's on Wikipedia look it up ah but really what it means is that you do too many dots let me let me show you it really means that you have something like a service locator dot get factory dot get our PC client dot get instance dot get me the user where all you really wanted was the user right you're kind of causing you going through a lot of dots to get to your to your user and we call it the dependent of the law of Demeter violation it usually comes into play with service locators now I'm going to say that service locators are much better than singleton because at least you have one place where all the messes are supposed to all the mess being all over the place but you still have a mess so let me show you how how this stuff hides dependencies suppose you have a house and a house says giving the service locator or what are the other names for this thing the manager the the what else people call the service locator registry yes there's lots of names for this same exact thing so now you wanted to test a house and you wanted to know what you have to mock out inside of the locator well guess what you don't really know you kind of have to look at the construct to see what's inside of it and so what usually people do is they create a mock locator and then run the test and they see no poner exception and then they see I have to mock out this method over here and then they mock that out and run it again and then they have to mock this method over here and then so on and over and over again and then you have end up with what I call a mockery where you have a test which mocks out 50 methods and by the time you mark out the 50th method you have no idea what you're testing and half the time you even forget to put the assert on the end so you just are just verifying that you can mock it I guess you're verifying the JVM which I guess is useful thing to do right so notice the difference if the locator needs to have the door window and roof I cannot tell without actually looking at the source code what those dependencies are but there is something worse over here and that is locator is typically a class that has methods such as you know get me a service a get me service be get me service C and so on so the compile time dependency becomes in order to compile the house I have to compile the locator in order to compile the locator I have to compile the whole world right and once we are on the pin compiling the whole world it makes it very hard for me to reuse the code because I can't give the house to my buddy because he will need to locate it and what the locator he'll have to get the whole world right so the compile time dependencies are usually a good giveaway that something is backwards now we can simply rearrange this and say hey just give me the door window and roof and all of a sudden the compile time dependencies are greatly diminished and the whole thing is much simpler so your test becomes hey I can make a door I can make a roof I can make a window and pass it in there now I have choices right here I can say well make a real door or make a fake door make a door that's configured in a particular way just like we're configuring the cache and it's all about having choices it's all about setting up your software in such a way that inside of your tests you can configure it in lots and lots of different ways so that you can bring out the the corner cases that you're interested in so the service locator is really mixing the responsibilities right because there's a responsibility of lookup and also the responsibility a factory because the service locator oftentimes not only this gives you handle to other objects it also is the source of other objects or it also calls the new operators in there as well so really the service locator tends to be this garbage dump of all kinds of random things and usually if you are lucky to have an interface for it if you want to instantiate the real one usually the dependencies for the service locator are pretty scary and usually inside of your tests it's usually next impossible to instantiate one of these beasts so it's not really helping anything really you know it's kind of helping but not really oh we talked about making a mockery here let me kind of show you the issue here this class seems to behave properly and says well look I'm a login page I'm gonna do no work in a constructor I'm gonna be all good about it like mishko said so here I'm going to ask for RPC client and I'm going to say I need httpservletrequest and I'm going to properly assign them into a fields so you look at the constructor you look at the fields and you say good so far the class seems testable but then you look at the login page and you realize wait a minute the the login page doesn't actually care for the request nor doesn't care for the client what the login page wants is a cookie and really what it wants is the Authenticator so that it can call the authenticate method on the Authenticator with the cookie so notice how we're breaking the law of Demeter right it's really client dot get Authenticator dot authenticate which is what we're interested in so we have two things in front of us to keep the client and the Authenticator and we have request that cookie which means again we are jumping across a dot that we don't want so if you count them up I think there's three or four dots that are really in your way the reason why this is a problem is because from a testing point of view the test becomes really scary you have to make a fake Authenticator then you have to create a probably a marker for it then you have to train the Authenticator to receive the get the create method or the what was a get I think to get Authenticator method on it you have to train all this behavior yeah basically have to train all these marks so that they can walk the dots only so on the end of the whole thing you can call the authenticate method with a specific cookie right so what you really are in interested in here is a cookie and there is the Authenticator and if I call the authenticate method something that should happen but what I really have to do is create all these other irrelevant objects set them up in a specific way train them through the mocks so that I can walk the dots and then I can write my test so if you look at this test looks pretty scary now this is a test it only fits on one one slide in reality I have seen tests like these which spend pages and pages and pages and you have no idea what's going on after a couple of pages well after a first page I think you have no idea what's going on so how about we change this and we say what what this class really wants is a cookie and what the class really wants is an Authenticator now some people at this point usually say yes but you see the HTTP server request you pass them that returns a different cookie depending on when you call it in AHA we come to a second problem which is that chances are that the class has the wrong lifetime chances are that the login page shouldn't have a singleton lifetime or a you know single instance in your application but rather a new page should be created with a new cookie over and over again so a lot of times the the root cause of the service locator often happens to be that the objects have much longer lifetime than they should have okay so this becomes much easier and look at this the test now becomes much simpler as well because you can just say make me a cookie make me a fake Authenticator and call the login on it and then authenticate and verify the pieces are working right piece of cake the test becomes a lot simpler and all it really did is removed couple of dots right the dots are what we call the law of Demeter violation okay Wow I have raced through this I thought I was gonna have a lot more slides than this questions I think there's a Dory page so I am gonna see if I can get it up no that's not the real link to it okay said mommy's here I'll do a question listen we want to talk to you about it son oh no questions yes live questions somebody had an on the BC could you grab the microphone yeah I what do you saying about nothing work in the constructor it makes sense but also it I see a problem where like after the constructor the instance is not ready to use in some places so you have to to do something to it and that would be the same thing something hidden in the API that you like have to instantiate it just call like initialize or whatever do you have a what do you say i diem for for doing that mhm so the people actually ask this question quite a lot and the example I usually give is that hey you know you instantiate a cache but then you have to initialize the cache so usually there's like an init cache method or something like that but really what it means is that you should really be having a cache factory class and in the cache and when the cache gets constructed it is constructed in it's ready state and that method for initialization really should be inside of your cache factory so the so for example if the cash gets pre initialize by reading stuff out of the database then the cache factory should really be the one opening up the database sucking all the data in getting it all ready and then once the data is all ready then simply call the constructor and pass in the hash map with the set already so the cache is ready to go so whenever you get in the situation like that all you really are saying is that the initialization should be moved up one level and usually end up in a factory yes okay so you're talking about these constructors we end up with the 20 arguments in the constructor then you break it up you have any advice on I mean what do we do when we get to that situation we need to break it up I mean those 20 arguments need to go somewhere right so it's a it's a sign that there's something wrong with the design and usually what it breaks down to is that the class has too much responsibility and usually if you look at those constructors you kind of look at them and you go like oh yeah these four they deal with sending emails and these three over here they deal with authentication and these four over there they deal with you know the persistence layer right and so what you really are saying oh I could build another object that abstracts those three over here to deal with authentication into a higher level concept like the Authenticator and I can take these four over here and I can wrap them in something like a mail server right and so what you're doing over there is you are simplifying the world by taking related pieces together and building a higher level object that probably is begging to exist because usually that logic has to live somewhere but chances are you just inline the logic into a higher level class so what I'm saying is you create a new class that has the related stuff and then you take the logic which should really be in there to begin with and you just move it down there so then you have only the number of dependencies are decreased through that trick so it's just going to make a long initialization chain then where the initialization change is actually the same you're just moving where things are because you always have the same number of end nodes that need to get built right that's not changing you're just saying that there is now a better grouping going on so that there's a there's more objects that they related things together and produce them or present them as a as a service to other objects but these new objects the Authenticator and the email or and so forth might only exist for the purpose of constructing this that's okay that's I'm also the auto okay chances aren't they actually want I mean in your example you're saying that they could and I'm saying it's okay but you're actually going to discover that if things are go together they go together throughout the application and you'll actually discover that it's the same thing that happens in other places and you're going to say oh look I have this object and then you realize wait the same thing is happening here here and there and so all I now have to do is pass the single object over there and I don't have to pass these three individual ones all right thanks sure yes so I see a common pattern here so whenever you require some instance to be created inside class you try to create it outside and passing as a new parameter in your constructor function but in some scenarios in our real application we found the case where for instance this kind of the change for make code testable however will change you to the format of your original constructor function but in our real cases that we found it it doesn't work with like a register pattern where you have many subclass which all require the same phone of a constructor function so basically they all take the some config let's say some config string as the input then they do something inside so so how how this thing will work out so yeah I think I volumes so you said the word config I would mean by configure I think let's say you have been across and they all need to be registered with some kind of register Factory okay so first of all we already covered the fact that you shouldn't have register or factory right because I had oh I go on ok so then yes basic that's my question so then each cost now taking like the different config its class can take a different the config string as their input so when you're saying that you're passing around config string which you really are saying is that you're pushing the responsibility of extracting information out of the configuration into the classes right yes and I was just kind of trying to argue that you shouldn't do that because that makes it really hard to test now sometimes you get into situations where the the object is lifetime are different so you have a single class who has a long lifetime but it needs to create lots of small ones so you cannot inject the short-lived object into the the long-lived objects then you have to inject the provider right or Factory so if you're in juice world you say the provider of that object if you're in a factory then you just create that some object factory class that you inject in there and that gives you the the ability to kind of wire them up any way you want it's not kind of answering a question I know because your question is a little different but I I think we would have to sit down and draw it out and I will show you how these things with melchiorri maybe I can tell you sure you can do that all right guys you've been a great audience thank you so much um do we need anything else or we good all right thank you very much for coming if you have questions I'll send them to me it's mis ko at Google calm and see you later
Info
Channel: Google TechTalks
Views: 95,749
Rating: 4.9691968 out of 5
Keywords: google, tech, talk, software, testing
Id: acjvKJiOvXw
Channel Id: undefined
Length: 56min 2sec (3362 seconds)
Published: Wed Oct 07 2009
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.