We're sunsetting PodQuest on 2025-07-28. Thank you for your support!
Export Podcast Subscriptions
cover of episode Mocking in Python with unittest.mock  -  Michael Foord

Mocking in Python with unittest.mock - Michael Foord

2025/2/7
logo of podcast Test & Code

Test & Code

AI Deep Dive AI Chapters Transcript
People
B
Brian Okken
Topics
Brian Okken: 我对 Michael Foord 的生平和贡献进行了介绍,他是一位 Python 核心开发者,对 Python 社区做出了巨大贡献,尤其是在单元测试方面。他创建了 unittest.mock 库,并对 Python 的测试工具的构建做出了重要贡献。 Michael Foord: 我目前从事 Python 培训和承包工作,之前在 Red Hat 工作过,参与 Ansible Tower 的测试自动化。我参与过英国原子能机构的项目,为核聚变反应堆设计软件。我编写了 unittest.mock 库,它源于我在 Resolver Systems 的工作经历,并受到测试驱动开发的影响。我严格遵循测试驱动开发,测试与代码比例至少为 3:1,但过度的单元测试会带来问题。单元测试应该测试行为单元,而不是实现单元;应该通过公共 API 进行测试。mock 的使用存在误解,Martin Fowler 对 mock 的定义过于严格。patch 和 mock 的结合使得测试难以测试的代码成为可能,但这可能会导致编写易于测试的代码的动力不足。patch 的强大之处在于它能够自动恢复被修改的状态。mock 对象可以伪装成任何其他对象。我更倾向于 AAA(Arrange, Act, Assert)风格的模拟,先设置,再执行,最后断言。模拟的经典用途是模拟外部依赖项,以在单元测试中返回确定性结果。unittest.mock 库最初是为了替换 Resolver Systems 代码库中大量的模拟对象而创建的,后来又增加了 API 限制功能。每次使用 patch 都是一种失败的承认,应该尽量减少 mock 和 patch 的使用。mock 应该作为最后手段,应该努力设计易于测试的系统。清晰的层次设计有助于减少对 mock 的依赖。测试策略会影响设计,应该尽量将副作用放入单独的函数中,以便更容易测试。在 Python 测试培训中,mock 只是其中一部分内容,我更推荐使用 Pytest 而不是 unittest。我通常直接使用 unittest.mock,而不是 Pytest 的 mock 插件。Pytest 的 fixture 机制虽然强大,但过度使用会造成“fixture hell”。任何框架或策略都不能阻止编写糟糕的代码。unittest 的缺点在于样板代码过多,以及缺乏 Pytest 的 fixture 机制等特性。我写了一篇关于软件开发和测试最佳实践的文章,可以在 opensource.com 上找到。

Deep Dive

Chapters
This episode is a tribute to Michael Foord, a key figure in the Python community and creator of the unittest.mock library. The interview, originally recorded in 2021, covers his career, contributions to Python testing, and his philosophy on mocking.
  • Episode is a replay of a 2021 interview with Michael Foord.
  • Michael Foord was a pivotal figure in the Python community.
  • He created the mock library (now unittest.mock).

Shownotes Transcript

Translations:
中文

This episode is a replay of a 2021 interview I did with Michael Ford about the mock library that's part of unit test. Michael was a Python trainer and contractor. He specialized in teaching Python and end-to-end automated testing of systems. His passion was for simplicity and clarity of code, efficient lightweight processes, and for well-designed systems. As a Python core developer,

He wrote part of Unitest and created the mock library, which became Unitest.mock. I just read that from his own introduction on Agile abstractions. We lost Michael in January, and I'm really happy that I got to interview him. It wasn't easy to get this interview either.

I started blogging in 2012 and soon after saw Michael Ford's name show up all over the place with respect to testing and especially with Python and testing, software testing. I asked him to come on the show to talk about unit test in 2016, but at the time he was working on Go and he recommended that I talk to Robert Collins instead, which I did and that was episode 19.

But I still wanted to talk to Michael. I saw him in person for the first time in 2018. I saw him in the hallway and he was wearing a black coat and a black hat. And I think he had goggles on the hat. So I just assumed that he came from a steampunk conference or something. And he may have. I don't remember if I talked with him in 2018. But I did get a chance to in 2019.

He'd written an article about software practices, and I wanted to interview him about it. And we tried to do the interview in person at PyCon in 2019, but the audio just didn't turn out well. We agreed to reschedule. And it wasn't until 2021 that we finally got an interview done. And by that time, I thought it would be great to just talk to him about mock. So that's the focus of this interview. I'm very grateful that I did finally get the interview with Michael done.

Welcome to Test & Code.

The mock library that comes with Python as unit test.mock started with Michael Ford. In this episode, Michael and I talk about mocking, of course, but also testing philosophy, unit testing and what a unit is, and TDD, and even where Michael's towel is and what color. Michael was instrumental in the building of testing tools for Python, and he's got a lot of great advice.

advice about testing. I hope you enjoy the episode.

The thing is, the single O is so much more common. People sort of assume it's Dutch for some reason. It's not. It's just English. Back in the days when we used to have telephone books, there was always a few in every telephone book. So, you know, not common, not rare. Whenever I see your last name, I don't know if it's spelled the same, but I think of Ford Prefect. But did he spell it with one O or two? He spelled it with one O's. There's also the Hoopy Frood in English.

the work of douglas adams and i i love that i've always felt sort of an affinity with those works both because of ford prefect and because i am the hoopy fruit who knows where his towel is definitely so do you have your towel with you i don't have it with me but i know where it is yeah where it is yeah it's purple and it's upstairs it's one of these microfiber ones so you can pack it really small for traveling it's great perfect

So one of the things, okay, so we want to talk about mocking eventually, but what are you doing now? Do you have like a consulting company or something, right? Yeah, contracting and training. Training. So I started training Python, doing Python training about 10 years ago now with David Beasley, teaching his practical Python and advanced Python mastery courses alongside Python.

My regular work, you know, I just do it a couple of times a year and I really love doing it and enjoying it. And it is a good trade to be in. And then I worked for Red Hat. My recent career history is that my last job was with Red Hat on Ansible Tower, their enterprise web application for managing Ansible and for managing computer infrastructure with Ansible. And I worked on the test automation team there, helping them build out Ansible.

I worked for them for a year and I felt I really wanted to go self-employed. It was time to do that. And so I've done half contracting and half training and I really enjoy that. I still teach David Beasley's courses. I've also taught some flask and some testing and everything.

Object-oriented theory for the Royal Ordnance Survey, object-oriented theory with Python. They didn't just want a Python course. They wanted something a bit more fancier because they're the Royal Ordnance Survey. So I did object-oriented theory with Python. That was great fun. Contracting, my most interesting project has been for the United Kingdom Atomic Energy Authority. And that was from January to June last year. And that was working on software for designing fusion reactors. Oh, wow.

So normally it's like, it's like a dream job. He's actually a PhD guy and it was his PhD project. So, but so he'd already committed to make it open source before he sort of accepted the job with that, with the UK. So they are going to make it all open source. It's blueprint. And I worked with him on turning it into an engineering project. He had a, uh, and at one point, I mean, it was phenomenally clever guy. I mean, I was literally actually removing descriptors off Metaclass, um,

No, removing metaclasses off descriptors. Which, you know, just does not need to exist. But still, you have to be really clever to make as much of a mess as that.

That was great fun. Yeah. And so the normal way that the fusion reactor design is done, the academic world, they're all silo. Everyone has their field of expertise, but there's a whole series of things that need to be done. You know, they want to work out the magnetic containment, the plasma containment chamber. So they've got place, they've got overall size, power requirements, positioning of the magnets. The really cool thing they're doing now is breeder blankets, which

Basically, fusion reactors, you put hydrogen in, you ignite it with a laser, enough heat and pressure to trigger fusion. And then using magnets, you shape and contain the plasma, usually in a torus.

And then this produces a stream of neutrons which hit big metal absorber blocks around the side of the lining the reactor, turns it into heat. And from there, it's normal turbine technology, which is very well understood. What they've done is they found that if they put lithium into these blankets, that the neutron stream bombarding the lithium every now and then will hit the lithium split into two tritium atoms.

atom split into tritium which is the fuel for the reactor so it's a you need enough tritium which is very expensive to produce and radioactive or pretty dangerous an isotope of hydrogen um and once you've got ignition it's then it's then self-feeding in terms of um of the tritium so that's a brilliant innovation anyway you used to design these things by sort of getting your specs sending for the first guy to do the first bit of design he'd take a month

send you back a bunch of numbers, which you send to the next guy. So the whole process took months. So you can't really do an iterative process of trying a bunch of things that way. And this, this guy, his genius is an interdisciplinary approach. And he essentially wrote, uh,

one application that does all of these different shows. And it's for initial design work. It's not the detailed design, but you can now do an initial design with fancy OpenGL drawings, which are beautiful and everyone thinks it's the whole point, but of course it's not. It's all the numbers. It spits out. You can do that in 40 minutes. So you can do iterative design processes. So it is and is going to revolutionize Fusion Reactor design. It was a fantastic opportunity to work on it and so much fun. That's so cool.

Um, did you say that was open source stuff? Well, I, I emailed him recently to ask him if it was open source yet. And he didn't reply to that bit. So they would, they were due to do it before now. So I think the answer is no, it's called blueprint. There are some

There is a paper he's released on it. Yeah. Okay. I was just going to drop a link if it was there. Yeah, I'll be all over Twitter with it when it comes out because it's fantastic to play with. You can design fusion reactors in your living room. I know you've got a site, agileabstractions.com. Is that the...

Yeah, that's my professional site. I haven't added any of the projects I've worked on since 2019 and 2020, I don't think. So it's a little out of date. But that's my professional site. But if somebody wanted to hit you up for training or something, they could go there, right? Yeah. Oh, michael.python.org. Okay. A python.org email, yeah. That's the...

Instant credibility. I got it because I helped out a lot on the web. I was one of the webmasters on various mailing lists, administration. And, um, and yeah, so, and I asked for Michael and nobody else had taken it, but it's a beautiful email address. I'm very proud of it. Yeah. So there was, there's this, there's a Python feed thing. I can't remember where it is. Python. Do you remember what that is? Python? Is that what you mean? Say that again? I can feed. Do you mean planet Python? Yeah. Planet Python.

I just remember that because I think that's the first time I ran across your name because when I started blogging about Python, I heard somebody say, well, you got to get your blog on planet Python to get listed or people won't pay attention to it. And then so I requested it and I think you replied and said, okay, it's there. It was one of the things I was looking after. That was back in what I think of as the golden days of the Python community, back when Python was...

about to explode with the web revolution and Google adopting Python. And prior to that, Python had mostly been used only by enthusiasts, only by people who really loved the language, which made a beautiful community full of passionate people really eager to teach you, you know. And then

Python just exploded. And so back in the day, having your blog on Planet Python, you could get 1,000 views for a blog entry. The glory days. Yeah. Now, then I ran across your name next when I started researching testing stuff. And you had a bunch of testing articles. But then also, your name is attached to the mock library in unit test.

That's right. That's right. Yeah. I originally wrote what's now unit test.mock and maintained that as a library for quite a while. And that came out of my first program and gig for resolver systems in London. I started with them back in 2006 and they were doing all extreme programming. So it was all pair programming, fully test driven development, customer representative doing prioritization, estimations, tracking, velocity,

all of this this kind of stuff we did that rigorously for four years and that was an amazing experience and it yeah it came out of that time and in particular i got a test a passion for testing as a way of ensuring product quality in in programming what whereas before the sort of style of programming that i was used to do knowing that things worked was a real challenge you have to try everything and we can automate so much of that yeah so so yeah that's that i i

I was passionate about Python and I became passionate about testing in that time. And so since, I guess, since then, you still incorporate testing within all your development processes then? Yeah, I mean...

We did test-driven development rigorously. We did extreme programming rigorously for four years. So that was the first thing we do is write a functional test which exercises end-to-end the feature that we're trying to add or demonstrates the bug. And then we start writing unit tests and those are layered. And we had a test-to-code ratio of at least three to one. And

And we, you know, we over-tested in all sorts of places. We had testing coupled to the implementation. So I learned a great deal about testing and the problems. If you over-test, you couple your unit tests, you're testing the implementation, so you're testing your private methods. And then when you come to refactor and you want to change your code, change the way you call your code, your tests tell you nothing useful anymore.

and they're broken because even when you fixed your code again, the tests are not going to show you the right thing. They're testing old code. Functional tests, your end-to-end tests, if you've got good, reasonable end-to-end coverage, even just a set of smoke tests, then you can refactor

And you can be reasonably confident that you haven't broken functionality because with refactoring your end-to-end tests don't change. So I think there's a lot more value in end-to-end testing. I think there are dangers in over-testing. I like to say unit testing is about testing to the unit of behavior, not the unit of implementation. Test through the public API. If you can't test through the public API, then your abstraction isn't right, you know, typically.

These sorts of things help you to avoid over-testing. Scripting tasks I won't test. Adding tests to big legacy projects is also very challenging, particularly in the face of ongoing feature work. And that's something you have to sort of work to incorporate gradually and pragmatically because businesses, you know, you only get paid as a programmer if the business keeps going. Yeah.

So you have to incorporate building in tests and testing to give you keep sanity with the...

the work of maintaining and extending the legacy project. I really like that. I'm going to steal that. Unitive behavior, not unitive implementation. I like that a lot. Because that's what you want to test, right? You want to test it does the right thing. You don't want to test how it does it, because in theory, at least, it would be very much nicer if you could change the how, and your tests still tell you useful things. And if you're only testing through the public API...

As long as you've got the public API right, you know, often you need to change that. Then, you know, you're free to change the internals and your tests still tell you useful things. Yeah. Now, and then, of course, as you know, there's always cases where

You really want to beat up some algorithm bit in the middle. And so there is going to be some tests around that. There are bugs where you can't replicate it without, you know, it's some timing issue or some file system quirk. And the easiest way of replicating is sticking some invalid data into some internal state because you know what triggers the bug. And, you know, that's going to happen.

All generalizations are wrong, including this one. Yeah, okay, I'm definitely going to have to get this one transcripted because we've got a lot of gems in here. Okay, so mocking, you got into mocking and stuff is a natural part of, I guess it depends on the year you were doing it, extreme programming and test-driven development.

But mocking is a little bit misunderstood by a lot of people, including probably myself. And probably me. So, and partly, it's partly your fault. Uh-huh, uh-huh. Alex Gayback blames me. Well, there's, I mean, there's, if people start researching it, they get into things like, well, the article by, I'm going to get it wrong, called Mocks Aren't Stubs.

I can't remember. Ah, by Martin Fowler, yes. Martin Fowler, yeah. The static typing of, he defines various categories and types of mock, ways you can use mock, and he defines them as different objects. And instead of defining them, they're fine as categories, but they're fairly rigid definitions. And I think by his definition, mock is all of his types of mock except a mock.

Okay, yeah, I was curious about that. So I object to his definition. Okay, so in Python, we use the unit test mock library or often wrappers around it. So like PyTest mock is a wrapper around the mock object thing so that you can use. It's basically context managers built around it, which is kind of cool. Also really cool that you can use them as context managers anyway, right off the bat. I didn't know that at first.

But yeah, so... That's an interesting point. And that was an innovation in mock that actually...

that came but it was from a guy at resolver systems it wasn't actually me although i get the credit for the guy called tom i forget his surname oh dear me poor lad good guy who discovered that so patches a decorator patch monkey patches things so you can inject or mock into any you know all sorts of places and the reason alex gainer blames me and i think possibly what you're about to say i might steal your thunders i mean patch makes it possible to test code

that was essentially really hard to test before. So the combination of mock and patch make it possible to test code that's really hard to test, which doesn't give you an incentive to write code that's easy to test.

And code that's easy to test is generally better code. So patch and mock let you disguise the fact that you're writing terrible code. And that is definitely true. But the innovative thing is that patch, patch which puts mocks into place, the thing that it does that's really powerful is it undoes it. It puts things back the way they were before. And monkey patching things is easy. Unmonkey patching them is hard.

And there's a lot of logic in Patch that knows how to do that. So you can use it as a context manager with Patch time.time as mock time. And then anything inside the context manager sees time.time as your mock object, which you can then configure the return values.

But then outside the context manager, time.time is restored to what it was normally. So the scope of the effect of the patch is limited by the context manager, which is what context managers are great for, you know, a visible scope of effect. And you can also use it as a decorator.

where during the function for the inside of the function that's decorated the patch is in place there and this was actually done in python 2.4 originally back before we had context managers so being able and it was tom who resolved the system she realized that the the way that we do decorators is entirely compatible with context managers so patch can do both and patches are an interesting beast and there's actually in context slip now there's

context decorator where you can write context managers that also work as decorators and that came out of what first happened in mock in result at resolver systems python 2.5 bit of python archaeology yeah so if people have kind of missed it so far how do you describe a mock like to somebody that doesn't even know what it is

okay right and this is so a mock object is an object that can pretend to be any other object essentially now the thing that martin fowler defined about mocks and what was common at the time so he talks about mocks and stubs and i forget the another one but the the essential point about a mock is that it record you can record your expectations and then replay those and so you

You create a mock object. You configure the mock object and say how I think this is the Martin Fowler one and the Java mocking libraries and the Python mocking libraries at the time. You create a mock object. You say, I want you to have these methods and I expect this method to be called. It should return this. Then I expect this method to be called. And then you do, you run your code and you hit replay and it throws an exception if it was used in the wrong way.

Oh, okay. So that's the record replay style of mocking. And for me, that puts the, you record your expectations on the mock and then you call your code. And that's us about face. You know, what I want to do is I want to inject a mock into the system and

run some code and then I want to be able to make assertions that it was used in the right way because not all of the things that happened to it might be relevant it might just be one particular thing I want to assert that you were called with this argument I want to assert the mock might even just be going in there just for the purposes of returning a pre-canned value stubbing out a system function that you don't want

called in a unit test that's a very good use of mocking mocking external dependencies to return deterministic results for the purposes of testing to avoid external calls in your unit tests that's the classic and a great use of markings you might you might mock out api calls mock out network calls mock out file calls and there's some some support in the mock library for particularly for files

Yeah, like for instance, I think of an example, which probably isn't very common, but if my test, if I've got a system, I don't know, a logging system or something, and if I find something critical, it's going to email a bunch of people. During the test, I don't want to actually email everybody, but I can make sure that the appropriate call to email the right people is called during the test.

With the right parameters. Yeah, exactly. That's the sort of thing that mockies will. And just to finish off the thought previously, so instead of being record replay style, mock is AAA. What is it? Arrange, act, assert. You set up your code, you call act, which is arrange, then you call your code, which is act, and then you make the asserts. So unit test.mock, what it does is you can configure it so that methods will return values or

or a method call will have a side effect, raising an exception or calling another function, or you can give a sequence of values for multiple calls. All sorts of ways. You can configure objects. They can pretend to be any objects. They can pretend to be a dictionary. You can configure any of the magic methods or all of this kind of stuff. You put it in place, and then all of the calls are recorded on it. And there's some convenient assert methods and convenient call methods. So

The reason I created it was for two reasons. The first thing was that we were doing full test-driven development, and we were creating all of these little stubs objects scattered throughout our code base with a mock workbook, a mock worksheet, a mock cell, a mock row, a mock column. And overall, it added up to hundreds, maybe thousands of lines of code. And initially, I was like, I can replace all...

all of this with a Python object with a done-to-get attribute that responds to every attribute lookup. So the initial implementation was about 30 lines of code, and it was to replace all of these mock objects in the resolver code base. But a requirement from Giles Thomas, who was the CTO at the time, was that we had to have a way of limiting the API. So if we accessed an attribute that shouldn't exist, it would still raise an attribute error. So that's where all of the spec stuff in mock came from.

So that was where it originally came from. It was motivated also by the desire of not wanting none of the existing mock frameworks, all of them in Python. They were all this record replace style, which I didn't like. And there the testing in Python community in Python was particularly close knit and fun. And we got the testing in Python BoF at PyCons, which had a great run for a good number of years. It was a lovely community to be part of. And mock really evolved very rapidly.

with users in the testing in Python mailing list and feedback from them and competition with other people writing libraries. I was full of passion and activity in those days. If any mock library came out with a feature I thought was good, I'd have a new version out with that feature in a few days. I answered every email about mocks on the testing in Python email list.

Showing them how to do it with my library, you know, I blitzed the competition. Well, it's interesting. I'm a sheer enthusiasm. So the testing in Python mailing list still exists, has very little traffic on it. So a lot of people think maybe it's dead, but we still get, I mean, I still pay attention to it and replies go really, you get really good replies if you ask a question there. It's brilliant.

It's pretty good. Now, I probably regret putting this in a podcast because suddenly people will start using it. Start using the testing in Python mailing list, folks. Right. But yeah, we've got some really great, smart people paying attention to it. Okay, so what is your relationship? I'm thinking of this as somebody coming in and going, maybe I should use mocks in my testing.

What do we tell people that haven't used this before? Because there is this, like you described, this test-driven development model, which is, okay, there's two huge, there's classical and mockist, but obviously we're talking about mockist TDD, which means we try to test every function in isolation with everything around it. Right, right, right. The trouble then is you're not testing the wiring between your parts. The great advantage of

The great advantage of doing that is that your tests run nice and fast, but you can have full coverage at the individual function and method level and still have no idea if your system works because the wiring between your components is not tested at all. And the same effort expended just at the functional test level

would give you confidence that the application actually works. The advantage of test-driven development, and the reason it's called test-driven development, the other way of putting it is I often talk about test first. But the idea is that the tests drive the design. And this is what I love about test-driven development, and this is what I take from it, even if I'm not always religious about test first these days. And it's that step of thinking about the design. If you do test first...

The first step has to be not, what's the solution? Let me bang out some code. It's, how do I call this? How ought this to work? What's the best, the right API for me to be testing? And so there's that. It bakes in right at the start. The first bit of thinking is, how should this look like? Because the default otherwise is you bang out some code. You think it works. I'll add a test. And what you're testing is whatever you happen to come up with, not anything.

the best way of doing it. And the other aspects of TDD, the simplest thing that could possibly work, building up tests incrementally, is that by evolving a design like this, so long as you pay off the technical debt of doing the refactoring, incorporate the cost of refactoring into your estimates, that you actually come up with better solutions

by basing your incremental approach on actual usage. So those are lovely reasons to do test first. And as you say, if you're doing test first, if you're trying to maintain some level of isolation, you're going to need some mocks. But I think that's the question then is,

The question then becomes, what is my testing philosophy? And the specific question we're asking is, how do I get started with mocks? And so I think we can answer that much more simply. But we can say, look, the two things in unit test.mock library are the patch decorator, patch context manager, and the mock classes, the mock object. And actually, most of the time, probably patch is going to create your mocks for you.

So first you need to use patch. And this often confuses people. So we can talk a little bit about that if you want. But basically you say with patch and then the location of the object. I'm going to patch out a method on a class. It's module name dot class dot method as mock object. And then inside the context manager, you can configure the value of the mock object. We probably want to say mock method dot return value equals three.

And then after we've executed our code, we simply say mock object dot assert called with an assert. It was called with the right parameters. So the very basics of using patch to inject a mock and making the asserts are quite straightforward. But where should you do this? Well, an obvious place is replace file access with a mock object. You can.

If you patch back open, the built-in open, then you know it was called if you've got your results and you'll save time in your unit test. Patch out calls to time.time with a deterministic, with something that's going to return you something deterministic. System calls, network calls, database queries, anything where you want to

return pre-can deterministic results and you can avoid a real-life network. If you're not testing your database access, if you're happy about the database access, that's covered maybe at the integration test level. Mock them out at the unit test level. Make your tests faster and less dependent on your underlying model. This kind of stuff is the place where mocking can give you a win. Yeah, and these external parts of your system, well, it's really the system under test. And I think

One of the things people don't talk about a lot is the test architecture often mimics the people architecture. Interesting. Well, I mean, let's say I'm working with a database, but I've got a database layer that some other team is working on. I think it would be... And I'm not responsible for the user interface. I'm responsible for this middle layer of stuff. It's completely reasonable to then...

I think that there needs to be system level tests, but as a team, I'm going to, I'm going to like probably stay,

feed my API and stub out my dependencies or mock my dependencies. The principle is sometimes expressed as don't test the browser, which really only applies to web application development. You test the code you own, not the code you don't own, unless you have to. Right. And then the other really doesn't matter what style of testing you're doing, whether you're doing a lot of test from development or unit testing

tiny unit tests or even functional tests, people are going to eventually need to mock out their external stuff like API calls to external services. You're not going to like an example that I'm blanking on his name, but Harry Percival brought up is a credit card processing. You're definitely not going to hit the credit card API unless there's a debug one, but why not just mock that out or stub it out?

So the other thing that Harry brought up, which I thought was a cool idea, is any real third party system, not some other team's system within your own company, but like a third party, like a credit card processing or something that you really want to try to

their API with rapid in your own object or own module so that you have a limited set of API functions or entry points into that service. And then, then that's a natural place to mock or stub out those functions is to hit those. So, yeah. And if your API system is making network calls, then you,

You can test this abstraction layer by mocking out the network calls, the calls to request or whatever, or the calls to their client. So you can be sure that's worked, that's doing the right thing. And then you've got a nice abstraction layer where you can put your mocks and completely replace that bit of the system. So you can test the two layers separately and be confident that it does work end-to-end. I know that the system under test calls the...

the API correctly that we've provided, the abstraction layer that we've provided. And I know that that makes the correct API calls because we've tested that as well. So yeah, I like that approach. The other thing that I think is neat, and I wonder if this is one of the reasons why you get blamed for bad designs, is that I don't really have to understand dependency injection to use mock. Ha ha ha ha ha ha.

Yeah, that was another motivation. In Python, dependency injection typically boils down to adding extra parameters to your function signatures. And I think function signatures are part of your API and messing with those as a way of managing dependencies is not necessarily ideal. I might have softened on that. I kind of think, I sometimes say this, and I think it's a useful thing to say, is that every time you use patch, it's an admission of failure.

You know, mocks ought to be your last resort. It ought to be possible to test your system and the parts of your system. It ought to be testable.

And if you have to replace a bit of it inside the live system in order to test it, then what you're saying is I couldn't design the system in such a way that I didn't need to do this. So working to minimize your use of mock and patch means that you're going to get the best value out of it, I think. Only the situations where mocking is really clearly the best approach rather than making it the tool that you turn to first. Because there's a definite... I mean, I've written code

that whether it's first it does this function call then it does that function call then it does another function call then it returns a result and i've mucked out all my dependencies i've mocked out this function call and the other function call and that function call and now i'm testing my mock objects and not my code right you know and that's crazy you know there i'm that's you've really tightly coupled your test to your implementation then you're testing completely testing your implementation details that's not a useful test you know really

So let's take that a bit further. So let's say I have, in order to test my credit card processing part of my system, I have, I've been using mocks or something with that. What's the alternative? How would I? I think having cleanly defined layers helps. So you have a single point.

I mean, the other thing I might do is do this via config files, you know, or have another mechanism that loads a mock part of the machinery in place when I'm running under a dev config. You know, it's like,

I want to be able to guarantee that when I'm running my tests, I can never send a credit card request. So I don't want to inject live things into the live system to make sure it doesn't. I want the system I stand up to not be capable of doing it. So...

particularly for credit card or anything that's privacy or security critical, I would think about having machinery that does that for me. And it might put a mock API in place, and maybe that would use mock objects, but you'd have a shim layer, you'd have some shim machinery.

Well, that kind of reminds me of the database stuff. So one of the things that people often do in testing is to replace the live database or a file-based database with an in-memory database. Right. Exactly. Yes, yes. But a lot of the databases like Postgres and others have a memory feature, so you can just use the live database and just have it be in memory if you want, for instance. Yeah.

Any warning signs to give people? I like the idea of like, look at patch first and possibly look at like your external system, like you gave a list, which is good, network, system calls, things like that.

And then the easiest isolating those, like, I guess, uh, making sure that those aren't all over your system. I wouldn't put like requests calls in every file of your, your, have it done in, in a layer, which is much easier to stub out and test and, and have your calls go through that. That's, um, so, and here you're sort of testing strategy starts to influence your design. And I think in a good way. Yeah.

You know, it's like putting the side effects into a separate function. So as much as possible, your functions are pure functions, which are then really easy to test. And it's really easy then to stub out, mock out the bits of your code with the side effects. It's the same concept. If the file writing happens in its own function, then the rest of the function is much easier to test.

This kind of thing, yeah. When you're teaching people, I see on your training site that you do teach a testing training course. How much of that is around mocking, or is that just a small part of what you're teaching people? It's a part. It depends a little bit on the customer and what their priorities are. I maintained unit testing in the standard library for quite a while. That was my other involvement with testing. I added test discovery. I helped break it up into a package.

I think Benjamin Peterson actually did the work on that one, but it was controversial. So I committed it under my name as the maintainer, and it happened. But even so, I still recommend PyTest for new projects, and I use PyTest for new projects. And it's, you know, I will teach people to use unit test, and there's often a section in courses I teach in general on Python. But generally, when I'm starting a new project, it's PyTest I turn to, and it's PyTest I teach. So do you use the...

If you're using PyTest, do you use the PyTest mock plugin or just use unit test mock directly?

I tend to use unit test mock directly. That's possible usually because I teach mock as a separate section, you know, just because, you know, you can spend a day or two days on mocking depending on how much of the API you want to learn to use and how many different scenarios you want to cover. And, you know, and I'm quite fond of unit test dot mock, but I'm not sure. I'm not sure that's necessarily best practice with my test. Okay.

the so i when i when i'm reaching for it i usually use the the the pytest mock plugin but it has like this mocker object that you can pull in and stuff like that but anyway the it's a it's a fixture right yeah it's a fixture that my one problem with pytest is how easy it is to get into fixture hell and i i even worked at one company where they they had a

culture or convention of providing stuff via fixtures so you would use fixtures instead of imports

And so you'd look in your code and like your ID no longer has a clue where things come from or where they are. And they've got fixtures taking fixtures, returning fixtures, and you're trying to work out where something happens. And you're following this graph of fixtures that you're up to the eighth level. And you're like, why? Why? You know, my life has descended into hell. So, so pictures are fantastic.

for, you know, limiting the scope of stuff. And I love the scoping, you know, but it's like you use them sparingly. You know, there's like imports are great. Use imports, not fixtures. Well, okay. I'll disagree with you on that. Fixtures are great as well. I've just been in fixture hell. But also just a reminder that there is no framework or strategy that can prevent you from writing really crappy code.

Oh yeah, you're too right. Maybe Scala or Haskell.

Well, okay, so one of the things that you hinted at, I just want to inject this in here, dependency inject this into the conversation, that with PyTest, you can put fixtures either in your test file or in a conf test file, and you can have one conf test file for every directory in your test structure. Oh, that was the other thing, wasn't it? It's like this fixture I'm using, where the hell does it come from? Yeah, you can have... And then you've scattered across your code base like...

And your fixtures can depend on any other fixture that is anywhere in its parent hierarchy. But I recommend people in a project to have one conf test file at the top.

I love that feature. It's like if I put a comf.py at the root of the project, PyTest knows what the root of my project is now and a whole bunch of other things just work. It's one of the nice things about PyTest. And that also helps people if they know if my fixture isn't in the file I'm looking at, it's over here. Just helps your whole project. Yeah, PyTest is really flexible. But the...

When I started looking at all these things, I did look at unit test and I have used unit test some. I actually was annoyed with a lot of the people's complaints of unit test. Because the thing that people say is it's too much boilerplate. And with test discovery added to unit test, you don't have to do the name equals main thing in your file. There's not a lot of boilerplate, I don't think, but

So, yeah, I mean, it used to be if you used unit tests, you had to write test collection yourself and test running. And there's a test result. There's the test collectors. There's a whole load of machinery. And really...

test discovery was the minimum to kind of bring it up to the sort of a usable level for projects. A lot of the criticisms I hear come from the... It's a port of like JUnit version 5 or something, and JUnit evolved a huge amount and UnitTest didn't. So we have the non-PEP8 style naming, and PyTest has tests as functions, whereas with UnitTest you inherit from test case, and you write test methods and

and you use the assert methods instead of the assert statement. And honestly, that's the boilerplate people are talking about generally. And it's like, yeah, I like writing test functions for PyTest, but I also like grouping tests together in classes.

you know so i don't mind grouping tests together in classes i don't mind the assert methods i was suspicious for years of the assert the pi test assert magic rewriting because the code if you get a you know the code that runs the test room they've rewritten the the byte code magically so they can tell the the intermediate values at all the points so you can tell you that the values of the variables in the expression that failed and that's amazing but it also means that you

you know, you're dependent on them having got it right. But like, yeah, after a bunch of years, I was convinced they got it right enough to let go of that. And using the bare assert statement is great for tests is great. It's, yeah. But yeah, unit test is fine. You know, it works fine. It's a common style of testing that everyone's used to. And really the boilerplate boils down to, well, you need to inherit from test case and you call assert methods for doing your asserts. But, you know, not very well.

The main thing is it doesn't have PyTest fixtures. Right, it doesn't have fixtures, parameterized tests. We have subtests in unit tests, which are a nice addition that not everyone will be familiar with. And so that's a context manager that allows you to have a bunch of tests

in a subtest that each can have a separate name and tell you which condition failed with which parameters. So yeah, that's nice in unit tests, but there's a bunch of stuff. PyTest plugins are really pretty easy to write. I did some contracting for a firm called GuRock, and they have a product called TestRail. And I wrote a PyTest plugin which reports for them, which reports PyTest test results and records them in TestRail. And that was...

way easier than I expected it to be. That was great to do, you know, and unit test is much harder to extend. And really a consequence of using inheritance as an extension mechanism, common for frameworks to provide something to subclass, but like test results, if you want a test result that spits out XML, JUnit XML, and then you want another, somebody else has written another test result subclass that

orders your tests by time or whatever, you have to use one or the other in unit tests. You can't really compose the extensions. So the way PyTest does plugins with plugin points, I actually had a version of unit test that did this, or unit test two, and I didn't merge that back into unit test in the end. It became nose two, which was popular for a while. But I think that was Jason Pellegrin. Is that his name? It was his project? Yeah, I think so.

It was a one-man project, and he couldn't keep it going, unfortunately, which was a shame. But that made unit tests behave very similar. But I think the way PyTest does plug-ins is right. And it's an interesting point about inheritance as an extension mechanism from frameworks, that it lacks flexibility. It's easier to understand at first, but hook functions do make it easier in the long run.

Hook functions with events, I think that's probably something that we're becoming more familiar with as more of us are doing async programming. It's a different machinery, a different pattern. Well, Michael, I'm just having a blast talking about testing with you. I need to wrap it up, but any calls to action for anybody? Yeah.

Calls to action. You know, or anything, drop your, we already talked about your agile abstraction site. Yeah. Give to the homeless. But the other thing I can say, which is I did write an article, which is up on opensource.com and I'll drop you the URL. You can maybe put it in the show notes. And that's, I think it's something like 30 things every developer wishes they didn't have to learn the hard way.

which is some pithy wisdom about testing and developing my experience over the years. It's quite religious and dogmatic about the testing, but there's a lot of really good points, and some of them we've talked about, and a bunch of them we haven't. So I'll send you that URL. It's up on opensource.com, and I reckon that's an article worth reading. I love that article. Yeah, it's excellent. It's a huge brain dump on people of some decent practices right off the bat. It's good. Thanks a lot for your time, and we'll keep in touch.

Thanks, Brian. I've really enjoyed it too. Thank you for listening. And thank you to everyone who has supported the show through purchases of the courses, both Hello PyTest, the new fastest way to learn PyTest, and the complete PyTest course, if you'd like to really become an expert at PyTest. Both are available at courses.pythontest.com. And there you can also join the Python test community. That's all for now. Now go out and test something.