 Thanks everyone. So here we are on the road to zero friction testing. I know testing can be quite difficult for some people. It might seem like a waste of time or it's too hard or it's a lot of effort and so we're here to reduce that friction. We're gonna be talking about Drupal test traits and how you can get the most out of using them. My name is Michael Strelin. I'm from Previous Next. If you were in Drupal South in Brisbane last year I gave a talk, five simple tips to level up your legacy code. If you weren't there or you haven't seen it you can check it out on YouTube. But the number one tip that I gave was to use automated testing. So today we're going to talk about how to get started using Drupal test traits. We'll walk through writing your first test then we'll dive into how to build a library of project specific traits to make your life easier and go over how we can maximize performance and reliability of your test suite and talk about fostering test culture within your development team. I believe immediately after this there's another talk about testing in this room. I think I'm not too sure but I think it's gonna go over some of the more fundamentals about like how to write tests in general. Whereas mine kind of assumes a bit more you know a little bit about testing but we're trying to you know make the most of this Drupal test traits library. So Drupal test traits is a composer package that basically lets you run tests against an existing Drupal site with user content, real content types, real views, modules installed and everything compared to like a traditional PHP unit test where you're basically mocking up a environment just to test like an individual piece of functionality. So some prerequisites. You need a local development environment. I know everybody has one of these some of us are fortunate enough to have a production environment sorry a separate production environment. That's a joke. You need composer and you need a Drupal site. So if you just want to follow along like if you're looking at this later and you don't have a Drupal site you want to use you can follow these commands to get you basically a recommended project thing. This is using DDEV. You can use whatever else you'd like to use instead and we're just running the drush site install with the standard profile. So obviously you need the Drupal test traits package and you need core DEV or I guess you don't technically need core DEV but you need these packages that come with core DEV. I'm not going to go into what they all do. So then you need to configure your PHP unit XML file. The main thing here which is an example that you can get from the Drupal test traits docs directory just copy it to the root of your project and the main thing you need to do is set the Drupal test traits or DTT I'm going to refer to from now on the base URL and so that's where your PHP unit executable can contact the Drupal site. So if you're using it if it's all local you might just use local host if it's in a container you know it might be some other Docker IP or whatever or with DDEV you can use the fully qualified domain name and that basically looks something like that and then to test that it's working you can as an example test which you can run against your standard profile installation that we did in a previous step which exists in the Drupal test traits library basically just run the PHP unit command and pass it the path to the test. The dot there indicates that the test ran successfully and we ran one out of one tests and they 100% pass rate and it all ran in two and a half seconds. So without looking at the code for the test it's done a whole bunch of things so it's created an admin user, a taxonomy term, an article node, tagged the node with the term, published the node and then performed some assertion so we tested that the author of the node is the admin user we tested that we can browse to the article in an actual browser or it's not really a real browser but we test that we can log in as the author and have access to edit that page and so that all completes in two and a half seconds so you can start to see the power of this compared to having you know manual user testing that's going to take I don't know at least five minutes. So if you want to see what's actually happening in that test you can enable HTML output basically we just need to add the printer class attribute in our PHP unit XML file there's plenty of documentation on how to do all this and we basically need to set a directory where it's going to write the HTML files and so having a look what that looks like when you run the test again basically you get a bunch of links at the bottom to they represent every HTTP request that was made during the test and if we click on them we can see there's our article there's the login page we're now logged in as the admin user we're editing the article and after we've saved it we can see it again so that's really helpful if you want to debug like what's going on why is this failing maybe you'll see here it actually says access denied or something like that and so that's why your test is failing so moving on to write our first test that example test was against the standard installation profile but we want to do some cool things with layout builder and content moderation so we're going to install umami run this drush site install demo umami there we have the familiar food blog magazine thing all right so basically in this test oh it's so some conventions for writing your tests that you need to follow basically the class name and the file name needs to end in test with the capital T each test method in your class needs to start with the lowercase word test and you need to set the namespace accordingly so if you're in a country custom module or country of module I guess custom module for this use that namespace triple slash test slash module name slash functional and then if it's project specific you can drop the triple part of it in the module name and place it like you know root directory somewhere but you have to configure PSR for class in your composer file it's really up to you I guess like if you're writing tests that don't really relate to any particular module that's a good way to do it put all your tests in the root somewhere so your first test will end up looking something like that like this so in this test we're going to create an admin role and a user then we're going to log in and visit and create we're going to visit the basic page creation form via the UI and we're going to create a page assert that a message appears saying you know the basic page has been created so you know confirm that it worked we're going to test that the title that we assigned appears in the h1 tag of the page and then we're going to assert that the body text appears somewhere in the page so when we run that it looks like that we've got a HTML output it's in our umami theme you can see the title and the body text and the little green message saying that it completed successfully so we're winning all right let's move on so just a quick note so in that test we use the UI like the node form to create the node it's often faster to just use the API to create the node directly so the Drupal test traits package has a bunch of helper functions like create node basically we use that create a node set the title and the body and the publisher and save it and that happens a lot quicker than having to like actually log into the site and load up the node form and save it and all of that so depending on what you're testing like if you're testing the form itself then you want to do it by the UI if you're testing like the functionality of the node then create it via the API so another note on cleaning up after yourself so because you're testing against a real database with content you don't really want all this this test data in there so Drupal test traits actually it deletes all the entities that you create when the test completes whether it passes or fails doesn't matter so if you're using the create node method that I mentioned before this contains this this dot mark entity for cleanup and then at the end of the test in the tear down Drupal method anything that's been marked for cleanup will be deleted with the UI tests because we were creating a user via the UI and logged in as that when that user is deleted then their associated content is also deleted but you may find sometimes you need to implement your own tear down function if you're doing something else like changing config around or whatever you might want to reset it to the original state okay so the next part of the talk is about building a library of projects specific traits so basically what you can do is identify common patterns in your existing tests abstract these into methods that you put into traits where possible use base classes so we usually recommend like when you start a project you create a base class that all your tests are going to extend from and that's going to extend from Drupal test traits existing site base and that you can put any common methods that you're always going to be using in there and by doing this you basically reduce the amount of test code that you have to write every time and you reduce the need to think which reduces the friction so moving on to a layout builder example so if you let's say we create a custom block type that we want to embed in layout builder and we want to test how it appears on the page to do this programmatically in your test normally you'd have to create an instance of that block and you have to populate all the block fields and create a layout builder component and attach the block create a layout builder section and attach the component create a node and attach the section publish the node and visit the node that's a lot so that looks like this this is 36 lines of code and I already hear it like I don't want to do that I'm not writing all that code that's too much does anyone here get paid by the number of lines of code that they write so let's look at how we can break this up into some traits that we're going to reuse again for the next block type that we create or yet other areas that we might use it so first of all I think we can have a block content test straight so in this we can create the block content entity and put that entity cleanup stuff so that that block contents going to be deleted when the test completes because that doesn't happen automatically with triple test rates it does for nodes users taxonomy terms but there isn't something block content and then maybe we'll put some helper functions in there for each of our block content types like basic block work we have some other more sophisticated blocks we create helper functions that have like name parameters for example so you can pass in you know the title in the body and whatever other fields then we're going to have a recipe creation trait because this is umami so it's all about recipes so we're going to use that that node creation trait that comes with DTT but we're going to populate some default values so instead of having to give it a title and a body every time we're just going to have those as default and yeah if you want to take it a bit further you could have bundle classes for your recipes where you can implement like getters and setters like set title set body set steps to cook or whatever we might have a layout builder manipulation trait if we remember there was all of those steps we need to take to create the block and embed it in all of those different places and then finally a content moderation trait maybe we'll have like a save and publish function so we can do all that in one hit without having to write multiple lines because most of the time we're not you know testing that we need to save it as a draft and needs review and all of that except in our model in our workflow tests but this is our block layout builder block test so we don't actually care about the specifics of that so that 36 lines has now been reduced down to 11 lines of code but I still have to think about this you still going through all the steps of creating the block and the component and the section and placing it on the page and everything so I think we can abstract it a bit more so if you create a few more methods it can look like this so basically create our body text we create our block and we create our recipe and then we have this function that does a bit of everything so add inline block to node that's combined those three or four steps we have before and then a publish and visit so that's doing our save set the moderation state and the Drupal get load the page and then we just do our assertions after that to check that the body text appears on the page that's down to seven lines of code so some other possible traits that will help you to speed things up when you're writing the test helpers for every content type or entity bundle that you have just makes it a lot easier than remembering like all the machine names of the fields that you need to set if you just have you know your idea you can order complete all the fields and everything like that then maybe some like page element assertion helpers like if you want to test that the breadcrumbs like the expert you know I want home about whatever maybe if you have a helper function for that and that will check that it's got the right class names and all of that on the on your markup or you could do the same for local tasks or meta tags you know assert that the meta description is whatever then probably some utilities like assert order in page so if you have a requirement to list things a to z or by weights or whatever excuse me you could have a function like that that scans that each of those expected items appear in the right order things like creating random URLs or random emails which are good for just like dummy data for testing some entity loading helpers so when you're creating things via the UI in your test you don't have an easy way to access the object that was just created whereas if you do it via the API you've got it immediately but you could use like a last created entity function or a load by properties function that would do just a quick query to find your entity that you created there may be some like editorial section helpers if you're using workbench access search API indexing if you're using that and basically yeah the list goes on for whatever requirements your project has so when once you've got a fairly healthy set of tests in a in your test suite you might find that you're running them on every pull request in CI in circle or GitHub actions or whatever and it you might feel that it's slowing you down you know it takes half an hour for your 200 500 1000 tests to complete and it can get quite frustrating especially if you know they start to fail and you had to wait 25 minutes to find out that it was failing and then you're going to go back and do it again so we're going to talk about how you can maximize test suite performance and reliability because the other part of it is if you have what's called flaky tests that pass sometimes that fail other times they can be really frustrating so the first thing is to avoid unnecessary work so like I mentioned before create entities via the API where you can unless you're specifically testing the UI or the form to create the entity then just to avoid the API you know don't attach images where you don't need to like if you have an image field you might want to test it once in one test but you don't have to upload an image to every single entity you create and then yeah limit the scope of the test so you know don't basically don't populate all the fields of your entity if you don't need to so I have an example this is kind of extrapolated from something that I found when I was doing some refactoring on a project recently basically we had a there's a requirement that external links on a page get like a specific HTML class and then they've got you know like the little icon that says it's external and this was kind of being tested within I think it might have been layout builder or something like this so we're doing a whole lot of work to create a block and attach it to a page and all of that stuff but then if you're familiar with data providers basically you can provide basically run the same test many times against different pieces of data so we've got our list of URLs that we're gonna run the test on and it was running like creating a block and a page and all of that for every single URL and there was something like 20 of them so that's a waste of time because really we only need to test that the the class gets applied if it's considered external and it doesn't get applied if it's considered internal so we can do that once and then run a much faster unit test to to do the pattern matching like to see example.com is not external and example.org.truple.org is external triple.org is external all of that so basically this is what the unit test for that would look like so we use the same data provider and we just call that is external function that we have in our code that you know it does whatever it does we don't really care but this test runs very fast because it's not creating the node it's not creating the block and all of that sort of stuff it's just checking if it's true or if it's false when you pass in those URLs and then the the other side of it so testing that the HTML class actually gets applied so basically we just create one article we put the two links one that we already know is it internal one we already know is external into the body and then we just do two assertions so this example is is really minimal like there's not a whole lot of difference but you know in some cases you might want to be testing it hundreds of data points or whatever so yeah just something to keep in mind so then another thing you can do is reuse the setup class so basically traditionally or in some test frameworks there's kind of a convention to have one assertion per test case so that might look like test that the external link class is added to the external links and then we create a node we do one assertion and then the test is done and then we have another test to test that it's that it is not added to internal links and we do the exact same thing again and there's a whole lot of setup involved so we have to create a node each time we have to set up and tear down the test so instead we can just combine it into one this is a fun one that I don't think a lot of people know about so reducing HTTP request is going to speed things up a lot and so basically you don't actually have sometimes you don't actually have to visit the node that you've created to to test the output so basically you can render the entity in code and then you can use this symphony crawler to basically query sorry I'm looking at the wrong side so that basically that's a that method is you pass in an entity to it and it basically spits out a symphony crawler which is basically and a class for checking if HTML elements exist inside rendered output and then if we if we want to use it so basically we create a node we create we get a crawler for that node and then we're filtering it by a CSS selector and then we make an assertion like does our link you know have that class or does it not have that class so yeah basically we've saved during a HTTP request for that but we still have the HTML that would be output that would be generated JavaScript tests so you can kind of get stuck thinking okay if I need to test like media library or layout builder or busy we're editors all of that sort of stuff you know I have to do a JavaScript test so that I can populate those fields because you used to you know the off-gambes for the dialogue or anything like that but most of these things actually have non-Java script fallbacks so basically the JavaScript functionality itself is already tested in core you don't need to be testing that so if possible just you know do a non-Java script test for these things and then if you're writing your own JavaScript opponents and you want to test their functionality we find that using jest for that is usually more suitable and more reliable as well Selenium which is the driver for running JavaScript tests is it's slow and it's often flaky I mentioned all of these things and then in your CI you might be able to configure concurrency or parallelism which allows you to run multiple tests at once we've got a package called PHP unit finder which basically creates a list of all your PHP unit tests in your project and you can pass the output of that to Circle CI which has a split function I think it's called and then it basically calculates the average time it takes to run each of your tests and tries to like balance it out so you if you have like you know for runners going in parallel it'll try to make it so they all finish at about the same time so yeah so basically the next part is about fostering a test-focused culture within your development team so yeah basically having good test coverage gives you productivity without fear of breaking something so you can come along with a new feature or changing the way a feature works or implementing or installing updates or new modules or whatever and you know that if your test suite passes probably nothing's broken or if something does break you know straight away oh okay I wouldn't have even thought that you know that section of the site would be touched by this new feature but it is so you can go in and fix that up that helps with resourcing and onboarding of new developers so if you have people moving around from team to team and on different projects and they they're not really familiar with the site so much they might have more fear that they're gonna break something but if you know if you're used to having good test coverage you know you're not gonna create problems the tests can sort of serve as a self documenting API examples because if you you've created this API you've got test coverage for it the test coverage is you know accessing the API in ways that you would expect it to be accessed to accessed in so you can yeah kind of refer to the test so to do this yeah basically add tests as you go don't you know leave it to the last minute before go live you know we'll write all the tests at the end once everything's ready to go because this is not gonna happen there's never any time then and you're not gonna remember all the things that you need to test so basically you know every PR should have test coverage don't you know approve your colleagues PR if it doesn't have test coverage or you know ask hey is this covered somewhere is there already existing coverage for this do we need more coverage that sort of thing and yeah what allow time when you're estimating your tasks with your clients when your project managers say you know this is gonna take two days but really you know maybe it's gonna take three days when you're writing tests and you know it might seem like more but it's gonna save you in the long run so basically if that's just expected and always included then you know there's no no problems with it so a quick recap use Drupal test rates if you want to read the docs read the example tests read other people's tests and just get familiar with what you can do write traits to make your life easier so you're not repeating yourself every time and you don't have to think about how do I put this block on this page you just call the method that you've already created share your traits with your development team maybe if it makes sense for you maybe create a separate repo that you can use across different projects and you know have it as a composer library yeah be mindful of performance and find opportunities to make your builds faster avoid JavaScript tests where you can obviously you know do it when you need to and yeah write the damn test any questions I think yeah a lot of people have sorry the question was have other existing libraries or repos or packages of helpful traits that you can already use I think probably yes I know at previous next we started one it's open source you can go find it but I think we've got one trait in there so far but I'm sure there are others that have done the same yeah I think a problem is it often varies a lot from project to project on like your style of you know how you do your fields or how you do put your blocks on your pages all of that sort of stuff like that so yeah definitely have a look around yes so so be hat has a whole bunch of components to it I'm not actually probably familiar with it but we're mostly using mink and yeah the browser kit driver and the Selenium 2 driver from it so mink I'm gonna get this wrong but I think mink is kind of like an API for controlling a browser so when you do like Drupal get that's talking to mink and then mink uses a driver so if it's the browser kit driver that's kind of like the PHP based more browser thing and then Selenium 2 is using like headless Chrome or Firefox or something like that so we're not using so gherkin the syntax that be it be hat uses we're not using that and I'm sure there's other parts of be hat that yeah we're not using here yeah so the question is if we're when we're writing generic traits if we're trying to contribute them upstream I think yeah where it makes sense we do I've certainly had a couple of merge requests against triple test traits so I but I think it I don't think it's trying to be a package that contains you know everything and trying to be prescriptive it's just kind of a place to get started so yeah I think there's certainly room in the ecosystem for you know something like that to exist yeah mostly this this is doesn't look good but most of the time we're like copying things from one project to another and and you know that's a bit archaic yeah yeah thanks a lot for those comments and then I'm really glad to hear that I will say there's definitely a place for those kernel tests and unit tests and the functional and functional JavaScript tests that we have in core and contra where you're shipping something that you don't know how it's going to be used but when you're building your own site then it makes sense to be testing your own site and I think like so triple test traits is not the only thing you can use so the hat which was mentioned and I think there's a talk later today on Cyprus which I'm keen to check out because I haven't haven't really had experience with that so definitely gonna have a look at that one great thank you very much