 Hello Drupalcon. My name is Alexei. I am from E-Pump company. Today I want to talk to you about health, the health of Drupal programmers. I hope they are such in this room. Great. So how do you feel? Good? Are there any complaints? No? Maybe it's because they are just no unit test writers here. They are probably busy with finishing their unit tests. But let's start anyway. A long long time ago in a Drupal version far far away there was a really happy time for programmers. Every programmer could just write the code without suffering with any of these unit tests. And if someone suddenly came with the TDD flag and shouted let's write unit test already they answered that this is not possible with our Drupal because it has a lot of services which are impossible to mock. And here you can see a pretty famous quote of Angie Byron about this from FAR 2015. But since the first pioneer was Christopher Hopper in 2008 inspired by this blog post. But he successfully failed at that time. Seems it was way too early. But times change. And now we already have almost 30 thousand of unit tests in our Drupal as you can see here. And even several steps to simplify unit testing. I found only 4 functions here they are. But also I found more than 4 thousand of active issues that are seriously stuck in the unit test status and they are stuck there for years without any serious progress. I also set out to see how things are going with this in Drupal Core and was very surprised to find only 15% of the coverage. It's too low. And one more find that made me think. Both in Drupal Core and in Contributed Modules I've noticed that they have much more functional tests than unit tests. Even more than twice. Since that all developers prefer to write functional ones over unit tests. So those developers why do they still not want to write unit tests? And really why? Try to remember your feelings when you successfully finished the unit test after several hours of efforts. Were you happy? Probably yes. But satisfied? I don't think so. It's because that unit test took you quite a few hours efforts and produced a huge number of code lines. Much more than the testing function. And here is the usual picture when the actual code takes around 5 lines but the unit test requires to write more than 50 lines. Are you familiar with this? I think yes. So let me try to do a little poll. What kind of software with unit tests do you have? Please write the hand who have business. Okay. Insomnia. Anxiety. And maybe someone does not have any of the above symptoms. Don't be shy. Write the hands. Oh, I see the hands. But I assure that you've written exactly the unit test. And exactly for Drupal. No? Okay. So I suffered all the symptoms in the past. Exactly like you. But no more with my new module test helpers. Would you want this too? Let's go. I'll show you the way. Here is a simple Drupal function that just loads three red syntacticals and builds a list of them. Nothing serious. Just 29 lines of code. Here is what it does. Just a list with three titles and dates and author names. That's it. And now let's just write a unit test for it using the classical approach. But probably not. The most experienced unit test writers should have noticed already in that code the config factory, entity query, load multiple, loading entities by references, and even to link. All of these are symptoms that writing the unit test will be a pretty tricky task. And most likely they will decide to write a kernel test instead. Let's do this too, shall we? Here it is. Nothing serious, very simple and short. Just 20 lines of code. Execution time is 5 seconds, not so bad. But this is still not a unit test. It's a functional one. And running 100 of such tests will take already almost 10 minutes in our pipeline. That's not good. And well, we are here to talk about unit tests, not kernel ones. So, let's yet brace ourselves and still write a unit test for it using the classical approach. How many lines of code do you expect? While you are thinking, try to remember the amount of lines in your last unit test maybe or average number of lines in the unit test you usually see. Ready to see the results of my attempt? Here they are. I've ended up with exactly 100 lines of code. Yes, as many as 100 lines. Just for our 29 line function. Just 3 times more lines. 5 times more than the kernel test. Nothing unusual. But execution time now is just great. 70 milliseconds. 50 times faster than our kernel test. That's awesome. And we've got 100% of the coverage. Pretty good. So each line of our function is covered by unit test and we can easily refactor it without fear to break anything. But that 100 lines of code. We've written as many as 43 lines of code just to mock a single entity query call. And per 8 lines of mocking for each node having 26 lines of mocking total. Setting up services for container builder took 23 lines. And suddenly we have all 100 lines filled. Are you satisfied with this? If you ask me, absolutely not. So, can you imagine a word where to mock an entity query you can write just a single line of code. Or create a mock of entity with prefilled fields also with a single line. And have a ready to use Drupal container with working core services. What if I say that I have it for you? Ready? Okay. I rewritten the 100 lines unit test almost from scratch. But now using the test helper's API. How many lines of code do you expect now? Obviously, there should be less than 100. Maybe 80, 50, 29, like an original function. No, less. Here it is. Just 12 lines. 8 times less than the classical one. It's pretty short and easy to understand, isn't it? And look, it's half size less than the original function. 12 lines versus 29. Let's take a closer look at the unit test code. The first line initialize the provided stop for the config factory service. And at the same time creates a new config record with desired values. This makes the highlighted part of the code works out of the box without any manual mocks. And the second line initialize the date format or service with the same approach. Because we have with the tested function the conversion from time stamp to string that requires it. And at the same time, we're creating a medium format here. This makes the highlighted line from our original function work as expected. Next five lines create a user entity and four nodes with required values. Yes, one line, one entity, not a line per each, like in the classical approach. And in this line, all the magic happens. The test helper's model initialize the class with passing working stops for all required services. So you can see here the config factory, the entity type manager, and date formatter. It automatically creates stops and passes them to the constructor to work out of the box. And then it executes our article list function. And in this function, it automatically provides the entity query. As you can see here, get storage, get query works out of the box. And even you can execute the query and receive the correct results. The same as in kernel test. It also provides the fully working load multiple functions. So you can just put the list of IDs and get the entities. And date formatter also works out of the box. So it simply converts from time stamp to string without any efforts from our side. And even fields with entity references works out of the box. So in the article, we have UID property and we can call entity to label and get the label of the author of this article. And even to link function works out of the box too and produce the correct links to the entities. And the final pretty boring stage when we are asserting the results. It's the same as in the classic test, nothing to reduce here. Now let's launch the test, shall we? And we've got 100% of the coverage. Cool, exactly like with 100 line unit test but with much less lines. And now let's take a quick look to another approach. Here is the current test that executes the query automatically via single magic line. But this magic with entity query works out of the box for now only for simple queries. And for more complex queries with conditional groups I have another solution. Asserting the crucial conditions and mocking the result manually. Here it is. This approach works well for any complex query you have and I'll explain how to use it a little bit later. Now let's launch the test second test. And we achieved 200% of the coverage. 100% for the first test and 100% for the second test. So we double covered the code. And it should work pretty stable. Seems that's all from my side. But seems not. I saw a couple of unit test writers recently. Even after applying entity steps to paint points I could still see that they continued suffering. Seems like they need more help. And I have it. Here is a brief list of paint points that test helpers covers for now and let me showcase them one by one. This is a group of functions to initiate Drupal services entity storages and save entities. They provide fully working steps of the real services and entities that can work well in unit test context without initialized Drupal core. All saved entities are stored just on memory including configuration entities. So no files or SQL database is required. All works out of the box without any efforts from your site. So you probably want to know what's this step. This step is a fully working class or service adapted to work in the unit test context without initialized Drupal kernel and without database. Here is a class that provides a step for config factory service. So I just extend the original class, initialize all dependencies, set default values and provide some helper functions. So you can see here in the constructor we're creating steps for all dependencies and have a overload, do load multiple functions that clears the static cache. And the step set config function is a helper to set config values just via a single line. Let's look how this can be used in real tests. Here is an example of a simple event subscriber for config safe and delete events. It just shows a message about each event. Nothing serious. So if you save some config it shows that config is saved for deleted the config is deleted, that's it. So we have a test helper's example services YAML file where we are describing this service and the actual code of the service. And here is the test for this subscriber. It's pretty short. And let's look into it. First two lines are for initiating the messenger and config factory service with getting the editable config. And next we have already two magic lines. The one line is for saving event and the next line is for delete event. So those lines automatically initiate our service by service name. So we are not passing the path to the class just passing the name of the service and it automatically finds the YAML file parses it, checks the related class, initializes it and all this works out of the box. Next group are helpers for private properties and methods. So sometimes you have some protected property or method that you should cover. But unit tests some of you may have heard that unit testing private methods is not considered as good practice. That's right. But not with our Drupal. We have a lot of abstract protected functions that we should implement in our custom code. Here you can see the list of functions that I found and there are a lot of more functions and they should be covered in unit tests. And let's look at the example how to use it. Let's get a field plugin that uses a protected function compute value. And these are just three lines that calculate the edge of the node. So current time minus node creation time and that's it. And here is the unit test for this three-liners function. In the first part we are creating the entity. Initiate the time service and we have only one assertion that the received time equals current time minus created time. And we have also two magic functions magic lines. The first line is checking the results with 100 of current time and the second with 200. Let's just to check that our logic works out of the box correctly. Next point is the steps for most popular Drupal services. I already implemented fully working steps for most popular Drupal services. The step is an extension of our original class that allows to initiate the service in the unit test context without installation of the Drupal kernel services so they just works and you can use them as usual in kernel test or in original function. You can find the list of services already covered by steps in the test helper's PHP file. On the left side the custom steps created over the original service classes and on the right side the list of Drupal services that can be initialized in unit test context without any additional changes. There are three key dependencies and I need to write additional code over the original class to make it work well without active Drupal kernel. But some services can be just initialized without any changes from my site. So I listed these services and will add new ones when find something new that can be initialized. And also we have some services that not covered yet by my module for example the renderer service. And for this I also have some improvement for you. You can pass the service name to test helper's service function and put the path to the class of this service. And that's it. After this my module will create a mock of this class with all available methods and you can just mock the result of required methods with using method with return approach. And also you can use my another helper function setMockItClassMethod that provides you ability to create a callback function that will be run in that context. So you have access to this variable all protected methods and functions in your callback function that differs from how pqpunit provides. And also you can initialize your custom service too using the test helper's in its service API function. So if you have some service you just put the name of the service and it will automatically finds the YAML file finds the related class and initialize it with passing all dependencies automatically. So just a single line and you have your service initialized. Next are helpers for testing entity queries and database queries. Using them you can check for crucial conditions in the query and mock the execution result. You already saw this way for entity query and the same approach you can use for database queries even if you have a raw database query which will help you a lot. And here is an example that I've shown you previously. And let's look into it how it works. So we are getting the entity query SQL service and setting the execute handler. This is a callback function that will reserve the object containing all the conditions and other properties of the query. And we are using the query subset that will check the availability of crucial conditions. Also it will check the sort range and access check values and then we are returning the expected result of this function. But we can do this with the classical approach too using the method will consecutive. So on the left side you can see the example of createMock and with consecutive we are checking that the first call contains status and one the second call contains type equals article. And this approach will work for this query. But if we just change the order of the conditions it will start failing because we are strictly describing the order of conditions but in the SQL query the order doesn't matter. So that's not good. And with approach with test helpers you can just list conditions in any order and it will pass. You can change the order and still have a green unit test. And even if you add some new condition for example promote equals 1 it will pass too because by default it only listed conditions so we are checking only status and type and other conditions can change without breaking the test. But if you need a strict check you can use only listed property to force check all conditions and with this approach it will fail the third test with new condition if you need it. And now let's take a look to a more complex example. Are you ready? Let's get a custom service and throw into it nodes with translations taxonomy with vocabularies and terms relationships between all these. Okay? Here it is. The actual code looks not so scary but believe me the scary will be when you will start to write a unit test for it using the classical approach. I didn't even try to start it without test helpers. Oh, here is the test using the test helpers API. Yes, it's not so short as the first example 41 line but this is still much less than we should write using a classical approach. The classical one can easily take 200 lines or even more. Let's elaborate on it. The first line initialize the language manager service and adds two languages French and German. The second pretty large block for creating two users a taxonomy vocabulary with two terms and two nodes. But now for each node we have not a single line as I promised. Well, it's because here we are creating not a simple node. A node with two custom fields and with translations. I'll explain how it works. Here is a custom field field synopsis. It's just a string so here we should only indicate that it should be translatable. After this, field can store translated values and another interesting place is field category field. It's not a base field so we should manually indicate that the type of this field is entity reference and the target format is taxonomy term. We can do this like displayed here and after this the highlighted lines in the testing function will start working in kernel test but in your unit test and the next again one single line that does also drop it initialize the service we are detecting the location of the YAML file, find the related class, initiates it with providing a stop for the entity type manager automatically and the last boring stage we are asserting the results by running the function three times. The first is in English language and it should return two values because both nodes have English translation. The next two calls with French and German language should return only the second node because only it have translations to that languages so the entity query automatically filters out nodes without translations and that's it, very simple as usual but perhaps some is worried that with this approach it will not be possible to use classical methods with mocking any results. Don't worry, I kept this for a while. We are mock methods parameter you can pass the list of functions which you want to mock manually using the classical approach and even add new methods that are missing in the interface for some reason like send as email as displayed here and you can even pass a mock of any field to the entity stop so just use createMock to create a mock of the field and pass it to the field value and if you have even mock customized field with custom definition you can pass the mock of the base field definition like this and it will apply not to the first entity but for all other entities of this type too so you can see here we are applying the custom definition to the entity one but getting it from entity two and it works because it stores the configuration of each entity and each bundle and you just need to describe this in the first call and then just put the values and that's it that's pretty convenient I've showcased you the most popular cases which test helpers covered for now but I don't want to stop and will continue improving the module I started the development of this module in July 22 related a few style for version in September after that I tested the module on several projects gradually added new features with releasing several alpha and beta versions all other members of our team started to actively use it too and was very happy with this the amount of time spent on unit tests has been significantly reduced by more than 5 times so the management was satisfied even more than developers and for now I've released it the first release candidate in April 23 and I guess it will work pretty stable and I plan to release a stable version in maybe the next months and as I see in Drupal log statistics this module seems to be already used not only by us with our couple of projects but some other people started to use it Drupal statistics shows in the best times the 15 number of uses yes the chart is going down in last months but that doesn't mean that the people stop using it it's because Drupal log counts only instant models and no one want to add new dependencies especially if this dependency is just for unit tests but I've invented a workaround for this so no one wants to write something like this in the YAML file of your module and show the dependency on some Alexei for your project but actually you don't need to do this this module will not come to your production environment because adding a development requirement is enough so you can just use Composer Required Dev, Drupal Test Helpers and that's it and you even don't need to enable this module in Drupal just having model files is sufficient you can see an example of this approach in my another model Opal Telemetry that already now uses Test Helpers to cover its logic by unit tests and it's very convenient for me and one more tricky question do we need to cover unit test functions by unit tests? and my answer is sure and I've covered them not 100% of the coverage yet but I plan to improve it when I find time for this and yes, I've used Test Helpers to test test helpers and now couple of words about the roadmap I have no strict plan yet just ideas what we could improve the first item is about adding new steps for more services and I'm continuously doing this when a new savers come to me I'm trying to create a universal step instead of working only for the current case next item is about integrating with more feature rich prophecy and mockery libraries in addition to PHP unit those libraries can significantly simplify some things but for now I don't want to force users to stick with one of these libraries so I just use it only route PHP unit API so maybe in future I've not yet decided about this and maybe with you help and together with unit tests we also have kernel tests yes, they are much simpler to write even for now and Drupal provides pretty good API and maybe there are nothing to improve in this site and the last but the most tricky one is try to move features to Drupal Core because no one wants to install new module it's pretty convenient to have all works out of the box with Drupal Core and I hope to see some movement in future related to this and maybe in the next year we will see my functions in Drupal Core and we can use it pretty quick without any contributed modules and what ideas do you have don't be shy, submit feature requests so now I hope you are ready to use the test helpers in your projects if so please give a star and subscribe to new releases or even better join the development but don't forget about functional integration tests only they all together are the key to the successful project and now that's really all from my side thank you for your attention and don't forget the appeal join the development let's do the making life of unit test writers easier together but probably you have a lot of questions right? because it's a pre-recorded presentation we can't do it online right now so just reach me by email or in Slack and ask for help and see you at the next Drupal cons bye