A Better Approach for Testing Micro Services – Introducing: Test Kits in Practice by Maxim Novak

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hi welcome welcome to this talk welcome to their books it's my first time at devoxx and I'm enjoying it a lot hope you too let's start I believe that every developer that cares about his product should have tests to the code and when developing micro services we have more challenges that we didn't have before and this talk I'll show you different approaches that I use when working with the testing micro-services environment so I work at weeks mix is a web-based platform that let small medium businesses to build a stunning website and manage their manage their business in one place for free without any programming skills and why am i T why I'm telling you about this we have 113 million users and we have over 800 micro services in production so we are doing this a lot testing micro services my name is Maxime Novick I worked weeks for four years and a lot of my time there and building infrastructure services which are services to take care of cross-cutting concerns like authentication and authorization email SMS URL for turn ninja etc and I want to show you a quote the integration with your service was the easiest thing I've ever done say it nobody never and important part of my job is to make it easy for other teams to integrate with my services so today I'll share with you the approaches that we take to make it easier so the agenda for today is to first understand what's the differences when testing Microsoft says why is it harder and then we'll see different approaches to cope with this so testing micro services is it more challenging what are the differences so let's start with a bit of history in the old world we had our monolith and we had the test we ran we can execute the whole code in one go and we test our application or maybe we had a database so we're on an embedded version of this database or run this database inside the container but it's still fairly easy but then microservices came so we started splitting this monolith so authentication is a different concerns Emil's different maybe billing and much more if that's not enough each and every one of them have its own dependencies so it looks like this and what I want to tell you today that it starts something nice and simple like this but when your company grow you have many users the product grow it becomes it have apps like this okay very quickly so let's see how we can handle this mess so so the testing microservices is much harder because of the separation so the code is spreaded in different places different teams write write the code and we can't run the whole system on our laptop but we still want a short and reliable feedback loop and we want to to make sure that our tests the test our assumptions versus the reality in terms of the logic of the service that we talk into and the communication protocol so before we dive in mmm let's recap on some testing is some tests test types that we'll talk today and to understand to make it easier for you to follow I'm held this imaginary example imaginary scenario where we are developing the store application we have a user that come to our store adds an item to the cart and make a purchase and then these are should see a confirmation page but the problem with it that the store application doesn't know the name of the user and we need to have the name in the confirmation page so we have our user server that we depend on it and it knows the name of the user so one type of test that you want to do is end-to-end test it also called out of process component test this type of test we will run our store application the same way as we run it in production and then the tests will interact with it the problem that we don't have the user server because it's developed somewhere else and this in the later I'll show you how we solve this so the orange line is the test boundaries okay we run our application and then communicate with it and the same protocols is the user would the second type of test that we discussed today is integration test so obviously inside our store application we have some piece of code let's call it users client that does all the communication with the user server and we want to test that it works well here in the integration test we also have the opportunity to test some edge cases or bad scenarios etc a unit tests are out of the scope here because unit test should test the pure logic of your service inside your service so let's see how we can approach this so the first approach is don't test but you probably don't want to go there I mean if you care about your application tests are very important to get the quick feed with luke quick feedback loop because you don't want to deploy your application to production to see that it's not working so i want to talk a bit more about this there is some developers find a way around this so they think that their tests but not really what they do is they have the users client and also something else called stub users client and then in test they have something like this so if it's in test mode use the stub users client if it's production mode use real one and it can be this if statement it can be some fancy dependency injection but still the important thing is that the application is aware whether it runs in tests or in production and we don't want this and there isn't that we don't want this is the the real implementation is never tested so we can't we can't know that it will work in production mmm so you do want and out of process component tests which are first as end-to-end in this case by the way the method here it's a valid way to do other tests like acceptin tests but it can't replace then twenties that give us the confidence see that our application works and if you don't have this tests I'm sure that you will have this embarrassing moment when you have some really tiny small change in your application you deploy it and the breaks and then you have this commit and deploy it again and guess what it's not working so now this one and after a few commits like this you give up and as someone else to help you so we don't want to go there okay so this doesn't solve the problem for us so let's see how we can test it so the first try will be to run against the external environment so you can turn your application against the production or staging or some other environment let's see how it works so we have our test we have our application and the user server is somewhere so the good thing about it that we do check that it behaves like we assume and we ensure that the communication protocol the schema is correct still you can't test some non happy process for example the server is down or server returns some errors and the bigger problem with this approach is that running against external environment is unreliable it has higher maintenance cost and adds complexity to your application and explain what I mean with it so first of all our tests now need to do an external network calls which can make them flaky but other than that it adds a lot of complexity sometimes the services that we use are doing some side effects on the world for example payment SMS etc and you don't want this to happen so now what you will pass a parameter to the production is this equal truth you don't want to do that also security comes into considerations sometimes your server talks to another surveillance with a some secret key so now this key need to be in your test environment which is of course less secure than the production another security issue is throttling sometimes the service that you're talking to will throttle you and will require capture for example your test won't be able to fill this CAPTCHA also this environment is shared between tests it's shared between teams and from experience it doesn't work for in the long term if something is the setup or two down doesn't work your environment gets corrupted and you need to log in and start fixing things so you probably don't want to do this I mean sometimes in the beginning it looks very tempting and some people do it but when you're working like this for a while you understand that you want a better solution so please try to avoid this the next thing is something called test doubles this one we use quite a lot and here our our service will talk to a stub or mock or fake over the same protocols it talks to the real service so if you are not sure what the differences there's really good blog post by Martin Fowler that explain the differences basically stubs provide canned answers that you pre program mocks you program with expectations and they replies to this expectation and fakes have some working implementation that usually have some shortcuts to it so let's see how it looks like so we have our application and it takes talks to the fake server over HTTP by the way it can be any protocol HTTP is just for the example and I guess it that's what most of the people you do so here we are like having a fake that represents the fake server so how this fake server looks like so let's say this is the real server in our case the user server it has an API logic validation now whatever so in the fax server we must have the API layer the same API layer but instead of the implementation will have stubs or mocks or fakes ok let's look at some code we'll have a Java example here so here's the purchase flow end to end test this is from the perspective of the consumer of the API the store of course so we generate a random user ID random name and we set up the fake we tell if ok you should know that when they ask you this is Rd you should return this name then you're doing your your flow you login you a dive into a card and you make a purchase as a result of the purchase you get a confirmation page and now you can assert that the confirmation page contains the name ok so this is the end-to-end tests we run just to make it clear we're on the store application exactly the same way we would run it in production and also we run a fake user server and we just test it so that's the whole code let's move to let's see how the integration test will look like so in the integration test you want to test only the piece of code that integrates with the with a user server so again we set up in a very similar way we set up our fake and then we are calling user server client don't get name with the user ID getting a result and we are certain this result so we know that our user client improvement implemented correctly now we also have an opportunity to test some edge cases for example user is found so in this case we will set up the fake in a way that it will return user natsound then we ask for the name and we expect to get any user in ass found exception thrown from our client now it's important note here that we because we implemented this tab we can't be sure that it behaves exactly like the production service behaves so for example we assume that it will return us HTTP 404 but maybe it returns HTP 200 with a payload that says that the user is not found so that's the the disadvantage of this approach so let's see let's see what we got here we do have a shortened reliable feedback loop but we implemented the test doubles in the way that we assume that it works and not the way it works in reality so so we can't be sure that it will work we test in the end to end our flow it looks that the end-to-end test our flow that's ok but we can be sure that the integration with external provider is good another problem with this approach is when the provider have some internal logic that we rely on let's see an code example to show this so this is a production code of the store application ok this is the method that does the purchase of course it's very simplified but just to make this point here so this method gets the user ID and then list of items to calculate the price have a sets a variable for is built successfully then build the user if it's successful set it to true if it's a stolen credit card set it to false and then we render a confirmation page we pass it this build successfully a parameter so if we build the user we said hey you got your items and if it's false we say the user your we couldn't believe you your order failed now we have this code and your boss comes to you and say you know what we have we are seeing a lot of usage of stolen credit cards let's just block this users like if he is a car with a stolen credit card I want to block it so I say of course it's really easy I'll just add this line here okay I know how to do it I read the documentation so we block the user now you're on your test all the tests passed of course of course all the tests are green and you ship it but what you don't know is how render confirmation page works and the way it works it tries to get the name of the client in the beginning but in case of black of blocked user it will not work so it will explode in production and that's just because you you didn't know how the user server works there is another disadvantage with this approach if there is a service provider that have a lot of consumers so most of the services in this company will probably use the user server each team of the consumers will have to implement these test doubles so it's a lot of duplicate work so let's sum it up this method then to end for the Internet itself those our problem but for the integration it's partial because we implement the test doubles and he'll work the way we assume and not the way it works in the real world and you can trust them so we worked with this method for a while and out of the lot of developers from other teams used to come to me and say look this works in my test but doesn't work in production help me with this why are my tests passing the production breaks consumer had troubles to integrate because they didn't understand stuff and again they implemented some fake doubles did it worked in their tests you know there is like this developer famous saying it works on my machine right but we want it to work it in production so we thought that we need to find something better we wanted a higher level of confidence so let's see how how we do it because we know that integration between teams is very costly process and we want to improve it so the idea here is to move their responsibility to the service provider so far we saw method that the the the service consumer can do himself and this method it's important to say that we need a cooperation between the teams of the provider and the consumer so let's see how it works the service providers the service provider provides something called a test kit which is a lightweight version of the real thing and then the user consumer can use it it looks like this so how the the end-to-end test will look like this test kit gives us opportunity and opportunity to communicate better how your service works so you can provide builders to set up the expectations so in this example we we have a user test builder and you can set to the user with ID and with the name and with any other field that you want it might have like 50 fields here but if you don't set any field it will just be a default and if we add the field it will be added automatically in this approach but in the test doubles it will just not be there so we set up the test kit and then at the same way we just ran we execute our code and we assert that the name is in the confirmation page okay let's now look at the ith sample and the integration test very similarly the setup is is very similar then we test we execute the code and we assert that the name is what we expect the non happy path now we don't even need to set it up to say a given user not found because test kid know how to behave when a request with the user that not set up it knows that if there is no such rules are set up you need to return either and found so it's even easier and then we test that we expect the user and found exception let's see the special case that we saw before about the blocked user so now in the IT we can also set that the user is not active and then we invoke the call the we set it up we invoke the code and we see user and found we also know that we have a back-office API that will show us blocked users so in this case we can test it as well and now we can make sure that the regular API will return user not found for block users and the back-office API will return the user so no guessing anymore you can just test it and be sure that it works the way you think it works so what does the consumer guess so you get a class if it's Java we usually have a start and stop method if it's HTTP or some web it has a port usually and we have some methods that will help us to set it up so we can say given user and we can update user and etc now for the applet user we don't provide it to user because some fields can be updated some fields can't so again it's your way to communicate with your consumers about your contract so you convinced now you want to build a desk it let's see what needs for this so as a teske developer you need to understand what is desc it so in this bar we have test doubles on one side and the real thing in the other side and tasks it it's something in between and for every use case it can be something else it really depends on you on your needs and you decide what goes in and what not so let's start looking at how it look like so if this is the real service that you provide of course we will have the API layer and now we start picking up which things we do want from the real code so for example we decided we want to do validation the same the validations so for example the user is UUID and if you're getting a string we want to throw an exception so we put it here the same implementation from the real code sometimes you want to take some logic so we have two parts of logic here we'll take logic one and usually when you have logic involved view sometimes also on the database so if you have a database you just replace it with an in-memory implementation of the database also often you want to play with the time for example you have a coupon service and you know that the coupon expires after one week so you don't want you just to run for a week right you want to be able to say now go a week forward so you change and you put some time mechanism that you can control from the outside and an important thing here so we decided that we were taking most of the stuff you're not taking logic - now what about the database we don't want that each test as I said before it's a lightweight version of your thing so you don't want to bring other dependencies of course you don't want to run another database for your consumers because if your consumers use 20 microservices each one of them will have a database so it's a lot of databases a lot of network instruction a lot of resources so we will not bring our next dependencies not services not databases so how do we solve this with the integration contract test also if you want to read more about this there is excellent blog about this by martin fowler again but explain briefly so it's the same test the trans against two implementations okay so me as the service provider I replaced my my sequel Dow with an in-memory Dow and the same test that I ran against my my sickle down I will run again the in-memory Dow this way I'm sure that it behaves the same and we are applying the same technique for the whole service so these services are more to make it easier for me to develop but we also apply this in the several scope so we are writing integration contract tests the trend the same test run against the real user server and the user test kit okay I'm at the service provider I'm responsible to do it and because I'm doing so my consumer the store here they know that when they run their tests against the users test kit it will behave exactly the same like it will behave in production with the real user server now we have all these benefits about tests but when we are writing test kit we have another benefit which enables you to communicate better with your consumers okay this is not related test not related to test but if you were doing test kits you also get it for free so now your consumers run your code so it's basically it's a live contract between them and you and if you change something let's say you change the validation and they use it in a way that it will not pass your new validation a test will break and then you can decide what you need to do maybe they have a bag or maybe you did the breaking change and you can see this and you can decide it before you discover this bag in production also so it's it's like much better than documentation because the commutation get stale and if it's even if it's not get stale like nobody reads this so this life contract the trans in the [Music] and the continuous integration now another benefit here that you can put your new test kit logic here so this way you another way to communicate with your consumers so for example you want to deprecate a method so now you can write log when every time they call this method you can write log the day we'll see Hey this method is deprecated please stop using this or you can even control an exception and then their test will break while in production it still works okay so it's very nice a way to communicate and it works good in large-scale organization now of course it have also disadvantages so one big disadvantage of this method that it's a lot of development so the team that responsible for this have a lot of work to do and also it brings out a lot more bill dependencies so now all the consumers depends on the implementation of your service in in CISD but we do get and end to end solved and integration solved in the way we wanted we are getting the short feedback loop and we are getting high confidence that it really works that if it works in tests it works in production that's that's the goal that we want to achieve that if something fails we want it to fail in tests and not in production it's much cheaper to fix it and no user impact so we saw a few approaches here let's um let's wrap it up so the first one was not to test it probably you don't want to go there then we saw that you can test against external environments well it might be tempting in the beginning but try not to go there as well then we saw the test doubles approach which is a legit way in my opinion we use it a lot at weeks and it's easy just easy to do it it's very quickly very easily and then the last way which is test kids which give you the highest level of confidence but has a lot of work and and then you have the dependencies issue so there is no rule like when to use what a general rule of thumb is that I usually look at the number of consumers and the complexity of the API so if the API is simple and you don't have many consumers just go with the test doubles approach you don't have a lot of integration problems it's pretty easy if your MPI is very complex or you have a lot of consumers you have a lot of integration issues you need to help a lot of consumers to integrate with you go with the test kits we finished a bit before time so if you liked this talk please read it and I'll take questions if you have any questions okay so I'll hang out around here so if you want to if you have more questions I want to discuss it with me feel free to combine thank you
Info
Channel: Devoxx
Views: 7,360
Rating: 4.6799998 out of 5
Keywords: Devoxx, Devoxx2018
Id: BhjFUVuPa2U
Channel Id: undefined
Length: 32min 45sec (1965 seconds)
Published: Wed Nov 14 2018
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.