Laravel TDD in "Live" Mode: Checkout Code Review

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hello guys today we'll have another laravel code review on this channel a little bit untypical a review of a job interview task performed by the person who emailed me so that person got feedback negative on the task for the job interview and we will review both the code of that person the task and how i would perform the task the task is this you can pause the video and read it all but i will summarize it for you basically price checkout system with this structure it's a php task it's not necessarily laravel but that coder decided to use laravel because it was allowed as you can see in the interview they say you can use any framework and also limitation an hour for this test this is by the way important because quite often i see in the job interviews they give test tasks with really limited time in order not to test how you perform the task itself but how much you can achieve and how you think and how you choose the shortcuts for something because usually or quite often it is impossible to perform a really well done project in limited time like an hour so anyway we have the limit of an hour and we need to implement something like this checkout system with some extra rules the rules are if you buy three or more of one product the price is different if you buy one fruit tea you get one more free and maybe a few more rules and there's even test data at the end to test the situations and the task is to implement a checkout system it doesn't say anything about design it doesn't say anything about how to structure the code so it's kind of a free thinking task now let's see how that developer performed the task i have cloned the repo attached and this is the result so it's a website web page where you can actually add something to cart so you can add a fruity you can add a strawberry you can add more fruity it calculates the balance then you can view cart and then you can remove items from here are you sure okay you continue shopping and the price is calculated here from what i understand and this is my first thing i immediately asked to myself the task didn't require to build a website with full like design with products with photos with cards on a separate page i think if the task says one hour limit i think they expect you to do something really minimal without fancy things like this icon for the card or these photos which represent actually strawberry or tea or coffee and i understand that that developer want to over achieve and over deliver and that is fine but the task wasn't about the website the task was to implement the system if we take a look at it again this was the actual task the implementation of this interface but okay maybe the over-engineered for the right reasons let's take a look at the code and there is a good readme written well great and in routes web i usually start with routes we have product controller index and what does it say we get all the products and by the way great thing that there are cedars products here for the product table good so in the product controller we get all the products and we get all the card cart is another object another eloquent model another database table we get the quantity and total and pass that to the welcome and that welcome blade contains all the products so on top we have for each of the products those images prices and then on the right hand side well first you have add to cart with the form of card store and then on the right side you can go to cart and it's calculated the total amount and then there are two more lines in the routes web route resource for the card but it doesn't make much sense to me to use route resource where you actually use two methods so route resource accept all those methods instead of having two routes for card index and card store and also cart delete should not be routed yet it should be route post for deleting with are you sure confirmation but it works let's take a look inside of the card controller what it does first the store method so we start from the request we validate so we validate the quantity but we don't validate product id interesting or actually we do validate the product from find or fail so it would throw 404 error okay and how do we calculate the price that's the essence of the task to calculate the price so we calculate it immediately when adding the product okay maybe then for this structure card where product id first i would use eloquent function called first or create something like that then you don't have to have if statement here but okay so we create the card or update the card right and there's nothing here about any calculations of those extra conditions in the original tasks so these ones are not mentioned anywhere here would just save the card and if you want to delete the card is just find or fail delete okay-ish and now where are the calculations of that extra logic so where is something like scan or total or something like that and where are those rules they are actually when viewing the card in the cart index we have get all the items these are variables which are not actually even used so they are probably temporary and forgot to delete and then for each of the items if that's a second product then we apply price policy fruit t and save so we update the total price of every item on the viewing of the checkout i'm not actually sure if it's correct because when i'm shopping online for example i would like to view the discount before i click the checkout button or view cart i'm quite used for discount to be applied automatically when scanning the product and even at the checkout in the mall in the shop when scanning the item the discount is kind of already there calculated so i don't think that's correct approach but it does work now what is price policy fruit t those are methods in the card model but how do they work so if the product code is this then we apply the discount and save but didn't we actually check already that the product id is two why do we have to check the product code again i'm not entirely sure maybe there's a logic here also variable that isn't used and similarly price policy fruit t more logic around fr1 and calculating the total price so yeah these are the calculations what are my overall comments over this code some small details around routing and eloquent are not optimized and could be improved but that's kind of okay depending on the position on the seniority of the position they apply to but the task wasn't about laravel the task was about php and i think the main reason why the interview failed for that person is that they didn't implement the actual task they were asked for so original task is this interface it doesn't say again it doesn't say anything about website about design all they needed to do is implement this checkout it could be laravel but that checkout interface or checkout class should have existed with method scan of the item you may say that it's similar to card controller store method this one store but it doesn't actually return the total the total is returned only later and also what is more important i think there are no tests here so this is the task perfectly suitable for tdd implementation so first you write test and you have exactly three tests what are expected from you and then you implement some kind of class or service for the checkout and you make those tests pass that was the task on top of that if you have time or if you have willingness you should have implemented the checkout process on the web with using that service class and let's actually try to do that so we will go with classical tdd test driven development approach first we generate the test make test check out test and it should be a unit test because we're not testing a feature so we won't actually do anything on the web or in the future we will give the unit the parameter of checkout item and then test the result so in tests unit we now have checkout test and we will implement all three methods that are listed here so test check out one for example check out one first we create the checkout actually let's copy one by one it should be almost identical so new checkout and none of that exists yet that's the point of tdd first we write test what we expect and we assert this assert equals co total to 2245 like this so this is our test for now it will totally fail so let's delete some feature tests here an example test here we have only checkout test and if we run php artisan test now it will fail oh now because test feature not found let's open phpunit xml file and let's remove the feature tests at all like they don't exist and now php is on test test unit checkout not found that's exactly what we expected now what will be our checkout object and let's create it as a simple php class and call it a service service is basically any php class in laravel so we create a folder in app called services and then in the services let's generate a new file check out service php and then inside we have namespace of app services and class checkout service that's it so we do have that class now and then in the test instead of checkout we do check out service it's automatically generated in the use by phpstorm and if we launch our test now php autism test we have another error undefined variable pricing rules and step by step we will create the functionality so basically this code review is a lesson on tdd on test driven development also tying that with the previous code that developer has actually written so let's continue we have pricing rules what should be the structure of pricing rules and i think that was the core task of this whole job interview to come up with some kind of flexible structure because the task says that ceo and co change their minds often it needs to be flexible regarding our pricing rules which means you would be able to add more rules more products in the future so pricing rules needs to be some kind of array for example array of rules which would be for example for some product code for example cf1 for the coffee the rule would be multiply called multiply and that would be the method inside of checkout service and you can do it in a different way for now let's try to make it as a method so actually let's take the actual example fruit t with fr1 should be get one free get one free then strawberries which is sr1 would be three or more price dropped to 450 so for example it could be bulk discount and let's actually come up with some kind of parameters so it's even an array for example method name and then there will be parameters bulk discount on how many so three items and price dropped to 450. okay so we have array of pricing rules for now it's not implemented but we passed that here now we need to accept that in the service so we have private pricing rules and then public function constructor of that checkout service accepts pricing rules and then assigns that like this so have an array of pricing rules inside of that checkout service if we launch the test again it will be another error of undefined method scan so let's define that in the checkout service we define the method public function scan of item and the item is just item code we could use item id but probably the original job description assumes that product code is a unique identifier so item is a string we can actually even specify that as a string and then to do of add item and we don't need the quantity here because as usual checkout is just scan scan scan one by one and this is exactly what is implemented here so we implemented that and our php artisan test now shows undefined variable item and the item in this first case is the original this frsquare so this sequence let's copy it one by one so fr1 would be the first to scan and then it would be sr1 duplicate duplicate duplicate so fr1 again for one again and cf1 again so that's the first test and we should actually even add that as a comment here what are we testing so example scan like this next error php is on test and define property total total should be a property of that service not even a method so so it should be public of total which is zero by default let's put it this way and then at the end of the scan we need to define the total by some new rules so for example for now let's add that to zero and then it will make the test actually running but failing not from the syntax perspective but already from the logic perspective failed asserting that 0 matches 2245 and the next step is to actually make it 22.45 okay now what should happen in the scan method we need to find the product by that item code so we do exactly that and this is where we use eloquent now so product equals product where product code equals item first if we find that product it shouldn't be something like find or fail in the service if we don't find the product we could potentially throw an exception or we just don't do any calculations and don't add anything so if the product is found we need to add that to card and recalculate this total like this so this should be some kind of variable to add that product to cart there are multiple ways how you can do that but let's stick to the original idea of the developer to add that to the database and in the cards database table we have product id quantity price and total in my opinion price and total should not be here it should be calculated so let's save only product id and quantity or not even that we don't actually need the quantity the column default is one so let's just add product by product if product would just add that to card card create product id equals product id and we'll have multiple rows of the same product and that's fine and then for this total we will have a private function of recalculating the card private function get total like this and this total will be this get total and let's return zero for now and this get total will be i would like it to be in some kind of structure as array of fr1 is one item for example like cr1 is two items and stuff like that i actually do that a lot of times i do the comments before i write the actual code so get the total should be in this array and then we'll apply the pricing rules according to that array product codes so card products will be equal card with product get so we get all the card and then let's add the grouping so select off or select row actually select raw products product code let's make it more readable and then and then sum of quantity from the card actually cards quantity get should be before and then we group by products product code and i think that's it actually let's dd card product and see the result we run the test again oh we don't have the connection to the database from our checkout test and this is exactly what is described in the php unit xml we uncomment those two lines and those should be okay so we will use sqlite memory database for the tests and also we need to make some changes in the test itself in the preparation of the test to use the database for the simplicity and for the convenience let's extend tests test case which comes with laravel it's not default php unit but more laravel related tests and then we can use refresh database it's a trait that refreshes the database in sqlite memory and also we need to see the product so for that we have a function of set up the test which first need to call parent setup for all the tests and orders so we need to return void here we can call the seed command by doing autism call db seed like this and now if we run our test again we run the test no such column product code oh of course because the query doesn't really join the product join i think we don't even need that relation actually with product join products on carts product id like this we don't need these signs actually we do let's actually do another thing so query and then we join let's try to do that again and we have dd that is great so we have product code and sum and just instead of sum we have quantity so we have card products now and then for each of those products like actually total equals zero and then for each of card products as product actually we do need the product price here so select row product code products price and then sum group by products code and then products price and then we can calculate total plus product quantity times product price and later we will use product code to actually calculate the discounts so for now we have total and return total like this hope i didn't miss anything let's launch that again artisan test no such column products price what is the actual column price we do have the price so why is that and of course it should be grouped by raw as well let's try again and we have 25.56 we have some kind of calculation great so that's already an achievement believe it or not and let's actually debug it by using info for product quantity and product price and see what we actually have inside of those variables like this info means that it will be written in laravel log file so if we launch that again the test let's see why it is 2556. we open laravel log okay and we have 3 11 5 3 11 probably exactly as we wanted for the clarity let's add another info at the end so each time we launch that adding to cart we will recalculate that so let's run the test again open our laravel log of course i forgot the product code so that is important what product code we have product code like this let's try again and now in laravel log we should have something like this so is for one with this then this one then two items three items and total five items so this is correct and the sum of all of that is actually the price the original price should have been 25.56 but after applying the discount it should be 22.45 let's try to apply the discounts so this is where we have those pricing rules now in place remember we have the pricing rules as this array so let's actually copy and paste it as a comment and we will view them and do for each of them so those are pricing rules i will comment it out just to see them visually do we have a pricing rule for this particular product rule equals this pricing rules of product product code like this or no if is not know the rule then it's array right so then we need to calculate the total not by this but by other function which would be for example total equals this and then there will be a function called for example get one free here so let's create that private function get one free but the function name will be dynamic so this rule zero maybe with parameters so we should probably get the parameters anyway with rule 1 and rule 2 probably and we will get them in here it should be probably more flexible as parameters but null and no but for limited amount i won't make it more flexible let's stick to this one so we are calling that function which is returning for now let's make it the same so return product quantity and product price so we pass the product and then rule one rule 2 for example parameter and we return for now we don't use rule 1 and rule 2. i'm actually surprised how complex it gets but anyway that should work or let's try it out actually i'm not sure at this point autism test too few arguments to the function get one free of course we need to pass the product because i've made it so so artisan test bulk discount oh of course we defined bulk discount as a feature as a function but we didn't implement that so bulk discount for now the rules will be the same so if it's not no the rule else if there's no rule then we calculate the total usually now let's run that failed asserting that 5 matches 22.45 that's interesting oh of course because it's not 5 it's total plus so it's total for all products forgot that one autism test and we should be back at 25 56 great and now we can implement those get one free and bulk discount so the original rules of get one free buy one get one free means that if there is a fruity you get every second item for free so we need to calculate the actual quantity which would be product quantity divided by two so it's floor of quantity plus maybe we have product quantity divided by 2 and what is the leftover i'm not sure if it's the correct english word and now let's change that quantity to here so basically if the quantity is three for example you will pay for one product plus one product if the quantity is two you get paid for one product plus zero product so for one product only and let's refresh that and we have a passing test guys so our 2245 is exactly what we expected so get one free test is finally passing so now we need to implement other two tests so test check out one let's copy and paste that into another checkout service or actually let's make it in the same method i start equals so we create another checkout service with the same rules and the scan is different so fr1fr1 like this and the result should be 311 and that should actually pass as well let's try it out we should have two passing tests no it's failing why oh because we don't clear the card so yeah it should be a different method so in every method it's clearing the database and pricing rules should be probably some kind of a property of the test but let's leave it this way for now and let's run that again we have two passing tests guys and then we add the third one actually f41 for one bad example like this and test checkout three will be sr1 f41 like this f41 sr1 like this and the expected result is 1661. 61. and that should probably fail 1811 is the actual price because we add three four five and then this one but if we buy three or more strawberries the price should drop to 450. and here's where we will use those parameters so rule one and rule two so bulk discount will actually be if product quantity is bigger or equal to rule1 then price equals rule2 something like this by default price will be equal to product price but otherwise it is different and we calculate that by price and we run our test again and we have successfully succeeded all three tests now finally so by this point the video took much longer than i expected just to make those tests pass but actually i think the job description was exactly that passing tests from the service and now you can use that service in any controller for example in cart controller or in product controller here in the store method by doing checkout service so new check out service scan request product id like this and don't care about anything else and whenever you need the total you get that total but you get the idea you have the service which makes the calculations you have the tests that are covering all the cases and then you can safely use that service in controller or wherever that's the main idea of test driven development you have the tests first they fail then you make them succeed then you implement the actual code and i think this approach of tdd would have gotten the job for that developer and now as a final part of this video let's actually implement that service of course it should not be product id it should be product code and by the way i'm shooting this part the next day from the other tdd because i was too tired after fighting the tests and making them work so now let's implement that and without that fancy design i will just scrap that and since the job description was to implement the checkout process it doesn't say anything about design let's create a new home page php artisan make controller home controller or something like that and then we can use instead of that get we will use our home controller index with name home so comment that out and in our home controller index we have a method of index and we have products equals product all so we do need all the products return view home for example with the products okay and that view home will be a simple html file without any design because remember the original task was one hour so we need to implement the background logic with the test which we already did and now we need to show something on the home page how it works it's kind of similar like a test just without autism test it would be a visual test so just html body we could even skip that and then for each of the products we show the product in a list like this so we have a list of products product name and product price like this let's try to launch our home page okay we have coffee fruity and strawberry i will zoom it in a lot and instead of price let's do number format with comma two and with dollar sign like this okay then we need what we need the scan method so we need a link for every product we need a link to scan that product and this is exactly what we will do so we will have some kind of a route of scan with product id or actually not even product id product code product code scan and then let's create the route scan duplicate that scan let's reuse the same home controller it could be a form it could be actually a post request to the scan but let's keep it simple name scan and we need to add a parameter scan of what product code like this we refresh and we have the scan links good and then in the home controller scan we need to implement public function scan product code what we do here will you guess new checkout service we just create a new object scan of product code and then we need the pricing rules so let's copy those from the test pricing rules are these and for now let's put them inside of the scan but in fact there should be some kind of config or some kind of database structure even for the rules to make them flexible and add more in the future so they shouldn't be in different places there should be in one place in config but again we're strict on the time one hour right so we scan the product and then we redirect where we direct to home probably return redirect to route home and in our home let's also view the total price so after that list let's put total dollar and then we do what total price right and how do we calculate the total price we have the same checkout service but not scan we need the total so total equals total and even here i see we repeat the same pricing rules so let's move them at least outside of the function as a property so public pricing rules and then it will be at least in the controller in the conflict of the controller so this pricing rules and this pricing rules or maybe it could even be inside of the service as the private service property or something like that as usual in the coding there are multiple ways how to do the same thing so we have our total and we view our total with actually total price it is called so total price and total price and also we do number format like this price two and let's launch our home page again and let's scan something we scan you didn't do anything why okay now i see a bit flaw in our implementation but we won't fix it too much so basically we're doing the scan and we're returning total only on the scan so we don't have a total outside of it and since we're doing the scan on different pages so scan is one service object and indexes another service object it doesn't save the total so instead of this implementation we should make public method of get total and then it will calculate the card and we should call that as get total like this and now let's see our card we have something we scan it's probably a free t let's click again now it's not free click again it doesn't change click again now we pay for the t so it's implementation of actually discounts and for the strawberry let's see the price difference five dollars five dollars 450 so that works as well great so finally that would be my result of doing this task so again tests service and then implementing that service from the controllers on the web of course it's not ideal of course maybe it should be differently structured in terms of how do we assign pricing rules maybe not in the constructor also how we publish the total also in the real eshop there should be some kind of user id or session id because it's calculating now all the cards not for a specific user but again within one hour that's as much as we can achieve and again i think that this implementation would have more chance for that developer who emailed me to get the job position what do you think guys anything you would add any criticism is welcome because i'm a human too i maybe made a mistake i maybe not ideal in my coding i'm teaching what i learn and from my experience but i do accept your comments and your criticism so be active in the comments i will not publish this as a repository because that's a repository not of my own it's from that developer but i think you got the idea just by watching all this video and if you watched it until the end thank you and if you want to thank me for shooting this long video in two days you can support my mission of daily videos on this youtube channel by checking out one of the three products you can see on the screen quick admin panel generator for admin panel live wirecast set of components and one of my currently 19 courses on teachable that's it for this time see you guys in other videos
Info
Channel: Laravel Daily
Views: 48,401
Rating: undefined out of 5
Keywords:
Id: 5XywKLjCD3g
Channel Id: undefined
Length: 36min 4sec (2164 seconds)
Published: Sat May 01 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.