About Features Downloads Getting Started Documentation Events Support GitHub

Love VuFind®? Consider becoming a financial supporter. Your support helps build a better VuFind®!

Site Tools


Warning: This page has not been updated in over over a year and may be outdated or deprecated.
videos:testing_2

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Next revision
Previous revision
videos:testing_2 [2021/05/07 16:23] – created demiankatzvideos:testing_2 [2021/10/22 14:36] (current) – [Video 17: Testing, Part 2: Writing Unit Tests] demiankatz
Line 1: Line 1:
-====== Video 16: Testing, Part 2: Writing Unit Tests ======+====== Video 17: Testing, Part 2: Writing Unit Tests ======
  
-The seventeenth VuFind instructional video dives deeper into testing, showing you how to build a real PHPUnit test to improve VuFind's test coverage.+The seventeenth VuFind® instructional video dives deeper into testing, showing you how to build a real PHPUnit test to improve VuFind®'s test coverage.
  
 Video is available as an [[https://vufind.org/video/Testing2.mp4|mp4 download]] or through [[https://www.youtube.com/watch?v=CY6cTY6_OX8&feature=youtu.be|YouTube]]. Video is available as an [[https://vufind.org/video/Testing2.mp4|mp4 download]] or through [[https://www.youtube.com/watch?v=CY6cTY6_OX8&feature=youtu.be|YouTube]].
Line 12: Line 12:
 ===== Transcript ===== ===== Transcript =====
  
-// Coming soon! //+// This is a machine-generated transcript and will be corrected as time permits. // 
 + 
 + hello and welcome to 
 +this month's viewfind video which is 
 +continuing the 
 +topic of testing which was introduced 
 +last time around uh in the previous 
 +video 
 +i showed you how to set up a test 
 +environment and actually run 
 +some tests and this video is going to 
 +assume that you have already done that 
 +and know how so this time around we're 
 +actually going to look into 
 +writing unit tests so first of all 
 +a quick reminder on what i mean about a 
 +unit test 
 +there are several kinds of tests that we 
 +can use 
 +in viewfind and unit tests are designed 
 +to test individual units of code 
 +so in theory every class in viewfind 
 +should have a corresponding unit test 
 +that confirms that it works correctly 
 +and you might ask why write tests there 
 +are a few reasons 
 +one is that test driven development 
 +is a very popular way of writing code in 
 +tdd 
 +you write your test first which 
 +specifies the behavior that you expect 
 +to see 
 +and then you write and revise your code 
 +until the test passes 
 +this is a really good way to get right 
 +at 
 +the programming portion of the problem 
 +and to ensure that all the code you 
 +write is doing what you want it to do 
 +of course some types of code are better 
 +suited to this type of development than 
 +others 
 +but certainly if you're writing a new 
 +component that does well-defined 
 +work doing it in a test-driven way 
 +is a great place to start uh 
 +and sort of a side effect of this 
 +development practice 
 +is that your tests become part of the 
 +documentation 
 +of the code you know it's it's certainly 
 +good to write documentation 
 +and to include good comments in your 
 +code but if you also have a test case 
 +that exercises the code and explains why 
 +it's doing that 
 +that helps to add to the understanding 
 +of what the code is for 
 +and what it's meant to do which can be 
 +really useful 
 +when onboarding new developers to the 
 +project 
 +and of course testing is important for 
 +some really obvious reasons 
 +we want our code to work correctly and 
 +if we have test coverage 
 +we can make changes with more confidence 
 +so for example if we want to refactor 
 +some code or make something more 
 +efficient 
 +if the code we're changing is completely 
 +covered in tests 
 +then when we make our changes if the 
 +tests still pass 
 +we can be reasonably confident that we 
 +improve the code without breaking 
 +anything 
 +now while i said in theory every 
 +class in viewfind should have a 
 +corresponding test 
 +unfortunately in reality that's not the 
 +case 
 +not all of this code has been developed 
 +in a test-driven way 
 +and so we actually have some pretty 
 +substantial gaps 
 +in our unit test coverage uh part of the 
 +reason i'm recording this video is that 
 +i'm hoping that some of you can 
 +help close some of those gaps and 
 +that you can be aware of this practice 
 +when making new code so that 
 +the existing shortcomings of viewfind'
 +test suite 
 +don't become larger so let me start 
 +by showing you a useful tool for looking 
 +at 
 +where we have and do not have 
 +coverage of code with tests in viewfind 
 +if you go to viewfinds jenkins instance 
 +our continuous integration system uh and 
 +you go 
 +into the viewfind project 
 +uh you'll see that there is an option 
 +here that says 
 +open clover html coverage report 
 +and this will show us a report of all of 
 +the unit test coverage within the 
 +project 
 +i should also note that if you ever come 
 +here and this link is missing 
 +it probably means that a build is in 
 +progress 
 +and you can actually go to one of the 
 +completed builds down here 
 +to find the link to the report for that 
 +particular build 
 +but at this moment in time there's no 
 +build running 
 +so we can just get the latest report 
 +through here 
 +and when you look at this report uh you 
 +can see a lot of red 
 +so you know we have more work to do but 
 +uh 
 +the important thing about this report is 
 +that it lets us drill down into 
 +different parts of the code base 
 +at the top level here it's split up 
 +across viewfind's existing code modules 
 +and we can drill down into namespaces 
 +from there 
 +and it reports on three types of 
 +coverage 
 +it tells us how many lines of code are 
 +covered by tests 
 +so if a line of code executes while a 
 +test is running 
 +it is considered to be covered 
 +then there's functions and methods a 
 +function or method 
 +is considered to be covered if every 
 +line of that function or method is 
 +covered 
 +so our line percentage is going to be 
 +better than our 
 +function and method method percentage 
 +and finally classes and traits 
 +and again a class or trait is considered 
 +to be covered 
 +if 100 of its functions and methods are 
 +covered so again these numbers are going 
 +to be even smaller 
 +but uh let's just pick something 
 +that could use a little more coverage 
 +and that will give us an excuse to 
 +look at how tests are written and then 
 +make some improvements 
 +so i'm going to go into the main 
 +viewfinder module which is where 
 +most of viewfinder's code lives and as 
 +you can see that's currently 
 +about 25 covered so we have quite a bit 
 +of work to do 
 +so when i click into here i'm now 
 +exploring the directory structure 
 +of viewfinds code so there's a source 
 +directory inside the viewfind module 
 +i'll click into there i also need to 
 +click into the viewfind directory where 
 +the viewfinder namespace lives 
 +and now i've gotten to some things that 
 +are actually interesting 
 +so here are all of the sub namespaces 
 +within the viewfind namespace 
 +and i can drill down into any of those 
 +but rather than go too deep into the 
 +code 
 +i'm just going to come down here to the 
 +bottom where i notice 
 +you know we have a number of things that 
 +are green that have pretty good coverage 
 +a number of things that are red and have 
 +no coverage 
 +at all but we also have this export.php 
 +which has yellow coverage it's almost 
 +there 
 +it just needs a little more work so i 
 +thought this would be a good example to 
 +look at today 
 +maybe we can turn one yellow light green 
 +in the process of learning about testing 
 +so when i click through to an actual php 
 +file 
 +my report looks a little bit different i 
 +have a summary at the top 
 +showing all of the methods within the 
 +file 
 +and again these have the same you know 
 +coverage percentages 
 +and so we can see here that most of the 
 +methods are fully covered but we have 
 +a handful that are either not covered at 
 +all 
 +or are only partially covered and if we 
 +scroll down we can see that this report 
 +gives us 
 +all the source code and it colors in the 
 +lines based on whether or not they have 
 +test coverage 
 +which is really handy so this lets us 
 +get directly to the place where some 
 +work is needed 
 +i should also notice you'll see some 
 +yellow lines um 
 +and that just means that these are never 
 +going to be covered because 
 +the code returns here so it's never 
 +going to get to this closing grace 
 +and that's safe to ignore uh in 
 +in this particular instance and also 
 +you'll see that there are many lines 
 +which are not color coded at all and 
 +that's because 
 +those are non-executable lines of code 
 +you know 
 +comments don't count for code coverage 
 +uh 
 +function signatures aren't covered uh 
 +it's just the the contents that matter 
 +so let's start uh by taking a look 
 +at the method that is partially covered 
 +which is this get bulk export type 
 +method 
 +and as you can see we've got one red 
 +line in here 
 +and two green lines so that clearly 
 +means that what's happening here is that 
 +this first if condition 
 +is never getting matched in any of the 
 +tests 
 +which means that this return is never 
 +getting called 
 +so we are going to need to add a test uh 
 +to cover that edge case and then this 
 +whole thing will light up green 
 +and before we do that let's just uh step 
 +back a moment 
 +and talk about what this export php 
 +file actually is viewfind has the 
 +ability to export data 
 +to external bibliographic management 
 +systems and to a variety of data formats 
 +uh and that requires a lot of 
 +configuration because there are 
 +different ways different formats need to 
 +be exported 
 +and this export class is mostly just a 
 +wrapper around all the configuration 
 +that allows other code to figure out how 
 +to behave 
 +when exporting different formats of data 
 +that makes it a pretty good candidate 
 +for writing tests on 
 +because we have well-defined inputs and 
 +outputs that we want to test 
 +so hopefully writing these tests will 
 +not be too hard 
 +as you can see we have a couple of 
 +properties in play here 
 +export config and name config 
 +and these are just objects representing 
 +some of 
 +you finds any configuration files so 
 +main config 
 +is config.ini export config is 
 +export.ini 
 +and then all this behavior here is based 
 +on what settings are set 
 +in those configuration files all this 
 +should be easy to simulate 
 +in a test but before we write any code 
 +let's take a look at the existing test 
 +and how that works 
 +so all of view finds tests are 
 +contained within the modules where the 
 +code that the tests 
 +operate on live so we're working now in 
 +the viewfinder module 
 +so as i've showed in past videos all of 
 +the source code for the viewfind module 
 +lives under the source directory but 
 +there's also this parallel 
 +test directory where the tests live 
 +so let's open up the test directory and 
 +in here 
 +there are a few subdirectories there is 
 +a fixtures directory which is where we 
 +store 
 +test fixtures or data that is used by 
 +some of the tests 
 +uh we're not going to be looking at that 
 +today but it's good to know that that 
 +exists 
 +then we have integration tests which 
 +we'll be talking about more in the 
 +future 
 +and then the unit tests directory which 
 +is where the unit tests live 
 +so we're going to drill down into that 
 +and so inside there we have sort of a 
 +parallel structure to the name 
 +source code but we use the viewfind test 
 +namespace 
 +instead of the viewfind namespace just 
 +to prevent collisions 
 +the test framework doesn't care too much 
 +about namespaces 
 +but it's still helpful to use them for 
 +for that uniqueness 
 +and if i scroll down far enough here 
 +is my exporttest.php 
 +now as i mentioned in last month's video 
 +we use the php unit framework for 
 +testing 
 +and this is widely used and well 
 +documented and today's video is not 
 +meant to be 
 +a replacement for a good tutorial 
 +on php unit i would strongly recommend 
 +that you seek one out 
 +uh but i'm going to show you just enough 
 +to get a basic understanding of what 
 +we're doing here 
 +and hopefully to help you get started 
 +and then other tutorials can 
 +build upon that so 
 +every test has to extend php unit 
 +framework test case 
 +which is just the base class for test 
 +cases 
 +in the php unit framework any 
 +test case class then needs to have 
 +public methods 
 +whose names begin with the word test 
 +so every function in this class that 
 +starts with test 
 +is one of the tests that we want to run 
 +and as a general rule you want to make 
 +these pretty granular 
 +so that if the test fails you understand 
 +exactly 
 +what behavior didn't work correctly 
 +you could just make one big test 
 +function and put all your logic in there 
 +but then if something goes wrong 
 +it's going to be harder to figure out 
 +what went wrong 
 +uh and you know there's there's some art 
 +and some science to this 
 +but as a general rule i recommend erring 
 +on the side of 
 +granular tests so that you can identify 
 +points of failure 
 +uh and within each test the real heart 
 +of a php unit is making assertions 
 +because the idea is the test is going to 
 +try to do something 
 +and then it's going to make assertions 
 +about what it expects 
 +should have happened uh and a lot of 
 +these assertions are just 
 +you know calling a function and 
 +asserting that its return value will be 
 +equal to something 
 +so for example in this get bulk options 
 +legacy 
 +uh test we are 
 +running the exporter with a particular 
 +configuration 
 +and saying that when we call this method 
 +it's going to return 
 +this array and so php unit 
 +will pass the test if all the assertions 
 +are actually matched and it will fail 
 +the test if any of the assertions 
 +are not the framework has 
 +a pretty rich variety of assertions that 
 +you can make 
 +though much of the time you're going to 
 +be asserting equality 
 +or asserting that some condition is true 
 +but i highly recommend taking a look at 
 +the php unit documentation about 
 +assertions 
 +just to get a feel for all of the things 
 +that you can express 
 +in this way as some of them are quite 
 +interesting 
 +in any case if we scroll back down 
 +through this file we're going to see 
 +a whole bunch of tests all of which 
 +contain assertions 
 +because a test with no assertions is 
 +completely useless 
 +and then down below all the tests 
 +there are a couple of helper methods 
 +so i have this get fake mark xml that 
 +one of the tests uses 
 +to create xml records uh and i have this 
 +get 
 +export which is just a convenient way to 
 +create 
 +an export object for us to test 
 +as i mentioned when i was talking about 
 +the export class 
 +it relies on objects representing 
 +configuration files 
 +and so this method just makes it really 
 +convenient to pass 
 +test configurations into an object 
 +without having to 
 +instantiate the config objects or or do 
 +extra work 
 +and if we want to test the default 
 +behaviors 
 +the fact that these configuration 
 +parameters 
 +default to empty arrays means that we 
 +can just say get 
 +export and we'll get an all default 
 +object without having to do any 
 +configuration at all 
 +i usually find that when writing a test 
 +like this 
 +having some kind of a helper method to 
 +construct 
 +the subject of your test is really handy 
 +but anyway now that we've gotten a quick 
 +look 
 +at uh what a test class looks like 
 +let's start building on this so 
 +as we looked at before the get bulk 
 +export type method is not fully covered 
 +and if we looked closely in this class 
 +we'd actually see that there is no 
 +method 
 +explicitly testing uh get bulk export 
 +type 
 +the only reason that we're getting 
 +coverage on that method at all 
 +is that one of the other methods is 
 +calling it and that coverage is 
 +happening as a side effect 
 +so we're just going to want to create a 
 +whole new test method 
 +that tests all the functionality of get 
 +bulk export type 
 +so that we can be confident that that 
 +method is behaving the way that it's 
 +supposed to 
 +so when writing a test the first thing 
 +to do is to 
 +analyze the code and figure out what 
 +it's supposed to be doing 
 +so that we know what cases really need 
 +to be tested 
 +so let's take a quick look at this get 
 +bulk export type 
 +method again so 
 +essentially there are three things that 
 +can happen here 
 +the first thing that could happen is if 
 +the format that we're asking about 
 +has a section in export.ini and that 
 +section 
 +contains a bulk export type value 
 +we're going to return that value so this 
 +is the most specific case 
 +where the export format has a particular 
 +value configured for it 
 +if that doesn't happen the next thing 
 +that we're going to do is look in the 
 +main config.ini 
 +where we can set a default 
 +export type that will be used for 
 +everything that's not more specifically 
 +defined so if that value is set 
 +we'll return it but we use null 
 +coalescing here 
 +so that if this is unset we then return 
 +the default value of 
 +link so our test should really look at 
 +three things 
 +it should confirm that the default value 
 +comes back as link 
 +if we don't provide any configuration 
 +it should determine that if we 
 +ask for a format that doesn't have its 
 +own settings we get whatever the default 
 +configuration 
 +is and it should 
 +return the appropriate format specific 
 +setting 
 +if one is defined so let's write a test 
 +that does 
 +all of those things 
 +so i'm just going to go beneath 
 +the last test in the file since i'm 
 +adding something new here 
 +and i'm going to put on my comment 
 +test get bulk export type 
 +and all test methods have a void return 
 +the test framework is working off of 
 +assertions it doesn't care 
 +what values the functions give it 
 +so we will create public function test 
 +get bulk 
 +export and that will return a void 
 +so how do we want to do this 
 +first of all let's do the easy part we 
 +should get 
 +link as the default if nothing is 
 +configured 
 +so we'll put in a comment to clarify 
 +that 
 +and we can just do this directly with an 
 +assertion 
 +we're going to say assert equal 
 +and this is of course a helper method 
 +given to us by 
 +the php unit framework that we're 
 +inheriting from 
 +uh and in assertions you always put the 
 +expected value before the calculated 
 +value 
 +uh this is how you ensure that error 
 +messages are formatted correctly 
 +so it's always expectation then 
 +calculated values so in this case 
 +we expect to get link back uh if we 
 +call this method on a configuration free 
 +export object then we just need to call 
 +the method 
 +on a configuration free export object 
 +which as i demonstrated earlier we can 
 +say 
 +this get export to get that object with 
 +no configuration set and that will call 
 +the helper method i showed earlier 
 +and so then on that object we've created 
 +we can just directly call 
 +getbulk export type and we have to give 
 +this an export format 
 +uh and it's fairly widely used 
 +convention 
 +uh in tests to use values like who 
 +bar and baz when you're putting in 
 +an arbitrary value so i'll just request 
 +the export type 
 +of foo so here we're saying 
 +if we get an export object with no 
 +specific configuration 
 +and we ask for a bulk export type we're 
 +going to get link back because that'
 +the default we expect 
 +that simple uh and you know it seems 
 +like this is kind of an almost pointless 
 +thing to test but 
 +it's important to test every edge case 
 +because 
 +you know say for example the code 
 +mistakenly 
 +assumed that there would always be 
 +configuration set having this test will 
 +confirm that that doesn't throw notices 
 +or cause 
 +unexpected errors always test every edge 
 +so that's our our first test and our 
 +easiest one 
 +for our other two cases the default 
 +configuration 
 +or the format specific configuration we 
 +have to do a little bit more setup work 
 +to make that work so first as always i 
 +like to start my test cases by putting 
 +in a comment about what i'm going to do 
 +before i do it so we're going to say we 
 +should get export specific 
 +values from export.ini if set 
 +otherwise we should get the default 
 +setting 
 +from config.ini 
 +so let me set up some configurations 
 +that we can use 
 +to test this scenario so first of all 
 +let's create our main config which 
 +should have a bulk export 
 +section which could that which should 
 +contain a default 
 +type setting and we'll just set that 
 +to download for this example 
 +there are three bulk export types that 
 +are currently supported 
 +which are link download and post 
 +since link is the default we can use the 
 +other two non-default values for our 
 +other two test cases 
 +which then lets us test everything as 
 +elegantly as possible 
 +we could also use fake values here and 
 +that would be equally valid for testing 
 +but 
 +in this instance it just feels right to 
 +use real values 
 +so that's what i'm doing so we've now 
 +set 
 +a global default bulk export type 
 +let's also create a format specific one 
 +so we can test both of these cases 
 +so the export configuration is 
 +keyed by the format identifiers 
 +so let's continue using 
 +foo as one of our examples let's say 
 +that who 
 +has a bulk export type 
 +of post 
 +so we've now created these arrays 
 +representing the configurations 
 +so we can pass them to our 
 +get export method 
 +to get a configured instance of the 
 +export object 
 +that uses these configurations so 
 +now our object is configured so we just 
 +need to make a couple of assertions 
 +so first of all we want to assert 
 +that if we ask now for the export 
 +type of foo we're going to get post back 
 +because that is what we configured in 
 +the export configuration 
 +so we can just assert equals post 
 +export get full export type 
 +of foo 
 +and now we need one more assertion to 
 +test the default behavior 
 +so for this we're going to assert equals 
 +download if we ask 
 +for export get bulk export type 
 +of bar so as i say foo and bar 
 +are popular placeholder values and tests 
 +we configured foo so we know what that'
 +going to give us bar 
 +is not configured anywhere so we expect 
 +that to give us the default value of 
 +download 
 +and so now in a few lines of code we've 
 +tested 
 +all three of the edge cases 
 +that the get bulk export type method 
 +could give us 
 +and as i say this is a place where 
 +granularity is debatable we could really 
 +have 
 +three tests here each testing one of the 
 +edge cases 
 +and then if something failed it would be 
 +really clear which test had failed 
 +um but in this case having three entire 
 +tests for 
 +testing closely related functionality 
 +just felt like 
 +unnecessary overhead so i did this 
 +as a single test there's no right or 
 +wrong answer 
 +you'll get a feel for it as you work 
 +with it more 
 +but anyway now that i've written this 
 +test 
 +uh why don't we run it and 
 +see if it passes so i'm going to go to 
 +the terminal 
 +and i'm now in my viewfinder directory 
 +that i set up 
 +last time for a test environment 
 +and as we talked about then 
 +i want to have make sure that my 
 +environment variables are set correctly 
 +so i created this 
 +test environment.sh script in my home 
 +directory to do that i'm just going to 
 +source that to be sure 
 +that all is set correctly and then 
 +i can use my uh home directory thing.sh 
 +script 
 +to run my tests with the php unit 
 +fast task 
 +and using the php unit underscore 
 +extra underscore grams property 
 +to pass an extra value to php unit 
 +in this instance the file name of the 
 +test that we want to run 
 +which we can type out here as viewfind 
 +home slash module slash view find slash 
 +tests slash unit test flash source 
 +export test dot php 
 +when i hit enter php unit runs really 
 +quickly 
 +and it tells me that it ran 12 tests 
 +with 21 
 +assertions and just to make ourselves 
 +confident that this really did work 
 +let's just temporarily break 
 +this test and see what happens so i'm 
 +just going to 
 +change this assertion so that it now 
 +expects z-link 
 +instead of link i go back to the 
 +terminal 
 +and i repeat that test it now fails 
 +it tells me it expected z-link but it 
 +got 
 +link it gives me the exact line where 
 +the test failed 
 +this is all really useful stuff 
 +and it will help you tremendously if 
 +you're using this for test driven 
 +development 
 +so we've done it we've written our first 
 +new test case 
 +here all three edge cases are now 
 +covered 
 +and it looks like the code is working 
 +the way we expected it to work 
 +if i go back to the coverage report 
 +let's see what else 
 +needs to be done so 
 +at the bottom here we have a couple more 
 +fairly straightforward methods that are 
 +just 
 +wrapping around configuration and 
 +returning defaults so for example 
 +get post field is either going to give 
 +us 
 +the post field defined in the export.ini 
 +or it's going to give us a default of 
 +import data 
 +very similarly get target window 
 +is either going to give us the 
 +target window setting from export.ini or 
 +it's going to 
 +compose the format name with the word 
 +main 
 +to get a window name 
 +i will write tests for these but i'm not 
 +going to make you watch me do it because 
 +this is 
 +extremely similar to what we just did 
 +however there's also one more uncovered 
 +method 
 +that's significantly more complicated 
 +to cover and so let's take a closer look 
 +at that one 
 +and that is this get bulk url 
 +method and this is more complicated 
 +because this method interacts with 
 +another 
 +object uh in this instance it's 
 +accepting a php renderer object 
 +from the mvc view layer 
 +of the laminas framework and it's using 
 +some of the helpers 
 +in that object to do work specifically 
 +it's relying on the url helper to 
 +call the laminas router and it's relying 
 +on the server url helper 
 +to figure out an absolute url 
 +and as i mentioned unit testing 
 +is about testing one unit of code 
 +the export class it is not our job 
 +in this test to test the url helper 
 +the server url helper and 
 +the php renderer we really don't care 
 +about those things 
 +except the extent that they expose an 
 +interface 
 +that this class depends upon and so 
 +it is in cases like this where we need 
 +to use 
 +mock objects mock objects 
 +are a useful testing concept 
 +where you create a fake object that has 
 +predefined behaviors 
 +which stands in for a real object during 
 +a test 
 +and allows you to simulate interactions 
 +fortunately a php unit comes with 
 +built-in 
 +quite rich support for object mocking 
 +and so you can use that when you run 
 +into a situation like this 
 +uh to test behaviors without having to 
 +bring in and construct lots of external 
 +objects and dependencies 
 +obviously there's also a need to test 
 +that real interactions really work but 
 +that's what integration testing is for 
 +we'll talk about that later when we're 
 +unit testing 
 +we generally don't want to go down that 
 +road 
 +and mocks are preferable as with 
 +everything there may be exceptions where 
 +it actually makes sense to bring in 
 +another class but that is the exception 
 +rather than the rule 
 +and you should think about mocking first 
 +uh 
 +when you run into something like this 
 +so let's analyze this code a little bit 
 +before we start writing 
 +uh just to see exactly what it's doing 
 +so this is taking 
 +the the view object the php renderer 
 +it's taking the name of a format and 
 +it's taking an array of ids 
 +and what this method is used for is 
 +constructing a url that can be used to 
 +export multiple records in a single 
 +batch 
 +so this is used by the bulk export 
 +functionality 
 +so we just need to test that this 
 +constructs an appropriate url 
 +for the input variables uh and we don'
 +really need to care 
 +too much about the internals of any of 
 +these helpers that it's using 
 +so what are we doing we're creating an f 
 +parameter 
 +that has the export format we're 
 +creating an array of i 
 +parameters that contain all of the 
 +record ids 
 +we're looking up the cart do export 
 +route which is going to do the actual 
 +work 
 +uh we're making an absolute url out of 
 +that 
 +so we're first using the url helper to 
 +turn this route name into a path 
 +and then we're using the server url 
 +helper to turn that path into a full url 
 +and then we're appending some parameters 
 +onto that base url 
 +based on this array we constructed up 
 +here 
 +then depending on whether or not the 
 +format we're using 
 +uh needs to be redirected to or can be 
 +directly accessed we're either using the 
 +get redirect url helper method 
 +or we're returning the url in an 
 +unmodified form 
 +so there are really kind of two paths we 
 +want to test here 
 +the needs redirect path and the does not 
 +need redirect path 
 +uh and in the interest of keeping this 
 +video short 
 +i'm just going to uh test the default 
 +behavior 
 +i'll do the rest of the work 
 +subsequently 
 +um so 
 +this is pretty straightforward we're 
 +just checking that a few things get 
 +plugged into a url 
 +it only gets complicated because of the 
 +use of this helper code 
 +and the need for mocking so let me show 
 +you 
 +how that works 
 +as before we are going to 
 +want to create a new 
 +test method which i'll put below the 
 +existing test methods 
 +we'll just say test get both url 
 +method returns void 
 +public function test 
 +get bulk url void 
 +all right so when you're doing mocking 
 +you sometimes have to kind of build 
 +things in an inside out way 
 +because what we have in this code is 
 +two different uh helper objects that are 
 +being retrieved from a container object 
 +there are a variety of ways we can 
 +approach this i'm just going to do this 
 +the most brute force way possible make 
 +two fake helpers that do fake work and a 
 +fake 
 +container that returns the fake helpers 
 +so i have to build the helpers first 
 +so that i can then put them inside the 
 +container 
 +so first of all let's just create our 
 +url helper 
 +and the way that you do this is there is 
 +
 +method that you inherit from the base 
 +test case class called get mock builder 
 +which uh gives you an object that can 
 +build mocks 
 +so i say this get mock builder 
 +and then i pass it the class name of the 
 +class that i want 
 +to mock in this case it's going to be 
 +laminas 
 +view helper url 
 +which is the view helper 
 +the url view helper class 
 +then uh it's usually a good idea to 
 +call disable original constructor 
 +which just tells the mock builder to 
 +build the mock without calling 
 +the real constructor of the real object 
 +which you rarely want to do when you're 
 +mocking 
 +and then i say get mock and that's it 
 +that's going to give me a mock url 
 +object which has the interface of the 
 +url view helper 
 +but doesn't actually do anything so 
 +there won't be any unexpected side 
 +effects of the test 
 +now uh when you're building mocks one of 
 +the most important things 
 +is to set up some expectations on those 
 +mocks 
 +uh as i said making assertions is the 
 +key to 
 +successful tests and when you're 
 +building mock objects 
 +you actually set up their assertions 
 +about what they are going to do or how 
 +they are going to be used 
 +which is how you verify that your test 
 +is behaving correctly 
 +so uh in the case of this 
 +particular object we're just expecting 
 +the object to get invoked so that it can 
 +look up a route 
 +uh and that's going to use the magic 
 +method underscore underscore invoke 
 +so let me show you what this looks like 
 +to set up an expectation 
 +uh you just call the expect method on 
 +the mock object so i'm going to say 
 +url expects and the expect method 
 +expects a parameter about the frequency 
 +or 
 +type of access of the method that you're 
 +going to be expecting 
 +so in this instance i expect 
 +my my code to call this method 
 +once and only once so i can say 
 +this once which is a helpful helper 
 +method 
 +that tells us expect 
 +one call there are some other things you 
 +can do here 
 +so for example i could say this any 
 +which means that 
 +it expects zero or more calls to the 
 +method 
 +it's really flexible but not very 
 +specific 
 +i could also say this exactly one 
 +which means that this will be called 
 +exactly once it's the same 
 +as saying this once 
 +uh just less concise so i'm going to put 
 +this back we expect 
 +this method to be called once 
 +now we actually need to tell it what 
 +method we're expecting so we 
 +uh arrow to method underscore underscore 
 +invoke now 
 +we need to tell the method how it's 
 +going to be called 
 +and the very nice with method 
 +is useful for this with allows you to 
 +make assertions 
 +about all the parameters of the method 
 +that you're expecting so in this case 
 +we expect invoke to be called with 
 +the name of a route cart dash view 
 +export 
 +so i can just say this equal to 
 +cart do export 
 +so we're now expecting that the method 
 +will be called with the 
 +parameter cart do export and if 
 +my invoke method had multiple parameters 
 +i could put a whole series of assertions 
 +here 
 +one for each parameter but in this case 
 +there's only one so i'm done 
 +and finally i can close by 
 +saying what i want my mock object to do 
 +when 
 +this method is called in this case i 
 +want it to 
 +return a value which i'm going to say is 
 +slash cart 
 +slash export so 
 +this is going to simulate what the real 
 +view helper does 
 +it takes a route name and it returns a 
 +path but 
 +it's going to do that without actually 
 +using any of that code because we don'
 +want to be distracted 
 +by the internals of that view helper it 
 +should have its own test 
 +that's somebody else's concern we just 
 +want to 
 +plug in some behavior to test the 
 +subject of our test 
 +the export class now 
 +this whole mocking syntax can take some 
 +getting used to 
 +uh it uses what's called a blue-ins 
 +interface where 
 +methods return objects which expose 
 +additional methods 
 +um it doesn't necessarily look like a 
 +lot of php code that you've written 
 +but it leaves really nicely because i 
 +can actually say 
 +url expects once method invoke 
 +with the value equal to cart do export 
 +will return the value 
 +cart view export so this is actually 
 +quite readable 
 +in terms of expressing what the test is 
 +doing 
 +so that's in any case one uh method down 
 +but now we need to mock the other helper 
 +which is server url 
 +and again we're going to follow a very 
 +similar pattern here 
 +uh this get mock builder 
 +[Music] 
 +helper server url 
 +class disable 
 +original constructor 
 +get mock and again we need to set up our 
 +expectations 
 +for the server url helper this 
 +expect once 
 +the invoke method 
 +with and in this case 
 +we're going to expect the input of the 
 +server url helper to be 
 +the output of the url helper so 
 +we expect this to be equal to slash cart 
 +slash view export because that is what 
 +the url helper 
 +was defined to return up above 
 +and we will say this will return 
 +a value 
 +of http colon slash 
 +localhost slash cart slash view export 
 +and again this is all fake but it's 
 +simulating 
 +what the real view helper would do 
 +which is taking a path and prepend 
 +a full host name to it 
 +so we're getting them but 
 +we now have our helpers only we need to 
 +also simulate 
 +the php renderer container that 
 +the code is pulling these plugins out of 
 +so again it's a very similar pattern 
 +view equals this get mock builder 
 +this time we're going to be mocking 
 +laminas view 
 +renderer php renderer 
 +and of course we know this part by 
 +looking at the type hints in the 
 +function that we're testing which 
 +specified that the view object was 
 +one of these php renderers that's how 
 +you can sort of get into this and then 
 +of course if we weren't already familiar 
 +with 
 +how the php renderer works we might have 
 +to look at that code and get a better 
 +understanding of it in order to mock it 
 +correctly 
 +but in this instance i know how it works 
 +and so i don't want to go too deep into 
 +those weeds 
 +but anyway for mocking this we want to 
 +disable the original constructor 
 +as with all the others and then get them 
 +on 
 +object now 
 +the behavior we want to mock here is a 
 +little bit more complicated 
 +than the behavior that we are mocking 
 +for the individual plug-ins because in 
 +this instance 
 +the same method is getting called twice 
 +with different parameters 
 +and returning different values because 
 +first the plug-in method is called to 
 +get the 
 +url of rather the server url helper and 
 +then it's called again to get the url 
 +helper 
 +fortunately uh the framework gives us 
 +some helper methods that make this 
 +quite easy to do so we're gonna say you 
 +expect 
 +and this time we're going to use that 
 +exactly specifier 
 +i mentioned earlier we expect exactly 
 +two calls to the method plug-in 
 +because two plug-ins are going to get 
 +pulled out of here 
 +by the calling code 
 +and since we're going to have different 
 +things happening instead of using with 
 +and will we use a different pair of 
 +methods 
 +first we're going to say with 
 +consecutive 
 +which allows us to specify an array of 
 +parameters for a series of calls 
 +so our first call we expect is going to 
 +be 
 +server url and our second call is going 
 +to be just plain 
 +url so now we've defined two 
 +inputs then we can use 
 +will return on consecutive calls 
 +to define multiple return values 
 +so on the first call we want to get back 
 +the server url 
 +mock that we defined above and on the 
 +second we want to get back the url mod 
 +and that's it we set up our entire 
 +uh mock test environment we've created 
 +two mock view helpers that do very 
 +specific things 
 +and we've put them inside a mock 
 +container that expects 
 +a very specific thing of course 
 +this isn't entirely ideal because 
 +this test is actually capturing some 
 +details that don't matter from an 
 +implementation perspective 
 +for example the order in which the two 
 +plugins are pulled from 
 +the uh container doesn'
 +impact the functionality of the code if 
 +we reverse the order in which the 
 +plugins were retrieved we would break 
 +this test 
 +even though it wouldn't impact the 
 +functionality that's not ideal 
 +but in the instance in in the interest 
 +of convenience 
 +we live with some of these sub-optimal 
 +things 
 +but it's it's the kind of thing you 
 +might want to keep in mind during your 
 +test design can we make this more 
 +general 
 +um but this is a really good way to 
 +demonstrate mocks 
 +so i'm doing it this way so 
 +with all of that done um we now just 
 +need to make an assertion 
 +so 
 +what i'm going to do is i'm going to 
 +assert that i'm going to get some kind 
 +of a url 
 +i i'll figure out what that url is going 
 +to be 
 +as i work through it all so 
 +for this test i'm just going to test the 
 +default configuration 
 +uh i'm not going to worry about passing 
 +in 
 +a special configuration yet um 
 +i would want to do that later to test 
 +that other case i talked about 
 +uh based on whether or not we're working 
 +with a redirecting url but 
 +for this test we'll just test the 
 +default behavior so i can directly call 
 +getbulk url on a default export object 
 +retrieve from the getexport helper i 
 +need to pass it as the first parameter 
 +my mock view object 
 +and the second parameter is the name of 
 +the export method i'm just going to use 
 +that placeholder of 
 +foo and the third parameter is 
 +an array of identifiers i'm just going 
 +to 
 +arbitrarily give that one two three 
 +so i happen to know 
 +uh what i should expect here so first of 
 +all 
 +because of passing 
 +because of the way i set up my mocks i 
 +know that the url i get is going to 
 +start with 
 +localhost slash cart slash view export 
 +because i've set up 
 +my mock server url method to return that 
 +and i'm also testing through my other 
 +mocks 
 +that you know this particular route is 
 +being requested of the url helper 
 +and so forth but if i get a url starting 
 +with 
 +localhost slash cart view export out of 
 +my get bulk url 
 +call i can be quite confident that my 
 +mocks were used correctly and that all 
 +of these assertions were met 
 +if anything was wrong along the way the 
 +test would have failed before reaching 
 +this point 
 +i also know based on reviewing the code 
 +earlier that i'm going to get an 
 +f equals foo parameter on here um 
 +because i passed in an export method of 
 +foo 
 +the code is adding that and then i'm 
 +also going to 
 +get three ids added onto here 
 +uh and they're going to be a little ugly 
 +because they're url 
 +encoded but that's going to look like i 
 +percent 5b percent 5d for those url 
 +url encoded brackets i'm going to have 
 +one and two 
 +and three 
 +and that's it if i've typed all of this 
 +correctly 
 +i'm setting up some mocks i'm making an 
 +assertion my test should pass 
 +let's see if i did it correctly 
 +so if i go back to the terminal i can 
 +see that when my test 
 +passed earlier i had 12 tests and 21 
 +assertions 
 +i should now expect if i run my tests 
 +and they pass that these numbers will go 
 +up i should have 13 tests 
 +and significantly more assertions 
 +because all of those setups in my mocs 
 +are making more assertions let's see 
 +what we get 
 +we get a test failure because i made a 
 +typo 
 +so let me go back and fix that 
 +i if i have it i type https here 
 +when in fact i'm returning http here 
 +and i'll just pretend that i did that as 
 +an exercise 
 +so now if i run the test again this time 
 +it passes 
 +as i predicted i have 13 tests 
 +more assertions we're up to 25 now 
 +and so that's uh a lot of the basics of 
 +what you need to see 
 +to understand unit testing if you can 
 +make assertions and you can mock 
 +objects you're well on your way 
 +to writing all kinds of useful tests 
 +but let's not just end there because 
 +i happen to notice while i was writing 
 +these tests 
 +that some of the code in the export 
 +class is not quite as efficient as it 
 +could be 
 +so let's benefit from these tests by 
 +optimizing the code 
 +and then using the test to confirm that 
 +we didn't break anything 
 +so let me show you the couple of things 
 +that i noticed 
 +first of all in get bulk url 
 +we create an empty params array and then 
 +we append the value to it there's no 
 +reason to do this in two steps we can 
 +just 
 +initialize the array with the value 
 +and then we've trimmed off a whole line 
 +of code that we didn't need to have 
 +similarly down here 
 +when we were looking at get bulk export 
 +type 
 +this entire if statement is really not 
 +necessary 
 +because now that we have the lovely null 
 +coalescing operator 
 +we don't need to do all these is set 
 +checks anymore we can 
 +reduce this entire function to a single 
 +return statement 
 +with no ifs at all so i'll just 
 +consolidate these comments 
 +and then i can change this to just 
 +return 
 +the format specific type if it's defined 
 +otherwise null coalesce to the global 
 +default type 
 +otherwise null coalesce to the default 
 +of link 
 +we've just changed together the options 
 +in order of priority 
 +much much more readable code once you 
 +understand 
 +null coalescing we got rid of several 
 +lines of unnecessary code 
 +so now i just run my tests and they 
 +should still pass 
 +and they do so i've just refactored with 
 +confidence 
 +and that's all i have for now uh next 
 +time we will look at some integration 
 +testing uh in the meantime as i said i 
 +really encourage you 
 +to read a little more about php unit 
 +maybe consider 
 +uh trying to expand bluefind's test i'd 
 +love to get some pull requests 
 +adding new tests uh and if you have any 
 +questions or problems along the way 
 +just reach out to the usual support 
 +channels and 
 +someone will help you out happy testing 
 +talk to you next time 
 ---- struct data ---- ---- struct data ----
 +properties.Page Owner : 
 ---- ----
  
videos/testing_2.1620404601.txt.gz · Last modified: 2021/05/07 16:23 by demiankatz