 Mae'r unig. It's a big last strike on the side of the screen. I hope that doesn't, I don't think that's going to cause any problems, but it does make my slides a little bit off-center. So I'm Oliver Davis, OP Davis, mostly on internet on Drupal.org, GitHub, Twitter etc. I'm a senior Drupal developer for Company Called Up Innovation. I'm a core contributor mental, which is a module maintainer etc, so pretty active in the Drupal space. So let's start by asking this question, why would you want to write tests? The first thing is, you want to write better code, so essentially you want to write code with less bugs, or ideally no bugs at all. TD also means we can also write less code, so by writing, if you take the TDD approach, you write out tests first, tests first, then only write enough code to satisfy the criteria of the test. So in most cases that means a lot of slender code base. Peter mind, so if you've got a test suite and you write the module, you can be confident that because your tests are all green, your tests are all passing, if you don't need to add more functionality or refactor system functionality, you haven't broken anything, which is quite a large piece of mind. To ensure consistency, again, if we're adding functionality, refactoring, removing legacy code, we want to ensure that our results are the same. Also it's a Drupal 8 core requirement, so if people aren't familiar, there are a number of gates for Drupal 8 core development that need to go through, hence it's engaged in all of your code passing the core, one of which is automated testing. Also why not test? This I think is the main reason why people are not on their heads. The main reason why people don't do tests, I think aside from maybe the knowledge gap of not knowing how or what to do, the main thing I hear is we don't have time to do tests, we don't have the budget to do tests. If I'm doing a proposal for some work and we're coming in over budget, first thing, automated testing and accessibility in one of the two things to get taken straight away. Also, if you've been allocated a task, let's say you have two hours to complete this task, you're on a time limit there and you don't have the time to write the test. I'd sort of not argue but try and quantify this a little bit to say that, essentially you're investing time, so you may be spending a small amount of time initially or an amount of time initially with the intention of saving more time later on, so if we say something, if we spend 10% of your budget doing time for testing, if that then saves 20% of time later on, that's a win, right? So, I try to think of it in that perspective rather than we just don't have the budget all the time. As I mentioned, there is a testing gate in the call. This is taken from the actual gates page on Drupal.org, essentially says new features should be accompanied by automated tests, self-explanatory. If the feature does not have an implementation, provide a test implementation, and bug fixes should be accompanied by changes to a test that demonstrate the bug, so if you're fixing a bug, you've to potentially put a test in to show the bug is fixed and what will come back to the page it comes from. Testing on Drupal, so there are potentially two ways we can do testing for this type of unit functional testing on Drupal. One is simple test, so it's based on this testing framework, this library from simple test.org. I don't know how, whether it's still used or whether it's outside of it. I think it's mainly Drupal specific at this point, I might be wrong. It's in D7 call, so it's a module there called simple test. I think the machine name is just testing, which is slightly confusing. It's been around the contrib since forever, as far as I can tell. It's been there since Drupal 5.6.4, I think 4, I saw it in the queue. They use their own file extension, at least in Drupal, so the star.test, essentially the PHP files, but they all have the .test extension. What seems to be the convention is to have all your test classes in one file, so if you're writing a module, you have one mymodule.test file, and you literally have classes one after the other, all in a single file. The other option to do is PHP init. This is a standalone PHP testing framework, so it's used with other projects like Laravel and Symphony. It is supported in Drupal 8 call, but it's not the default, so simple test is still the default in Drupal 8, although there is a shift towards that, I will get to in a moment. They use the more standard .php file extension, and you tend to have one test class per file. The project that I use, I do a project called the Drupal VM CLI, which I don't know if anybody has used the Symphony console application generating config files for Drupal VM. Recently I got done the route of running PHP init tests on that, so again, not Drupal specific, plug it to any PHP. There is a PHP init initiative, Drupal initiative, official one. This is the page you can find out about it. Essentially it's talking about moving Drupal 8 call tests to PHP init rather than simple test. The intention is to deprecate in simple test, and then remove it completely before it gets to Drupal 9. I found an issue that referenced a big chunk of tests, which would be converted on the 21st of last month. I don't know whether that was the case, but that was the thing I found. This is some of the text from it. As I said, the 21st was the date they were going to convert some of the tests, but there was a backwards compatibility layer, so that we could just pretty much change the base class that we were extending, change it from Drupal web test case to use the browser test case. The PHP init provides an implementation of it, and apparently it's also a script to ultimately convert test files. I haven't had to use it when I upgraded one module I'm going to take called overrideodeoptions to Drupal 8. It was initially done in simple test in D8, and I just moved it to PHP by literally changing the base class, so I haven't had to use that yet. The developers are encouraged to use browser test base instead of simple test as of 8.3, so pretty much now. Both systems will run in parallel on the condition of Drupal, so essentially until Drupal 9. I like this quote, the timeline of the deprecation of simple tests is under discussion, so it's still going to be there for the long run. Types of tests, so uni tests are the first ones to talk about. Essentially test PHP logic. You have a function or a class, you give it this, and you should be able to get that back out. There's no database interaction, so it's only testing PHPs running. Because of this, they're very quick to run, because there's very little overhead there. The other option is web test, so I'm going to mention web test base, this is the test we're referring to. These are testing functional updates, so they're actually in the background creating a brand new Drupal site based off the sound installation profile. Enabling the modules you turn to enable and actually essentially clicking around behind the scenes, capturing the output of it and running tests against that. Because of that, it's having to interact with the database, but also means they're quite slow to run because these things take time. Compared to the PHP test, we don't have to do that. Writing testable code, there's a principle called the single responsibility principle, which is more applied to object orientated code, but I'm sort of abstracting it out to functional procedural PHP code. Essentially it says that if you write in a class, the class only needs to do one thing. Therefore I'm studying that to say if you're writing a function, the function should also do one thing. I've seen similar things where we've got a module with a preprocess hook, and maybe it's got a 400 line preprocess hook, where it's split that out into separate functions that say do A, B, C and D. Do those as separate functions that are responsible for their piece. A is more readable, B is more testable, so you can test that piece of function out of the installation. Dry, don't repeat yourself, rather than do the same thing multiple times, abstract it to its own function, abstract it to its own method on the class, reuse it. Only have to test it in one place. Use dependency injection and code to interfaces. If you're in Tim's talk really, talk a little bit about dependency injection. This also means that if you have a dependency, you can use what's called mocks and stubs, and say that if you're injecting service A, I can mock that. You have not to inject the entire thing, so it makes your test faster, but also you can then test. You're not having to worry about how that works in your test. You're only worried about how you use it rather than actually what it does. Also, if you're working with data, you can also mock your data and pass it into your test as well, as well as your configuration. Test driven development, or TDD. This is the process. You start off by writing your test first, so you don't want any functional test first, then you run it, it's coming to file, it's coming to your red, there's nothing there. Maybe you're going to run a dribble get on a page that doesn't exist, you're going to get some 404, or you're going to do something that's going to fail, and that bit's quite important. If you write a test and the test passes straight away, you don't know how good that test is, so it's important to see it fail, first of all. You write code until the test passes, only enough code is needed to pass the test. Repeat, so keep going, keep adding more tests, keep writing more functionality. Then, when your test is driven, you can then refactor it, because you know what you're doing is working, and then you can change your code, you can refactor what you need to refactor, take away things that are not needed, but you've still then got the knowledge that your tests were passing previously, so you know what you're doing is still working. Only refactoring your test screen. How do you start writing tests? These examples are very dribble set and specific just because that's what I've been doing lately. So, in this case you've got a .info file, because dribble seven, and we're using the files, square brackets in text, and tell it to load our example .test file. So, this is only dribble seven specific, because dribble eights, you have it in its own class, it gets auto loaded by the namespace. Then, when you .test file, you write your own PHP class, you extend the base class that you need, so in this case we're doing a web test, so an actual browser test. You could also be extending a group of unit test case, we're doing unit tests, and the first thing we do is return, there's a static method called .getinfo, and that you just return back an array, standard array, you give your test a name, a description of what you're testing, and then a group, so you can group tests, you might have multiple, you'd very likely have multiple tests, so if you've got web tests and unit tests, for example, you put those into the same group and run them as a group. Following on from that, we start writing these methods, methods are just functions, and we start them, and this one just says test something, and because it started a test with a lower case T at the beginning, simple test, or PHP knows it's a test, that's how we can tell. And then within these we can start running various assertions, some assertions, just a way of checking something that's the right thing to think of it, so this is probably the most simple test ever, which is going to set the true is true, which obviously we'd expect to pass. One test method can have multiple assertions, so if you may be testing if a page existed, you might check the response code, first of all, to check it's a 200 response, and then check that the page title is X, and the content is Y, so it doesn't have to be one per function, there we go, yes. There's also a set-up method, so this essentially we're saying what, we're creating our world this way, I've heard that recently, so we're saying when we run our test, the test essentially run, so we'll just say, yeah, it starts off with a standard Drupal install behind the scenes, it stores a new version of the standard installation profile, nothing else, and in this situation we need to tell it essentially what to install, but I believe it ignores the dependencies, so I think you have to do this even if you're specifically depending on a module, and this is what the set-up method is for, so we're going to extend, sorry, override the set-up method, and then call its parents, because you have to do that, because if it's maybe this module, we can just pass it in as a string, if we're working on multiple modules, we can just pass them into the ray, as you can see, using the ray syntax to do that, and just anything else that's needed to run before each test method is run. Some of these things could be Drupal create user, if you need to have users installed, so override-adoptions is a good example of this, so that essentially means users with a role of X see this checkbox below that checkbox, or this input field for maybe the author, not the others, so we can create users and you pass them an array of permissions, so who's allowed to do what? Drupal log in, very standard logs in the user account, pass your account into it and it will log them in. Drupal create node, creates nodes, pass it an array, which load type you want, which field of energy you need, et cetera, Drupal log out, we'll see lots of people out, it's various other things we can do in there, these are the most common ones I've sort of got to use, and then we can start adding assertions. There's quite a long link there off to the Drupal API documentation. So these ones are fairly standard, we can do things like assert true and false. No, I'm not no equal, there is also one for not equal, most things tend to be positive and negative, so you can say if it's true or not true, it matches or it doesn't match. We can also assert the role text, so rather than the visible text on a page, we can actually dive into the HTML code behind it and say does the HTML include a div with this class or something of this ID. If we want to assert the response code we get a 200 response for an okay, we can use the assert response. Assert field, field by ID if we're Drupal specific, so again, we'll find options using these quite a lot, just to say is the field if somebody has permission to change the author, we can use the assert field or not assert not field to say is this user able to see this checkbox or this option and also assert title just to compare against the page title. How do we actually then start running tests? So we can do it through the UI, first of all, if you have a similar test module enabled you can go to the configuration screen there's an option there for testing and we start seeing this whole list of all the tests that are defined by each module by the group that we saw originally so you can see them all. Check the box at the bottom of the button at the bottom of the form we select to run them and we see something like this so it depends on what you're running run through each of the tasks and you start seeing feedback across the bottom so it's showing you feedback for each test that is run how many passes and how many exceptions and how many debug messages there are and at the end you get something like this so you take it back not the list page but the list of tests within what you've run you see a little summary that shows you how long the tests take to run it took 2 minutes and 19 seconds for the added menu module and then again you can split down each of these lists you can see exactly what what's run so this is what we want to see we want to see green we see a bit more description about what we've got so which group the test is in which file ran it what line it's on what method it's being run and also we can see for example this one how many steps for both a link you can click that actually loads up the site or screenshots of the site that it ran so if you're trying to debug a test you can either export it to flat files on disk or you can actually do it this way and browse around the site and go step by step and see what the output was for debugging the way I prefer to do it is from the command line so in there's a run-test.sh so it's a a bash script file although it's PHP so for Drupal 7 we can just run PHP scripts.sh and for Drupal 8 it's just sitting within the core directory so just run the same thing PHP core scripts or we can pass through various options so if you want our appers to have color we can also attach with color we can give the bugs output we can run tests like all tests or tests with a certain module or tests with a certain class or a certain file if you're doing PHP unit testing cool will come with a file called PHP unit XML and we set some of these options for you things like colors and which directories to open or we can just run it by running PHP unit so although that doesn't seem to have got the goal executable installed if you're running something like the Drupal Composer project that will include PHP unit as a dependency in which case it might be vendor bin PHP unit if you don't have that included in your path I have vendor bin in my path so I can just run PHP unit from the root directory so again we can just run PHP unit and run everything we can tell it a specific directory so if you want to run maybe a certain module a certain file in the module or if you want to run one specific test method you can use dutchass filter and then just give it the name of that method to run and it will just run that one particular a particular thing so that's how to do it I've got a couple of examples to look at so there's a module I wrote quite recently called the collection class module this is it collection underscore class and it adds if anyone is familiar or has used Laravel that uses collection class it's quite sensory and essentially rather than returning back an array it returns back a collection with collection items so it provides various helpful methods essentially for those items so rather than having to run things like any you then get a map method on the collection sort of like this actually again inspired by the way that Laravel was doing this with me to collect just a global function which you can pass an array into and then you can start running methods off that so all which are like just the items in the array counters back the count which are the keys of the array values for an example it's just again wrappers around mostly the array the array functions and this is part of at least the collection class itself so it's just a collection class we are using I am using the x-hole to load module if anyone is familiar with that so it does live within a Drupal under sort of collection class namespace just to provide it to prevent any class name collisions collections fairly generic we are implementing a few interfaces but don't worry much about that pass through our items and depending on whether we are passing through an array or another collection it does slightly different things and from there we can start running methods on it so it's a string we will return it back as a JSON array to get all count first to return to the first item we can run tests or conditionals and say is this thing empty so essentially we are running that on the empty object so test for this, this one is built fully TDD upfront so within the setup method we are going to make two collections the first one is going to pass through three strings and the second one is more the multidimensional thing so pass it back an array of arrays and each array has a title and status so these are primarily used for using array filter to filter out the one with the zero value I'm not sure if that's an example but that's what we're there for and again we just want to make sure we run the appearance method any time we if you extend a class and then you make a method with the same name in most cases you want to extend make sure you run the parent it's true for constructors and also true for this particular setup method and then we start doing things like this so we're going to test the collect function so that global function may call collect is going to assert that it's equal to and we're going to get the class for the first collection and then that should be again because it's using the X auto module to give it the namespace Drupal slash collection underscore class slash collection so X auto load 20 is not where provides Drupal 8 namespacing and auto loading so the same way that Drupal 8 auto load classes X auto load does for Drupal 7 so you can use like PSR0 and PSR4 auto loading and Drupal 7 essentially so that's that's why it's Drupal slash module name slash collection so that's just testing that we can test all so all returns back is actually the values of the array so in this case we can use it so it equals to do that and what we're expected to happen is if I get the first collection so because it was in the setup method and assigned to a property I can get the first collection by doing this first collection we can run the all method on it and we're just basically saying that should be returning an array because that's what we're passing to we can do test count so we know what we're expected to do three items in the array so we can test that still in the case test merge so there's the opportunity to merge two collections so in this case you've got a collection A, B and C another collection of D and F essentially runs an array merge on the two put them together so in this case we're saying that if we take the first collection and merge the second collection and get the results back out of the array it should combine the two values together so the resulting should be A, B, C, D and F so this actually says at the top the test run ran in zero seconds so it's gone through let's see 22 passes zero fails in zero seconds so because these are all we're only testing PHP logic we're saying that given this class I'm going to give it this array and expect this back out again we're not doing any database actual we're not touching Drupal at all or we're doing a test in that function so it's super do the quick you can see these are the tests that are being run you get this other output and if you don't specify a message so you can do things like a search equal A and B and if you do that and that's fine and it uses these fairly standard messages and pass your own message in I think these are using most of the default ones so this was completely module all it does is run these things fully clearly driven you do test only another example we can look at is another module I did called ToggleOptionFields like an idea what it does by the picture so again this is new module on Drupal.org ToggleOptionFields essentially add the button to the top of the node form that says showOptionFields so this came from one of our client projects we did fairly recently and it had quite a long node form and this one can see I think they just said they wanted a shorter node form that was the requirement and then I decided what we would do was show the required ones initially and then re-add the optional ones later if they could set the button to do so it said you do this using some ToggleFields access there is an alterhook so I assume it happens quite a lot with clients this was our initial scope and then they said oh actually we want this field that's not managed optional is optional field to be shown even though it's not required so in this particular case there's an alterhook in the module that we can do from another module so this is ToggleOptionFields and it uses a mixture of our web tests so in this example we're looping through the available elements out of the form and there's a function that we run that says is this a field element we pass it the element name and then if it's not it just returns early otherwise it carries on down the chain stores the element form element as an array by reference so we can modify it and then if the particular element is in our OverriddenFields list we're going to check its access value and then otherwise we're going to check the access value or yeah on the access if the field is not required to slow access to hide it so the OverriddenHook means that we can actually say whether things are true or false so we're just going to return back the actual value so in this example what do we want to test so function in the test are all the correct fields shown and hidden and we're going to unit test then to check whether the field names are true about the correct results so the very first thing that we show is this a field then that's unit test that's fine so this is the function that we're going to test so we pass in the element name so here we're going to say that if it's the body or it's the language it's a field otherwise we're going to do a substring based on the name and we're going to get the first six characters and if it's equal to field underscore it's a field so how do you go about testing this we're going to use a search through for most of it and we're going to pass it in in the case field underscore tags so because it starts with field underscore and likewise the body because we have that in sight later we can verify that that still passes even though it doesn't start with field underscore and then we can also test that the opposite is true so we're testing that we're giving it a value which is false in this case so the title is A not a field, B not a field because it's not start with field underscore so it's not field but it's not within our exception exception is probably the right one so this is what we get so three passes in again in zero seconds because unit tests don't need to touch the database at all passing method name element name into the method checking the output we do need some web tests so we'll actually do the dribble and check that the things are actually working so we're going to need the setup method for that in this case we're going to create a user and the user can create articles and pages and then we're going to log that user in so I'm just netting these together rather than separating them out into their own variables we'll start off by using variable set so the way that this currently works as we pass an array it's a variable in the variables table of which lots of times we want to be affected by this so in our client implementation there's only a need for one particular content type so we added the option there so we can do that using variable set we'll see what you normally and there's other little helper methods such as refresh variables that clears out some caches and things by the scenes so we're saying we're only going to affect articles so we're going to assert the first assertion so we've actually read the custom assertion in this case so rather than having to rewrite the same test several times again don't repeat yourself idea we're going to wrap this in a custom assertion so again it's just a method on the test class again this one's private we just call it assert in this case tag fields are not hidden and that's just a wrapper around assert field by name we're going to pass it with field tags we have to include the square brackets, the language so UND is undefined we expect that to be known in this case we are passing a particular message back when we view the list of results we see our own particular message we'll see field tags visible rather than feel the actual automatic generated string so then we'll see them doing the same thing and essentially what we can do is use a method called dribble get so dribble get forms a get request so it's actually a text of that or a notes page if you type it into the URL so in this case because we've got missions to view missions to create articles we can see this page so we could have done an assert response here to check it was a 200 but we didn't do that in this case and we're just going to run three customer assertions so you want to check that the button is found you want to check the button it's not found oh, high option of fields are not found it's the two buttons, the button changes depending on whether or not you've got it visible or not, it will say show optional fields, high option of fields depending on what state you're in we also then want to see if the tag field is hidden what we also want to do is do a post request so essentially click that button and then do the same thing testing both sides so in this case dribble post, you give it the path that you want to go to which is loadout article passing your data through in this case we don't need to give it any essentially give it a title of something to click so our button is show optional fields this is our output so all green this is actually run 68 passes including the ones that extended from the base class and these have run in about 24 seconds so bit more than 0 seconds that you test had but keep in mind it's stalling dribble potentially a right test against it you can see that with the last two there hand optional fields button not found test methods will be passed through rather than just say something something is false because it allows it to put a bit more descriptive so a few takeaways it's fair to say that testing could produce better quality code a bit of a few times where I've written a module or maybe on work time and everything works fine and then when I've written some tests on personal time maybe I've given the wrong permission or I've written access to notes rather than minister content or vice versa I never remember which one was the right one again, writing tests is an investment so rather than saying we don't have time or we don't have a budget to do this trying to think of that mindset of spending a small amount of time now sometimes you can say a long amount a large amount of time in the future and it's okay to start small and then you don't have to start with test driven you don't have to start with test first and then write code in this case I've been working on sites and I've been doing fixing a bug and just for the sake of fixing that bug if I write a test to make it look like the bug doesn't come back start off with one or two small tests and show you build the test test 3 all the time I'm just going to do a quick plug for the Drupal Camp Bristol again so we're third year for the Drupal Camp Bristol this year in three years of June July business day, sprints and sessions any questions? yes we just said it's true I think that situation when I wrote the morning I was working on an IP address group and I was writing a test and that test started to show some bugs that I never thought about before and it was really nice but there's one thing to wait about the schema for the company test are very peculiar that thing because in the name of the module that's got the schema the test for that just the schema just close up and this is something that if you enable the module use the Drupal as normal you're never going to have the problem and I didn't know this I've had the same thing so I was writing a module maybe one called copyright block one of the ones that I maintain wrote it all and it worked fine and then tried to write the test for it and then hit the same error essentially saying your configuration schema is not valid or something of that effect it completely worked for me using the module testing the module it worked perfectly so I've hit the same problem I've not got my head completely around the configuration schema and such and Drupal yet and I think that's maybe where the problem is I can quite happily build stuff on the front end of the site but how does that what we need to define in that config file in order for it to match I haven't quite got that bit around yet but yeah if that's the case if we go on the TDD approach maybe you probably hit that error sooner rather than writing the whole module and then finding the error at the end so that's a good one bringing the error to the surface sooner to fix the problem the data that you used that you passed through your assertions so that you have kind of multiple types of data yeah to a degree so I was doing something quite recently and it was sent to you the result was you were trying to create Drupal essentially and the client was given an XML feed quite a long XML feed of data and we had to pass that and create nodes out of it essentially so that I did in a test driven way because I wanted to assume that the data that I was given was being A in the nodes were being created and B the fields were being mapped correctly in certain situations field on the XML field A and B were concatenated to make C so I did that with a unit test based approach in that situation rather than relying on A in external URL to be visible and having 1000 nodes or other labels at the given time I made a subset of that a smaller XML file essentially maybe with two or three and then it was property listing site so put them on to one, two, three fake street sentence reference and then just because I knew what the data was going into it I could then assert for the right data coming out so I could say that given I know that it's going to be one, two, three, fake street but no, then on the other side the title has to be I could use a certain title to then say one, two, three, fake street so the advantage of that word I could actually test it so it sends you a sort of mocking or stubbing of the data and there was a function within the module code itself that said something like import nodes from XML and that optionally took an XML argument which all there was an option there but it was null by default and then if that was null it went to the API but if it wasn't null it didn't because it already had the XML so within the test I could just say get the XML from this file do fileload contents or whatever and pass that and then give that to the file so this slight changed the way I had to write the code for it to work but the advantages were again didn't need to actually write a third part to run run the test locally or in full control the data that I'm testing against so it's not going to change and yeah I could do it with maybe two, three, five nodes rather than a thousand so I could run those tests quickly within maybe a few minutes but what it meant was when it gave it to the client and he ran it on his productions here and again a thousand let's say a node through I'm confident that my mapping's worked and it's one to the right field and A and B we can count it to make C and then everything else so does that answer your question? It does, yeah. It's a problem with your integration so have you managed the data? Yeah again we're talking a bit about mocks and stubs there again so it's sort of similar but I think in PHP that's a little bit easier but in this case it was a bit easier to do so that's similar