
Conference Session
Inside the Pyramid - A path to faster, clearer Drupal testing

Francesco Placella
Senior Architect | Technical Lead
Drupal core requires a test for every change, but it has never said which kind of test to write, so the suite has grown slow to run locally and expensive to run in CI. Francesco Placella estimates that 20 to 25 percent of the Drupal Association budget goes to running CI tests on drupal.org. He shows how the test pyramid, with many fast unit tests, a reasonable number of integration tests, and only a few end-to-end tests, can make the suite faster and its failures easier to trace.
Session Description
Drupal has a large automated test suite, and much of it runs slower than it needs to. Core requires a test for every change but gives no guidance on which type to write, so contributors default to kernel and functional tests even where a unit test would do the same job faster.
Francesco Placella (Senior Architect | Technical Lead) proposes adopting the test pyramid as an explicit strategy for Drupal core, contrib, and custom code. He separates test levels (what a test covers) from test types (the PHPUnit base class it uses), runs the same test under different test types to compare speed, and explains why entities make unit testing hard in Drupal today.
For contrib and custom code, he walks through wrapping entities behind your own domain interfaces, mocking only the interfaces you own, and using fakes and thin integration tests for the rest. He closes with proposals for core, including converting tests to lighter types, letting kernel tests share an environment, and a two-question rule of thumb for choosing a test type.
What You Will Learn
- How Drupal's test types map onto the test pyramid
- Why test level and test type are separate decisions
- Why Drupal's suite leans toward slow tests, and what that costs in CI
- How to wrap entities behind domain interfaces so most logic runs in unit tests
- When to mock, when to use fakes, and when a thin integration test is the right call
- What a test pyramid initiative for Drupal core could look like
Transcript
[00:00:04] Well, actually, actually, let's get started. So, welcome everybody. I'm Francesco Placella from Tag1. I'm senior technical lead, which means more or less that I'm mostly attending client meetings and my relationship with code is read only. So, former core team member, and I'm on drupal.org since 2007. So it's been a while. Okay. So next one. Yeah, I felt I owed you this.
[00:00:37] So basically, the initial plan for this session was a core conversation, but it happens that it's no longer a thing. So I will try to say something useful before making some proposals. I will go through some Drupal history, drop a few quotes, and last but not least, I would like to get some feedback. So I will welcome questions and answers. So, problems: it seems we have some.
[00:01:07] So the goal of this session is to talk about the issues in our test suite, and to talk about those I would like to perform a quick recap of the history. So our initial support for automated testing in Drupal came around Drupal 6 with the SimpleTest module, that then landed in core in Drupal 7, and it was mostly focused on UI testing. Sorry, let me adjust this. Drupal core at the time was mostly procedural, and the reception of this new automated testing framework was exceptional. Everyone was excited about it, and we started basically adding test coverage for anything that moved. So yeah, what could possibly go wrong?
[00:02:01] Well, let me answer with a couple of quotes. This is from Nat Catchpole, that a lot of you will know. So: "When we first added SimpleTest, we opened dozens of issues to add coverage for everything we could think of, from entity CRUD to random admin forms, by whatever means necessary, and this allowed us to build up a lot of test coverage, but also an unmaintainable tangle of tests where most of the coverage was implicit or overlapping." Yeah. And the next one is from someone else that I take very seriously. So, yeah, I won't read it all, but it's basically saying that running tests was fairly slow.
[00:02:46] And so, what about now? Yeah, let me restate the obvious, but we've come a long way. We have PHPUnit since Drupal 8. Most of our code base is object oriented, so shout out to everyone that worked on that. We have very advanced tooling. We have several base classes, test traits, GitLab CI that's doing an amazing job. But our test suite in itself has not been improving at the same pace, I would say.
[00:03:15] So basically, among the tools, the most important ones are the base test classes. We have, just to recap, UnitTestCase, which is a thin extension of the basic PHPUnit test case class, and then we have kernel tests that build an entire kernel and allow you to pick and choose the subsystems you want to enable to perform your tests. You have browser tests, or functional tests, that are actually relying on an entire request response cycle through WebDriver. And then we have functional JavaScript tests that are also relying on WebDriver and are used to exercise the actual JavaScript in our UIs. And then we have the build tests that are a special case. They are mostly used to test whether our packaging is actually working, so our ability to install Composer and so on. And I sorted this more or less by speed. So the fastest ones are the unit tests, and the slowest ones are, I would say, the functional tests. Build tests are kind of an exception.
[00:04:30] So why am I saying that our codebase, our test suite, is a bit problematic? Because back then, when we started adding automated test support, we introduced this test gate that was mandating that any change landing in core would have test coverage, regardless of it being a new feature or a bug. Everything had to be tested, but the guidelines stopped here. So there was no clear direction about which kind of tests we should produce each time. And so this basically resulted in people going with either what was available or what they were most familiar with. Or they would piggyback on existing tests, which in itself is not a bad thing, but it depends on the tests available. So by now we have a lot of these cases. We have a lot of tests that were inherited from the original introduction in Drupal 7, and so we have a big abundance of functional tests, that are among the slowest ones, and many new ones are also functional tests.
[00:05:42] So what's the result? Well, this is the result. So basically this is showing you the breakdown of test classes by test type. So we have unit tests at the bottom, kernel tests, functional tests, and then a very small amount of JavaScript and build tests. And why am I showing you this? Because in a moment we'll look at different shapes that I think are more ideal and are where we want to go. If you are here, you may have heard about the test pyramid, and yeah, a pyramid is the ideal shape here. So, we are not there yet.
[00:06:26] So, another quote for you. This is Adam Fenroxima, and this is an excerpt from an issue where we introduce best practices for AI agents regarding the generation of test coverage, and it's basically saying, and it's fairly accurate, that at the moment in Drupal, unit tests, so the fastest ones, are the least useful kind of test. And this is the harsh reality, actually. So it's not what I'd like things to be, but it's basically our reality, and the more tests are generated through AI, the more this reality is going to be reinforced.
[00:07:10] And why is that? I think the main reason why we don't have a lot of unit tests, we are not able to leverage unit tests as much as we could, is because a lot of Drupal code manipulates entities, but entities are very hard to unit test because they bring a lot of baggage with them. And so the moment you try to instantiate one in a unit test, you find yourself mocking your mother and her dog, basically. But does it have to be this way? Well, let's have a closer look.
[00:07:43] So tests ended up being slow to run, and that's mostly locally, because Drupal CI is nowadays very fast, but is also very expensive. You will be able to read details about that in Nat's blog, that's linked there if you download the slides. So basically, I think between 20 and 25% of the Drupal Association budget is currently going just into running CI tests on drupal.org. And aside from that, the tests are often redundant, because there's no strict coordination, and so many tests that rely on functional tests or kernel tests may be testing overlapping functionality. So we are actually testing over and over the same code paths in different tests, and we may find inconsistent granularity. For instance, we may have a functional test that's testing an entire form, and then next to it another functional test that's just testing that an option in a select is behaving correctly, because that was added to fix a bug. Okay. So the DX and maintainability in this light are not exactly ideal.
[00:09:00] So yeah, another quote from Nat. This one was about an issue that basically resolved a long-standing problem, because the only way we had in UI tests for test users to log in was to go through the login form, and that actually required three requests, and now with this change we went down to two, which is still quite a few, but it's much better. So yeah, and this was running on every single commit. And there were, I think, many, many tests that were doing this. I'm not sure whether the 300 figure is correct or is... [laughter] Okay.
[00:09:45] So basically, yeah, this is what I'm talking about with redundancy. So basically we have the explicit redundancy that I just talked about with the login example, but we also have implicit redundancy. So whenever we are testing at the wrong level, and we'll talk about levels in a moment, but meaning that we are testing our code at a much higher level. So let's say we are testing our code path through a functional test. Let's say we're very far from the class that we want to exercise. It's implicitly testing all the steps in between the request and your code, so the entire stack, and so that's also implicit testing.
[00:10:29] Another example of implicit testing is when we are enabling modules just to autoload classes. I'm not sure everyone knows that you don't need that in unit tests. Since unit tests use a bootstrap, well, all tests in Drupal use a bootstrap script that autoloads every module class, you don't need to enable a module to actually have access to the classes. You can just call them in your test. And so enabling a module just to do that is basically exercising the whole module extension pipeline, and it's basically testing this over and over for no reason, because it's not what your test is about.
[00:11:10] And then we are often needlessly relying on persistence. I've seen many tests where the entity is being saved even if it's just been manipulated. It's just been dealt with as a data object, basically, and so it doesn't need to be actually stored. And then that alone is a reason for picking kernel tests. And we'll see in a moment that if you don't need persistence, there are other ways that are much more performant.
[00:11:40] Sorry. Okay, here we are. So here's the potentially controversial part of my session. So I want to present a potential solution for both problems, both performance and maintainability, and this is the test pyramid. So in my opinion, this is the right tool to catch two birds with one stone. So when I'm talking about the test pyramid, I'm talking about a very established concept in IT. So it's basically defining a few levels your tests can be organized by, and there's no widely accepted distinction. Every ecosystem has its own. But let's not focus so much on the labels. Let's get at the concept.
[00:12:33] So the top is about end-to-end tests, so tests that exercise your codebase through its entire scope, let's say. So in our case, in the case of Drupal, it would be a request response cycle. Then there are integration tests that are about testing subsystems together, let's say. I'll be more precise in a moment. And you want to have a reasonable amount of those, whereas end-to-end tests would be just the bare minimum to exercise the critical paths. And then you have a multitude of narrow unit tests that test everything in your codebase. So if your codebase is object oriented, you can do a lot just with unit tests if the code is organized properly.
[00:13:25] So, yeah, we just went through this. So one more thing about integration tests is that it's not only subsystems, but also about crossing boundaries. So for instance, if you're just testing your own code, but your code is actually dealing with persistence, it's basically crossing the process boundary. It's going outside of the process. It's reaching out of the process. For that you need an integration test. And yeah, unit tests, as we said, are within process, are in memory. That's why they are very fast. And they can come in two flavors mainly: solitary, which means that the only instantiated object is the subject under test, the class you want to test, or sociable. There's nothing wrong with that. You can pull in more object collaborators, as long as they are part of your codebase. We will tell more about this in a moment.
[00:14:27] So, my elevator pitch. If we adopt the test pyramid, we are going to get speed gains, because we will have most of our test coverage on the bottom level, and so through unit tests. And also, if we do it right, we will have a bigger breadth, meaning that we can test more. We can add more test coverage, because as you can imagine, if your class has an if branch, you have two cases to test. If you have two classes with an if, you have four cases to test, and so on. So if you test your individual classes, you're testing two branches at a time. If you let them combine together, the number of test cases to test that code, that you can go through, explodes. By the time you get to the top, you have basically something that's completely impossible to exercise completely. And so that's where we are right now. By no means can we have exhaustive test coverage through the current system. Instead, by adopting the test pyramid, we could have a much finer grained test coverage at the bottom.
[00:15:42] And then we will also gain explicitness, meaning that if you ever tried to debug a functional test, you will know that you will get an error that most of the time doesn't tell you which line you have to look at. You have to navigate through your codebase, exercise your intuition, maybe spin up a debugger and cross your fingers that it actually works with a browser test. And after some time you may actually be able to track down the issue in your class. But if you were unit testing that, the unit test for that class would be failing, if things go right. So you would know instantly which class you have to look at. So the bottom line is that if we are able to push down test coverage as much as we can, we get all those benefits. And this is my elevator pitch.
[00:16:31] And speaking about elevators, this is the shape I think we should aspire to: the test skyscraper. Meaning that, yeah, I'm starting to wonder what (unclear) would think of this, but I think we should aspire to something that has a very wide base, and then the upper layers are much thinner. How much we are able to squeeze, we'll see if this goes anywhere, but I think that's an ideal goal.
[00:17:10] So, are we going to lose test coverage in the process? As I was mentioning before, I don't think so, if we do things right. Actually, we are going to gain test coverage. But yeah, unit tests alone are not enough. I'm fully aware of that. So as I was saying, whenever we are going to test wiring or crossing process boundaries, we will definitely need integration tests, and we will need end-to-end tests for the most critical stuff. So: can my user log in, is my node showing up, stuff like that. We don't need to test an option in a form select.
[00:17:47] So I think this is the most important slide of the session, because one thing I realized in thinking about this is that these two shapes here are very similar, but they hide a trap, because there's no, in my opinion, direct mapping between our layers in the skyscraper and the levels in the pyramid. So they are correlated, but there's no direct mapping. Meaning that you can test, sorry, you can test a form object, an individual class, in a UnitTestCase, even if that's about UI. You can write a DB integration test based on UnitTestCase, not KernelTestBase, and if we have time we will have a look at some code that does that. You can, and this is a recent addition, very exciting, you can test a full request response cycle end to end through a kernel test, not a functional test. And yeah, of course, you can also unit test an object through a browser test, but don't do that. That would take us back to Drupal 7.
[00:18:52] So, the bottom line is that test levels are not the same as test types. Test types are tools we have at our disposal to provide test coverage. Test levels define the scope of our tests, and the two may map to one another in most cases, but may not in many other cases. It seems to me that most of the time these days we're just assuming that if we need an integration test, we need a kernel test, and if we need to test something at the UI level, we need a functional test. That doesn't need to be the case.
[00:19:26] Sorry, wrong scrolling. So yeah, the goal, I think, should be to widen the base. So push down towards UnitTestCase as much as we can. So if we can do an integration test using UnitTestCase, that's great. It doesn't mean that we are going to break the rule and use a unit test where an integration test is called for, but it means that we can still get a fast test, and we'll see how in a moment.
[00:19:55] So, first let's have a look at some code and some numbers, finally. So let me show you this. This is a test trait that I wrote, but don't focus so much on the code. It's not showing up. So don't focus on anything. The window, maybe. Yes, let's do that. This window. Yeah, it works. Cool. So I was saying, don't look at the code so much. This is basically a test trait that I'm going to exercise from different test types, through a tool that I wrote that's called Delteros. And the point is just to give you an idea of the difference of speed between unit tests and kernel tests. So let me see whether I can share. You see this? No, you don't. Sorry. Let me see whether I can add a new one. Yeah.
[00:20:49] Yes. So basically this is now starting that test trait you saw right there, through a unit test based on the createMock native PHPUnit functionality. So this is a thousand iterations; it completed in under a second. Let's go through the Prophecy one. This is still a unit test, using the Prophecy API. Much less than a second. And this is a kernel test. Okay. So, I hope there are no fans of slow cinema in the audience, because I'm not going to sit here. I will cut this short, and we can get back to this in a while. So, let's resume our presentation.
[00:21:44] Okay. So I think that the bottom line is that we should treat our test code the same way we treat our production code. So we try to be as considerate and as strategic as we can in how we are shaping it and architecting it.
[00:22:02] Okay, let's move on with hopefully useful stuff. So if you're not here for the conversation, maybe you will get something out of this section. So let's get our hands dirty. So what can we do to adopt the test pyramid? Right now, I think that we can do work both in contrib and custom code and in core in parallel, and so this section is not so much about core; it's about what we can do in custom code and contrib. So, I'd say we should try to follow test-driven development and GOOS best practices. GOOS is a very famous book, Growing Object-Oriented Software by example, and we should also have ideas on how to deal with legacy code.
[00:22:53] So, let's have a look at some code. No more talking. So let's start with this unit test. This is a core one, to exemplify. Is it showing up? Yes. Cool. So this is a unit test that's exercising an entity, a content entity. So yeah, a bit of boilerplate. And let's have a look at the setup code. Setup code. Setup code. Setup code. Don't read it. Setup code. Setup code. Setup code. Setup code. Maybe it's done. No, it's not done yet. Setup code. Setup code. Yeah, this is the first test. We are around 300 lines of setup. Okay. This is not what I would call a readable test. Okay. Well, I mean, it's setup code, so you could argue that the readable stuff begins here, but I think you get what I mean.
[00:23:48] Okay. So the GOOS book says that the pain we are feeling in just reading those setup lines is design feedback. We should do something about it. But since that's core code, we cannot do anything about it. That's the job of the core entity system maintainers and anyone else that wants to participate in that. Yeah, I'm planning to do something about it if this goes well, but yeah, we'll see. Let's stick to custom code and contrib.
[00:24:17] So contrib code and custom code shouldn't need to worry about this, because normally entities are just used to move data around in your process, within process execution. So if they are used that way, there's a way to handle them so that you can write unit tests without getting all that baggage we were looking at before. And the reason why it would be a good idea to do that, besides the fact that it allows you to use unit tests, is that entities are not domain specific. Everything we write is domain specific, has a goal that's not talking about entities. Even, I don't know, something that's acting at entity level in a generic way, like the Content Translation module that deals with any entity, it's still talking about domain specific stuff. It's talking about translations. So it's not talking about entities, because that's a very fuzzy term that doesn't mean anything for the domain specific logic.
[00:25:26] So the idea is that you should define data in your own terms. Always use your own interfaces, so that you can hide the implementation. Entities are an implementation detail, and by doing that you're hiding entities from your tests as well. So let's look at some code. Okay. So this is an article interface. I'm going to show you some quick code I put together that is implementing a newsroom, so something very common in Drupal. And as you can see, the interface, and don't focus again on the details, as you can see, the interface is not extending NodeInterface. It's just an interface that's speaking your language. Okay.
[00:26:06] And the implementation of course relies on an entity. This is a bundle class. Whenever your node is being loaded and it's an article, this class will be instantiated, and it will implement your interface. But the interface doesn't know about nodes. Okay. It knows nothing about nodes and entities, and so you can write your own code in a way that doesn't take entities into account. So let's have a look at hooks. Sorry for the (unclear). Hooks are an integration point. So hooks will know about entities, but that's the only bit of your code that needs to know about those, because as you can see here, we have a very thin hook that's performing an access check, and then it's delegating the whole rest of the logic to a service of yours that, as we'll see in a moment, is actually working with articles, not with nodes.
[00:27:00] So let's have a look at that one. I should have done this in an IDE. I should have done this in an IDE. But yeah, this is your code. This code is working with articles, not with nodes, and it's reaching out to a service that's also dealing with articles and not nodes. So, by the way, this is just showing a message to editors when an article needs review. Nothing fancy. It's just to give you an idea. And this is the service it's reaching out to. Again, as you can see, it just deals with articles. There's no entity here. See the namespace.
[00:27:39] So let's have a look at the unit test. Sorry. Yeah, spoiler. Let's have a look at the tests. So we have only unit tests here, mainly because we're not covering that hook at the moment. And so, as you can see, the setup is very thin. We don't need to mock a lot, and we can exercise the whole logic without ever instantiating an entity. And we can also, as you can see here at the bottom, use some tricks. This test article is a test implementation of the article interface that's not relying on entities, so we can easily instantiate it in a test. And so this is basically the idea behind it. As long as you can hide entities, you're fine with using unit tests in all your domain specific code, and you will get a big boost in performance.
[00:28:41] So let's go back to the session for a few other words. Of course, nodes, entities, they are stored and loaded. It's not like there is no persistence in our world, and persistence needs to be tested with integration tests, as we saw. So what do we do about that? Same thing. We hide, sorry, we hide entity logic away, so that our domain specific logic doesn't need to care about that. Here we have an article repository that's implementing a repository interface, and it's basically hiding all the entity queries our system needs in a single class. So your code can just mock the repository interface and doesn't need to know about persistence. Okay. The only test that needs to worry about persistence is the test that is testing this class here.
[00:29:36] And so again, let's have a look at tests. This time we have a kernel test. Well, we have two, because we also added the one about hooks. But yeah, this is the code. It's a plain kernel test. As you can see, it's doing very few things. It's not testing a huge range of things, because that's the job of unit tests. This is just testing that things are wired together properly. Okay, so back to the session.
[00:30:08] So the bottom line, the rule of thumb, is that you only mock your own interfaces. You don't mock third-party interfaces. Oh, well, there's an exception. You may have noticed that I mocked TimeInterface somewhere. That's because TimeInterface is so small, so isolated, and so self-explanatory that mocking it is not going to put you in any danger. But what's the danger? The danger, if you start mocking stuff that you don't own, is that you are going to make assumptions about the behavior of those things that you cannot check, because you're mocking them. And so if the behavior of those things changes, your tests won't know. Maybe they would break, but they won't break, because they are just making assumptions about that implementation.
[00:31:00] Okay, that's why. And by the way, this is a rule coming still from the GOOS book, and that rule is saying this because mocking is a very special case of test double. There are various flavors of test doubles, and mocks are test doubles that are actually checking that the mocked object is going through some specific processing. We all saw a mock saying "expect this method to be invoked," and you cannot do that with third-party code, because you are not guaranteed that those methods will still be invoked. So that's why you don't do that. And the other reason is that you cannot act on the feedback. So the idea is that as soon as you start feeling some pain because the test is too hard to set up, you're supposed to refactor your code to make things easier, but you cannot refactor code you don't own. And that's why you don't mock third-party code.
[00:31:58] But you can use fakes. We just saw that I created a test article that was faking the implementation. It was not production code, but it was not a mock. It was a concrete class that was being passed around in the test. That's fine, because we own that implementation, and it's also fine because that's a value, well, sort of value object. It's passing data around. It's not properly a value object, because it has an ID, but it's passing data around, and so there's no logic to override. There's no logic in there. It's just passing that data around, so you can be confident about the behavior.
[00:32:33] I think we are a bit late on the schedule, so I think I will skip ahead a bit. But the idea is that you keep raising fences like this. You keep adding adapters into your code, and by doing that you are basically allowing yourself to test most of your code through unit tests instead of entities. Here in this slide I'm linking to various other code examples that I have somewhere on drupal.org, but I think I will only show you one, because we are a bit late. So I will show you a thin integration test.
[00:33:14] So this is a test that's extending UnitTestCase, as you can see, but it's actually setting up a database connection through PDO in memory. And so basically this runs easily, without all the baggage that a kernel test would entail, but it allows you to test a repository that's not based on the entity system. For instance, if you have a plain table and you build a repository around it, you can use something like this. It takes 10 minutes to write. Let me show you the rest of the code. And, sorry, here we go. The trait. This is the trait. So it's just creating a SQLite connection in memory, and you can use that in your test.
[00:34:11] And you can, as you saw, sorry, where is it? You can test something like this. I think here, here we go. We have an example repository that's just working with a database schema and plain SQL tables. If you have something like this to test, you can write an integration test. It's an integration test, it's crossing boundaries, but it's relying on UnitTestCase. It's not relying on a kernel test. Okay. And so this will run much faster. This runs as fast as the tests we saw before.
[00:34:48] Okay. So next one, let's go on. So, as we know, we live in a perfect world, and refactoring is not always an option. We don't always have the budget or the will to refactor our code. So if we cannot change our code, and we have entities that are being happily passed around and used as a way to move data around inside the process, between objects, what can we do? If you stick to interface type hints, well, we all do that, so it's not this big advice, but if you don't hardcode Node in your method signatures, you can use this Delto library. Sorry, I wasn't meant to click this. It's just the blog post where I present this.
[00:35:36] This Delto library is basically a collection of test fakes, so not mocks, because we are talking about a third-party library, that is allowing you to instantiate something that implements the entity and field interfaces without being entities. And so you can instantiate those in your unit tests without having to rely on kernel tests. And so that's basically the workaround we are using in some of Tag1's projects. It seems to be working fine, but I'm fully aware this is a workaround.
[00:36:17] This is the first feedback I got from a colleague of mine at Tag1, because we have vigorous discussions in Tag1. And so yeah, he was basically reiterating the point. Actually, he's what inspired part of this session. He was basically reiterating the point I was making before, that you don't mock third-party interfaces. And so what I ended up doing was landing on this fake approach. So these are not mocks. They are not making assumptions about the behavior, and the assumptions they're making are backed by contract tests. So there are tests that are confirming that both the fake objects and the actual entity objects are behaving the same way. So if there is any drift, we will catch it.
[00:37:11] And so, again, this is useful when entities are being passed around as DTOs, as a way to transfer data around. It's not useful when your code is relying on persistence. In that case you need a kernel test. There's no way around it.
[00:37:28] Okay. So what about the future? Right now there are a few issues in core that are trying to address the suite performance by converting tests towards less heavy test types, and there are a few that I'm working on myself. So if you guys tomorrow want to join these efforts, you can look for me or for Nat. We may be working on this stuff. I don't know about that; I will be. And then we can also try to improve test performance itself, as I was showing. We can try to introduce, well, that's what I'm working on, actually. We can try to make kernel tests faster, for instance.
[00:38:13] So right now, whenever you add a test method to a kernel test, you are basically doubling its length, because every time you add a test, a new setup is run. And so for each test method a new setup is run, a new database is set up, a new container is built, and this is adding up. So what we are working on is trying to make kernel tests share an environment, and so share a database, a container. And we were going to try and address the problem of code isolation, because of course, without autoloading, if two tests share a process, they are going to load code that may interfere with each other. So the goal is still to have per class isolation, by having a single process running each class, but not a process for each method, which is costing us a lot.
[00:39:10] And so what we should also do, and there's no initiative that I'm aware of right now on this, is try to add more test tools to perform lightweight integration tests, like the one I was showing you before. So the ability, for instance, to have just a module handler available, so that you can invoke hooks from a UnitTestCase test, and just write an integration test relying on UnitTestCase, and so basically check that your module is wired properly, the hooks are being invoked, without having to install the whole container to be able to invoke hooks. This is an example. Not sure it's visible, but that's the idea.
[00:40:00] And so, yeah, part four, the conversation. We have five minutes, so I'm not sure we will be able to say a lot, but the first thing I would like to do is figure out... Yeah, this is what Kent Beck was saying in 2008, that we would have it figured out by then. Not sure about the rest of the world, but it doesn't seem that in Drupal we are there yet. I hope we will get there soon.
[00:40:21] A few ideas on how to make it. So my personal favorite approach would be to improve entity unit testability, come up with best practices for production and test code, create example modules, well, one example module, make core adopt it, so all core modules follow that pattern, update AI best practices, because yeah, you can imagine AI will rely on this a lot. And by the way, AIs that are exercising their logic through kernel tests are much slower to respond than ones that rely on unit tests.
[00:41:02] And so this is my proposal, at least to start this. My idea is that we should have a two-question rule of thumb that starts with the level. So we figure out the scope of the test first, what we want to exercise, and then we try to find the lightest available tool to implement that. That's just an implementation detail. The default would be UnitTestCase for mostly everything, if we can do that. Of course we won't be able to do that, but we only climb up to the upper levels of the ladder if absolutely required, because every ladder we climb costs us in terms of money for the Drupal Association and CO2 in the atmosphere. That's my take.
[00:42:01] So if anyone has questions or wants to share thoughts, this would be the time. I tried to provide a few suggestions otherwise. Thank you. Okay, it seems I was convincing. That's great. Thank you.
Event Details
- Conference
- DrupalCon Europe
- Date
- September 30, 2026
- Location
- Rotterdam, Netherlands
- Skill Level
- Intermediate