 Hello everybody. Thank you for coming to my talk on two of my favourite subjects and automated testing, testing and development. My name is Oliver Davies, OP Davies everywhere pretty much online including Drupal.org. The slides are already online if you want to look at them as we go or later on the second half is quite code heavy so it's there if you want to look at it later on. I'm a software developer, I'm a full stack development consultant and I'm an open source maintainer so I develop and I consult on a lot of Drupal projects for clients. My day job was using Drupal and I maintain quite a few Drupal modules and themes so I've got quite an interesting perspective on this coming from a few different angles. This is a screenshot of quite an old tweet. This is from Tim Millwood, this is February 2012 which seems like what it is a long time ago. I realised the other day and this says anybody want to maintain or comment in this module could override node options. I said yes and then in 2012 I became a maintainer of this module. So this is how the project page looked back then, the UC stats looked like. It was used on just over 9,000 sites back then. There were Drupal 5, 6 and 7 versions I believe and then currently looking at the page a couple of days ago it's currently used on just over 38,000 websites which is quite a lot of websites. A lot of them are Drupal 8 and Drupal 9 but also if you look at the overall installations across all the projects it's going on 173 which out of about 50,000 modules and that at all. So there were some existing tests. I took over this module and those were really critical to prevent regressions as I'm doing new releases and bug fixes and refactors of this module. So why write tests? Peace of mind I don't want to break 38,000 websites when I do an update. I'm sure you guys don't want that either. Also I don't want to prevent regressions either in the module or in client code of my own projects as well. I want to be able to catch bugs earlier. So if I can find a bug in a test and fix it there rather than it going on to production and then having a client find it or have a failure of a CI pipeline or something I want to be able to fix it right there and then test it and allow me to do that. Usually you're doing TDD, you write less code. You only write enough code to make your test pass. Tests are there to tell you when to stop. If you don't have tests and you're not doing TDD then you'll just carry on writing code and you'll end up with a lot of code you maybe don't need. It acts as documentation. So normal documentation in markdown files or Word documents or whatever can become out of date and be stale. Tests can't do that because you run them very, very regularly and if something comes out of date then the test will fail. The Drupal core requirement, so if you want to contribute something to Drupal core you need to have a test with it and this is just more important as Drupal core is speeding up as we know we get new releases now every big releases every 12 months. Modules support multiple versions. The current overriding options module supports 8, 9 and 10 in the same version and this is just going to become even more important as we hear about automatic updates and core updating by itself. So a little bit of history of testing in Drupal. In Drupal 7 we had a simple test module that was included as part of core. When Drupal 8 started with the added PHP unit which is really the de facto testing framework in PHP still is. We also passed these out which is very popular but I still think PHP unit is still the default and the de facto version. It then became the default for us in Drupal 8 and 9 and then in 9 a simple test is gone. It's in contrib. You can still install it if you want to but it's more there's a transitional thing to move to PHP unit. So how do you write PHP unit tests for Drupal? Everything is a test, each test is a test class. They also have a PHP dot PHP extension. You have tests per module so for each module has its own test directory. You're not writing tests at a project at an application level. You're writing them per module. There's a separate Drupal slash tests namespace. So if you're used to using some Drupal slash your module name, you'll do the same just with tests before it. And then because of PSR for water loading, the class name must match par name, the namespace must match your directory structure and one test class per feature. So very similar to writing controllers and other classes that use PSR4. So three parts to a test. We have a range, act to search. So first of all we set up what we need to make our test work. We then perform some sort of action and then we make assertions based on it. So given we've got these things, this thing happened, does our site now look like this. You can also mirror this to given when then. People used to seeing some user stories and said this criteria where you've got given I'm logged into this user when I do this thing then that should happen. Some things to test. Here's some examples from projects I've worked on before. Tracing nodes with data from an API. Talk more about that in a minute. Calculating attendance figures from an event. We built an e-commerce site where the product was the event and people attended by buying a place on it. So we calculated attendees by who bought the product. Determining if an event is purchasable. Same project with date ranges and if it was full and things you might not always be able to purchase a place on an event. Promotions and coupons for new users. If you sign up you get two or three coupons to go on an event or to do something. You want to clone events. So this is a very event-driven project actually. We're to clone events so we want to have one event and then we want to duplicate it again the next month or the next year or something. I want to send private messages to attendees. So we did that using a private message module. So we wanted to queue them because some of these events had a lot of attendees on them. So we wanted to queue them so we don't overload our web server and send 500 e-prior messages in one go. This is another old tweet. This is from Matt Stauffer, or the tweet of Matt Stauffer's quote, which I liked. Test the thing that you lose your job before it breaks. So I want to have confidence that if I'm finishing the end of the day or I'm going on leave for a week, or I'm finishing a project, or I'm pushing an overnight options update, I don't want to worry about everything breaking, or comes down to this sort of confidence and being able to be happy that things and common work is expected. So what does a test look like? As you said it's a PHP class because we're doing a PHP test. It's in our Drupal test namespace. We're going to use in this case a browser test base class. A number of these different types of tests we can use in Drupal, and there's various base classes we can extend. So this one's browser test base. We'll talk more about that in a minute. And then we've got a test method. So it's a function or a method within our class that we can do. So this one just has test something, and then we're going to make an assertion that false is true, which is obviously going to fail. So it breaks into our normal... That's how you'd write a normal PHP class in a control.org. Not too simple. There's different ways of writing these test methods. You could use the camel case, the suggested way of... Everything's to start with the word test. So we can test something as in uppercase s something. Convention is to use snake cases for camel names. Snake cases for test names. Snake case for test names just because it's more readable. I quite like this convention. I use it quite a lot. And then you can also drop the test prefix to get completely and use an annotation, so at test comment. So usually I'll use the third of these options, but you'll see different variations of it in different projects. As I said, there's different types of tests. So it's not just unit tests that are available to us, which is common. So I think people think it's just unit testing. It's not. We have these four different types of Drupal that we can use. Functional tests, which are also called web test browser tests, feature tests, that actually make tests against endpoints and check in the results and things. So make sure you're actually looking at the responses you're getting from your code. You're going to run kernel tests, which is also integration level tests, and then unit tests, which is what we'd expect for testing logic and things. So function test, test end-to-end functionality. So you can say go to this page, do we get the right response code, do we have the right text return mark, does this match the HTML that we expect to get back? So it's very user interface sort of level testing, which interacts with our database. So we're not mocking anything in this type. We're actually using real database. It's going to be like a MySQL database, a SQL-like database that's separate to your normal database. So you don't have to worry about your things being deleted when you do a test. It's going to complete Drupal installation behind the scenes. You can test it in profile. So your data is safe. Because they're doing all this behind the scenes, they're a bit slower to run, because they're having to do all this setup on this bootstrapping behind the scenes. There's variations we're running them with or without JavaScript. Integration tests are a level lower. So we can test modules. We can test our service container. We can make sure our service is run properly. We can do things against an actual database. So we're still using a real database for integration tests, for kernel tests. It's still doing a Drupal bootstrap, but it's doing a more minimal version of it. So we've got a bit more setup to do, just to get them to run, but they are faster, because it's going to do less for the start of the box. The unit testing. This is very PHP logic level testing. This is a typical calculator thing. I put 2 plus 2 equals 4, that type of logic. So not testing service container or databases. We're testing very small, isolated bits of logic. These are super fast to run, because they're just testing logic things. They're not retouching. So we can't do a thing with a database. If you're going to need a database, you need to mock everything. So you're saying this is a fake version of this thing that we need to use. On the downside, they can become quite tightly coupled, because you're mocking everything. Sometimes you can just end up testing your mocks. I've done this before, and I've written the whole thing, and then gone, I'm just testing what that mock does. I'm not actually getting any value from this test at all. If you write very tightly coupled tests, they can be difficult to refactor. If you write tests that say, this thing gets called three times and returns this. If you refactor that later on, it might not return it three times, or it might not get called three times anymore. It still works, but your test is going to fail, because you've been very, very specific on how it works. So how do you run the test? There is a run test.sh script in call. I rarely ever really use it, but it's there. So it's sort of a wrapper around some of the underlying plans that we can run. It's a PHP file, even though it's a .sh file, so you can run it with PHP filing. There are different flags that we can pass to it, like all if you want to run all your tests. You might want to run the test in a specific module or in a certain class. We can specify that as part of that. This is one I'm doing in one of my Docker examples that are on GitHub, specifying you want to run tests within the example module. This is SQL Lite database. So we're going to put the SQL Lite database in this file, so that's where it can run quickly. And then because it's Docker-based environment, you can also tell it where our web server is. So we can make requests and then look at them and get it back. Because it's on Docker, it's not all in localhost. They're all named differently based on the service name. So this one's called web. That's the other test will fail. So here's an example. If I run that, this is what we'd see. We can just run. This is running against the example module project. So we're going to run this example page test. We can see when it ran. Let me get a little summary at the end. So we can see what tests it ran and very importantly whether they failed or not. So this one passes. I hope it would. Usually I'll just run the page per unit executable itself. So I need to specify still the simple test base URL variable that tells it where our site is. So I need to set that first and then I can call the vendor bin page P unit executable. There's a page P unit XML filing call in the call folder. So I do specify dash C, which just tells it where our configuration file is and then any pass the test if you want to run. And we see something similar. You can see what it's run. We get a little dot whether it's passed. If it fails, you get an F. If it's an area, you get an E. If it passes, you get a dot. It's not just things on the screen. And again, we'll see how many tests it ran and how many paths and how many failed. So we can create our own page P unit XML file. As I've said, there is one in call that we can use. And it's just a file that configures how a page P unit runs. So we can copy the one out of call. We've got an XML.dist file which is our default version, our standard version. And we can then copy it to make changes to it as we want to. So this is a very Drupal core focused slide. If I was doing this on actuals of application or for a project, I can make my own version of this file in the root of the project and make my changes to it there. But this is a very core example. So things we want to change are base URL. Again, so it might be local host. On a Docker example, it might be web or whatever your container is named. We need to give it our database. So it could be my SQL database. It could be the same database as your application is using with a different prefix. I like to use an SQLI database. And then you just create it, populate it, throw it away afterwards. And if you're doing functional tests, you can have it save the code and screen, sorry, the screenshots of what did and save them in a file in a directory called browser test output directory. So you can actually go back and see them afterwards, which is really helpful for debugging. Something else I use quite a lot is stop on failure. I set that to true. So whenever a test fails, it just stops. So rather than running all the rest of the tests, I just want to know there in there that something failed. So an example. So on a project a few years ago, we had to do an integration with this service called Ruby. So recruitment management solution, people post job address and things on it. One of the things the client wanted us to do was have those job ads appear on the website as well as the other places they had them. So our spec was they wanted to create jobs in their UI and we wanted them to appear in Drupal on the website. The application URL is something that came into us. They would apply to another system, sort of the middle part of this equation. We take the domain, we take the role ID that they'd set and then there's some other parameters and things like where the job would come from etc. Jobs were linked to offices so they passed to tell us which office it was located in as well and there was a certain number of days that this job was active for. They also wanted to specify the path so it would be like mysite.com slash something and they passed it to that as well. So this is how it looked. We had the broadband stuff on the left. They send us an X and L we made an endpoint, a web again point available and they would publish the data there in Drupal. We would do our stuff there, create the node, people would click it, they would then see the application URL, click it, make their application in the screen. So we made a route that allowed us to accept data from the XML. We added a system user, so just a Drupal user that only had access to do this one thing, just to keep things less insecure. So this is the amount of time and the number of days it had to be visible for and we got the branch name, the locations, the attendance, what is it, plain text and XML and we would link them to entity reference because we had offices as a content type as well. And then the URA says that they'd pass us so we'd map that to the path. So this is what the data came in looked like in a PHP arrays. We got it from XML, we made it into a PHP array first of all. We can see we got a command first of all adding or deleting the job address. They give us something like a username and a password. The number of days, some details, the details would become the body, the job title becomes a no title, etc. Yeah, and if there was no error, we created the job and gave our 200 okay response back to probing so they knew it worked okay. If there was an error with some description, then we returned an error code and the method we gave them that. So they were able to give some feedback to the user at the end as well. So how did we use different types of tests for this? Functional tests first of all. So we made sure that our job nodes were created and created with the right URL. We got the correct response code back from them. We then used some JavaScript tests and the application URL we had to update the JavaScript because reasons. There was something to do with UTM parameters I think that they didn't work locally but as soon as you put them like UTM source it didn't work so we had to end up having to do with JavaScript. Actually it wasn't the best solution but when I had the tests I was quite happy that it worked so that was a nice benefit. We used kernel tests to make sure that the job nodes added and deleted as expected. Once they expired they deleted and then also made sure that our application URL was generated correctly based from the number of pieces we were to make it from. We then used some unit tests to make sure the number of days we converted to timestamps correctly because we were using the timestamps. Some results we had no bugs everything worked as expected it was right. There was some debugging time so there were some queries that it did stop working at one point but it's easy for us to go well as long as you're sending us the data in this format it will work because we've got tests and the tests are passing. So if you're not sending us the data in the format it's going to break potentially but it's actually really quick for us to debug this because it rather has to go through the whole module and then debugging everything we could just lead on our tests and say everything from outside is still working. So again, send us data in this format please. So test-driven development so we're really sure we're automated testing as it's on this thing. TDD is on a separate thing. It's just the workflow of writing a failing test first so we start with the test this can be quite scary to begin with it was for me, I'm sure it is for everybody. You want to write the code first because that's what you used to doing and then write the test afterwards. There's advantages to that. With TDD you write the failing test first and you want to see it fail. Then you write enough code to test a pass but you want to do any refactoring that you want to do whilst things are passing and then you just go through that cycle again so you maybe add some more assertions or maybe we want to do the next test afterwards. We call red, green refactor so red is when things are failing green is when they're passing red, green, blue unfortunately my slides are not going to be red and green unfortunately. It's also the approach I use when I support modules to duplicate including the node option module so I used to make a branch and then move the test across first look at the failures make them pass and that's how I did that which workflow give me a reading out test so I like to write tests outside in so I can start with the top level start with functional tests see how those work first they're probably the easiest to set it up and probably the most valuable does our endpoint work do we get the right response code that we need and then when I need to I'll drop down the level I'll do integration tests or unit tests if there's something that doesn't work in a browser it depends based on what you're testing and what you need I like to think of a program I wish for thinking you write the code you wish you had and then the test will tell you why it doesn't work and then you write a bit of it to work and then over time your test will be green then you know it works in my tests I like to write comments first usually so there's some spec things out usually again in that given when then or arrange a certain phase and then fill the code in afterwards excuse me I'll just keep me on track and usually I like to write the solutions first I'll start with the bottom of the test then it will work my way up I'll say that I expect there seems to be one and it will go well you don't have a variable called articles I'll write the variable articles and work it my way up it's the opposite of the inside out testing you start from unit tests to it's way up but I prefer this approach here's a project I've been working on at the moment this is the counter number of tests I've got so another thing how much is if you put them in organise them in the correct directories you can then call them using different test suites so I can just say run the functional tests run the kernel tests that's what they do in this project and I've got a count of the numbers and things that we've got right on this project I've got 57 functional tests 180 assertions in there I've got 38 kernel tests and then 5 unit tests and the other approach we get is a testing trophy so it's wider the bottom and works its way up in that sort of pyramid sort of fashion I'm doing the opposite so I've got more tests on the top when working my way down I run these in a CI pipeline whenever you push using GitHub Actions and they run in about 2 to 3 minutes so we've got a demo I'm not going to cover it all unfortunately just because of time it's a short and simplified example I've done a longer version of this before I've probably used something and I've probably used views for it in an actual real situation I just can't type and do this with views but for this example I'm going to we'll go down this with the custom code root you can see how this works so if you think I'll accept this criteria as a visitor I want to see a list of articles of blog posts at a page and I want to see them sorted by the post date the most recent blog post at the top working on my way down so my tasks are I want to make sure that the blog page exists I want to make sure that only published articles are shown and I want to ensure that they're shown in the correct order so we can start by scaffolding out the test so this is a start we'll do this outside in we'll start with the functional feature test first so we'll just scaffold that out this is in the functional namespace again I like to group the functional tests everything grouped first of all by type and then if it's like sub-directories I'll usually mirror the sub-directories structure so we'll do that as well there's a couple of default things we need to pass one is the default theme I think there's a Drupal 9 I'm thinking we need to start to find the default theme as well I've had test the fail because you changed the theme and the markup is slightly different Stark seems to be a good starting place for me anyway and then here this is part of my scaffolding I've got an empty modules array we'll see that in a minute and then we can write a test so I can say that we expect or it loaves the block page we'll use that sort of snake case type name with annotation I think it's fine over test code and I changed my phpcs file to say it's fine on tests so we're going to use this functional group of get so that's going to go to the page or to the URL that we've specified get the actual request back and then we can do assertions against it so we can say that first of all we want it to return a 200 response so it's a TDD so we haven't written it yet so what's going to happen so this is an example of what we'll see we've got a big E at the top so we've got an error because the page doesn't exist about half way down the slide here we can see current response status is 44 as in the block page doesn't exist so we haven't created it yet but we're saying we want to get a 200 response code so we want the page to exist again we usually get this little stack trace of what's happened, what's called what and then at the bottom we get the number of tests, the number of assertions there's all the summary at the bottom and errors in capital letters which is nice so again usually I'd use use for this but for this example we'll do it in a custom coding way we'll make a reading file and we'll call this blog.page and we'll say available at this path slash blog which matches our criteria we'll specify the controller we want to use this will be in our DrupalCon module in the controller directory and we'll call it blog page controller I'm not specifying the method names we'll use an invocable controller it's just my preference we'll give it a title and then some requirements so we need to be able to access content to see this page so we've run it again and maybe we think now that's going to work or at least we're going to get a different message or a different error but we don't in our test we don't know that the node module sorry that the spoilers, we did it at the DrupalCon module our module is not enabled so in our modules directory we just say we're going to enable the DrupalCon module if you think about your Drupal websites and the whole application and worry about saying the word project but I think project and Drupal.org sense is a module or a theme so if you think about uploading it to Drupal.org then it's not going to know which modules and things you've got on your project so we need to specify them so once you've done that we say enable the DrupalCon module that we're making our error is going to change so we're making progress this is this TDD workflow that you have a failing test you then make the error change then you keep going so now our response code has changed so now we're getting a 4 or 3 we've gone from a 4 or 4 to a 4 or 3 we're still wrong so we still get the failure so we need to enable the node module in this case and let's just see here because of this permit if I go back up the slides we've got the requirements permission the factors content which is given by the node module so we need to enable the node module so once again our status code has changed so we're now getting a 500 error code so something's broken but at least the page exists now so we need a controller we've told it that you need to go to this controller and then go to the invocable class and then that's what we're going to use we haven't created it yet again because we're doing TDD so we haven't created our controller so the next step is to make our controller and we're going to make the smallest controller possible to get our test to pass so let's make invocable so we use the global underscore underscore invoke and we don't need to return an array because we could have turned a render array so we'll just return back an empty array that's enough to get our test to pass so we're getting our correct response code now so we're getting our 200 so that's a good first step I think so the next part is we are the look to refactor it's not really a thing to refactor at the moment but we'll keep adding more assertions so we know we're getting the right code what we don't know yet is whether we're getting the right page the right page title or the right text on the page this will fail and we can make some changes to our block but it's a controller to make this work so we're going to get it so we're going to return back an empty array we'll say hash markup and we'll return back the text that we want make that work and the title will be set in the root style between and again our test passes so again imagine this is green usually this will be green in your terminal output but not in slides so we know the page works but now we need to make sure we've got content on it so I'm going to use a repository pattern use the repository pattern for this and then an entity a kernel test base so this is a kernel test integration test but again there's a number of different classes that we can use so entity kernel test base extends normal kernel test base I found for a long time when I was writing on this particular project anyway kernel test base I'd have to do the same set of steps every time and then I found the entity kernel test base that did it all for me which made my test a lot smaller so I'm going to say so we're going to test a classical article repository again the convention is it's the class you're testing but the word test at the end of it it returns blog posts and we're going to get our repository out of the container first of all so we use this container which is given to us by the class we're extending we're going to try and get our classical repository we're going to say get all and then we're going to assert the count so we want one thing back usually I do this over a couple of steps I'm squeezing things in because of slides and time so we're expecting one thing so just enough that's enough of a test to make it fail for us to do it so first of all we have specified that we're going to use this article repository class but we haven't made it yet so we're getting an error that is saying service not found so service not found exception is getting through because we're trying to call something that doesn't exist so if we make it this will be our repository directory and call it article repository again the smallest thing we can do the simplest thing we can do to get our test to pass is just to make in this case an empty control an empty repository and because we're making it a service we need to add it to our services file for our module so I like to do services in this way I just use the full test name and the namespace and everything for it and then it's just as quickly tilled at the end again it's not something on the slides on the screen so again our error has changed it said we didn't have an article repository now we do so the next error is that we don't have a get all method so it's add a get all method so again we're going to want to have it return back an array so let's just return back an empty array now at a point we're actually getting a logic failure we're not getting a set of failure where the class you call doesn't exist we're now failing like our solution is failing now like you said we want one thing back and we're getting zero back so I'm going to inject the entity type manager interface into our class and we can use nice PHP 8 parotid constructors we get the node storage and in our get all we can now just say return this storage load multiple again like this isn't going to work as we get to the other use cases so we got other node types for example then it's going to return them all but for this test it's going to get work so again what's the simplest small thing that we can do to get our test to pass because we're injecting something to our constructor now we're going to just enable auto-wiring which is another tool on its own otherwise you could specify arguments and pass so each argument separately so this will just inject what we're asking for into our class so again we get this message node entity type doesn't exist because we're even trying to create nodes so we'll enable it in our modules array again the same as we did before and we're back to the situation again where zero matches one so in our test we also have a thing called a node creation trait so it's a trait but there's lots of methods on for creating nodes as the names have been applied and in there we can say this create node and we can specify the array of things because again like this if you're putting this on dribble.org it's not going to know about the content on your site because it's going to be a separate standalone module so you need to create this content within your test for it to work and make its versions against so before we do anything we're going to create a node so this is our arrange step at the top and the first third of our test then our act is we're going to get a repository and get all the things and then we're going to say do I assertion at the end so now we've got an article we've created an article our test passes so this is a constant red green refactor this fail little pass refactor phase that we're doing again now we're green we can keep building so we can either in this case something so we don't we get one thing back but is it the right thing so we can make sure it's an object first of all rather than a string or something else completely the index key one because we're getting them back from the entity type manager so they are actually it's not a typo honest it is actually the one that comes back it's node one that we've created so we're saying it's an object so the first one we return back is an object but then we can say it's got to be a node and then we can say it's got to have the correct node type it's got to be an article we've set our title now upon line two so we can then assert that the title is the correct title as well so not only now are we getting things back from our repository we can also make sure it's the right thing that we're getting back so how do we make sure things are published so everything we've done so far is we could have done really with the functional test we can make sure the text is on a page so because the test is at integration level how do you make sure that only the published things are returned that's pure art criteria we don't want to see our unpublished things yet so we can also set the status in this case we can just use the note that the constant for published are not published, it's on the node so this is just zero or one isn't what sets the status and we're going to say now based on this test and based on our arranged depths out of five nodes we're already expecting to see three so we're assuming our count is going to be three this is going to fail because we haven't written the code for this yet we're still returning back everything so we're saying we expect to get three where we're getting five so now we can go back we can go back into our node repository again and we can update our get all method so instead of saying load multiple, load everything we can pass now we'll switch to using load by properties and then we can use set status there and just add a condition to our query so now we only get back the published ones so again this passes this is a real one for a functional test we could say we see this text on the screen what we can't really do very easily is say this is first and then that is next and this is that we could look at xpath this is going to be much easier to test it so this time we can set the created date and let's set them all to have different created dates we're going to get now by default which isn't what we want so let's set them in an order we know is going to fail because we want to see this test fail first it's a little bit of an anti pattern to be like yes our test failed past first time you really see it fail first you know it's testing the right thing because if I put them in the right order they would just pass and it would work but it doesn't actually work the way we expect at all so the order I've put these in it needs to be 3 the 1, the 2, the 5, the 4 this is going to fail so we expect them to be in that order if we're sorting them in the way that we've said that we wanted the way our criteria says we should so as we expect I guess our test is going to fail certain the two arrays are equal so we'll just go back a bit so on line 15 here we're saying the certain same so we're getting the node IDs that we return back and then saying we expect them to be in this exact order we're not saying look at our page and so we want this h1 and then this and then that order we're literally looking at the IDs and we're getting back from our code so it's returning them back to it's not returning them back as 3, 1, 2, 5, 4 it's returning them back as 1, 2, 3, 4, 5 in the right order so it's just failing because we're not matching our expected order so let's go back in and we can make an update so we can still get our articles from our repository that we can sort them so we can use the UA sort function or PHP might be a better way of doing this and then we can pass to the callable and we can say node A and node B and then this created time is greater than this created time since the last time I did this talk you should be able to return back whether it was bigger or not as a boolean but now I've got to pass through as an integer so we get to use the call spaceship operator to say whether it's less than or equal to a greater which I thought was really cool so once it's sorted we return them back our test classes so there's a whole lot of delay to this as well so if I drop down and do some unit testing I've got the longer version of this if I do a presenter and that's like a unit test we'll do the simplified version of this talk so this was a tweet I did this talk at a user group in the UK a while ago when was this 2018 goodness me a screenshot that somebody had taken after this and said I've got this root test awesome 20 there's a lot of old tweets in this slide I've just realised so I tweeted that afterwards and the response back was this terrible quality photo for the total driver to test and it was great so far it's really helped me to give me a piece of mind to help me to uncover a bug I wouldn't have otherwise the main thing is about this confidence it's like again are things working the way you expect and tests aren't going to tell you where the things work but they're going to tell you where the things don't work so there might be use cases you haven't tested yet that you haven't found or that you still need to write a test for but if you've written a test for something and then later on you're changing it or you're refactoring something and you break it, you'll know because your test is now going to fail and I think that's quite common why people don't like extending or changing code or don't refactor code because it's scared of breaking it so if we can leverage these tests as a safety net and we can know that the test passed to begin with I've made all my changes and it still passes then things still work we did a big refactor on not big but a fairly large refactor options module a while ago and it was, yeah, we were able to do that because we had these tests in place so I started actually I started doing a code during the day when I started learning and then I wrote tests in the evening for that code that I wrote and I found a bug that I wouldn't have found otherwise and that's what really got me hooked on testing so thank you very much there's some links here to the PHP UNID documentation things and the Drupal org documentation my site is alloverdaves.uk and then the last thing here I'm going to be doing a free 7-day email that ultimately testing course for email so you can go to slash ATDC, Ultimately Testing Drupal Course put your email address in there and I'm going to finish that this week he says on camera we'll get there so yeah and this will just go a bit more into this detail from what we've just seen and we'll look at the UNID testing we'll do the full breakdown of everything in that course and that's inside the path for free so thank you very much