I guess I'm a "Chicago School" fan. I like testing the actual subsystems, NOT a mocked version "abstraction." Doing so, will sometimes find bugs you otherwise would not find. A concrete example from a job long ago: My subsystem had inputs from various places, and would modify them in certain ways, then pass them on to its caller. I did NOT have full control of the input, as humans were involved. Using "focussed randomized data" for input, my tests ferreted-out just about everything as Jenkins did its thing every-hour-on-the-hour. Occasionally, after several days of good builds, a failure would happen in my subsystem. An edge case was detected, and I could mitigate it. Later in the project, ZERO errors in tests in my subsystem BUT the subsystem CALLING mine WOULD have various errors, as it didn't handle what I gave it correctly. MY system was debugging THEIR system. By the time the product shipped, there were ZERO errors reported on both our subsystems after 10 million interactions. Ours were two of only a very few subsystems that reported zero defects in the first six months.
In my latest assignment I started to appreciate unit tests even more. Despite not practicing pure TDD, I tried to think about producing different states and assert the results with either data or errors. To me tests give you feedback on your design. And when you refactor the code, you have a test guiding you.
@@tongobong1 I don’t subscribe to any particular approach. But generally, I start with an interface. It simply depends on my knowledge about what I’m doing. I have no fixed focus. Just an idea of something fitting together with another part. The expected behavior will change as I write code. I would like to avoid dependencies whenever possible. Mock as little as possible. I have experienced code not being written for testing. Not having single responsibilities - taking multiple dependencies that make it hard to mock and thus difficult or even impossible to test.
@@marna_li top down from interface is a good way to start. Just don't let the top down approach spoil the design of your project. You should remove dependencies from lower level business logic as much as possible and put them to higher level simple controlling logic like GUI...
it took me a while to grasp the london school. the problem i had was, when a mock returns a value, it is basically duplicating knowledge which is in the mocked object’s implementation. so the test implementation doesn’t just depend on the unit under test, but also on the objects being mocked. which can turn refactoring into a nightmare. with chicago school your test implementation only interacts(directly) with the unit under test. (and to counter the question: but when it fails, how will i know where the bug is? answer: in the change you did a second earlier. because: tdd!) when i finally found „growing object-oriented software, driven by tests“ they solved it with the „tell, don‘t ask rule“: methods should not return values. and i was like: a weird way of writing software just to do london school tdd? a few months later i found a talk by alan holub, where he says that in object-oriented programming messages between objects don’t return values. (wait? what?) because it would counter the information hiding principle. instead of asking for a value, tell the object to do something. for alan kay each object was like a separate computer, communicating with other computers using asynchronous messages. sounds a lot like event driven microservices, erlang or the actor model. it’s also interesting to listen to what „the grandfather of microservices“, fred george, has been able to do with this approach. so after 30 years of doing c++ i am suddenly beginning to learn what object-orientation is. :)
Interesting! I feel the pain of tests that only test that I wrote my code the way I wrote my code. Mocks always lead there for me. So I'm looking for ways to get rid of them. Do you have documentation/videos you would recommend to learn the tell dont ask approach?
i think it was in here ruclips.net/video/0kRCFVGpX7k/видео.html it was just a short mentioning within a long talk. the book i now found is fowler’s “refactoring” book. and i even discovered that i also completely ignored the “use polymorphism instead of switch-case” in the clean code book because i failed to see it in it’s context.
Your analysis is very good. There is indeed a connection between the London school and the "pure OO". I'm still on the journey of discovering this though. I've practiced TDD, then learned about OO mostly from Yegor Bugayenko, who has very good explanations on how to practice true OO in Java. My guess is that with the london school of design, you can create an OO solution faster. Whereas Chicago may or may not lead you there, it's up to you.
Thank you so much for your video. After several years of tdd i've found that for me the best approach for tests on undeployed application is: 1. Chicago school for the central layer (the one with business logic: domain layer + application services) with tests doubles instead of third layer adapters (repositories, clients, publishers and so on) 2. Adapters in the first and third layers are tested in integration testing manner with the real technology (in-memory database, wiremock, spring webmvc for controllers, testcontainers and so on). In the first layer tests all the interactions with lower layers are mocked out so we test only technology integration. This approach solves several problems: 1. Test doubles don't prevent refactoring: they are only between layers. Behavior usually doesn't move from one layer to another because central layer is only about business behavior + orchestration and border layers are only about interaction with io-technology. 2. These tests are really fast, especially the central ones that are always in memory. Adapter tests are usually not so fast but still it is much faster than deploying the whole application just to try simple idea how to fix something that can be wrong.
The comment about excessive mocks is spot on! I work with some products that are legacy where tests were added in later and new products where we practice TDD. The consideration of testabilty has lead to better designed code.
It feels a lot like a comparison between unit testing & integration testing. But at @14:40 Dave is clearly talking about unit tests... that are testing more than the units themselves.
I practice what we call an outside-in approach, which sounds very similar to the London approach. We create high level acceptance tests first. As abstractions are "discovered" we write tests for each "unit", mocking dependencies where it makes sense. While not all colleagues agree with my definition, to me a unit is a concrete implementation of a single contract/interface. I also follow the practice of abstracting edges and testing only my code and leaving integration for acceptance tests, which can run with both text doubles and actual dependencies.
If ATs can run as a part of CI (at least well enough to give you high level of confidence in your code), then in theory duplicating those tests with similar Unit Tests should not be needed. But usually ATs run during CD only. I always made my ATs running on local at least to boost the confidence in the code, but I did not make them run during CI (only during CD). I almost always felt that lower level unit tests were not needed then, except of those which tested aggregate roots (in DDD). But I guess more unit tests would sometimes help in case I forgot to run ATs on my local. It's also a bit more convenient and depending on your setup also faster to run unit tests locally comparing to ATs. My run in 20 sec for hundreds of tests so I did not complain. I wonder what number of unit tests Dave would write when ATs were easy to run on local and gave high confidence in that code works.
I start with interaction testing and then add state tests. At that point I delete the interaction tests as the interaction is implied once you have state tests.
One issue I have with mocks, is that the expectations of the abstraction are fragmented and duplicated in the tests. For example, if abstraction has method put() and get(), the user of the code has expecation is that putting stuff in would mean getting it out later. But with mocks, this expectation is not clear, because from perspective of mocking framework and tests, these methods are completely unrelated. With fakes, this expectation can easily be made clear in the code of the fake itself. And tests must all pass when running against that expectation. And when we change expectations in the fake, the tests should fail when that new expectation would conflict with code being tested. With mocks, when expectations against the abstraction changes, you need to analyze all tests using mocks to verify those mock setups are correct under the new expectation. Something which is tedious and error-prone.
In my opinion, if you have a dependency that utilized put() and get(), you should *never* mock that at all. Because state is not an interaction, and mocks serve to test interactions. And, quite frankly, If I have a *state*, then I should not care how often that state is being called, I only need to know that it is the state that is there. So - don't mock state. In response to the second part, I see every interface, every interaction as an API, meaning it needs to be designed, documented and maintained. And this also means that, if I *change* the behavior of my API, then I *must* check every interaction anyway. Yes, this is faster with fakes, but as soon as you have a multi-module setup, keeping the API in module A (say, program-api), the implementation in module B (program-implementation) and fakes or similar in module C (program-fakes), and then expose the API to another module (say. program-processing), you also need to pass the fakes-module to said processing module, to allow testing that with the automatically updated fakes, which is already more entanglement than necessary. and should the API be exported to another proejct *outside* of the program-multi-module setup, this gets even more tedious. So while there are upsides to using the "common fake" approach, defining an interface, describing its intricacies (like not accepting null, for example) and treating it as a full public API member can already save a lot of work, as one tends to think a lot more about the "correct" design and use, when considering that the API is public and changes are expensive.
I also tend to bounce between London and Chicago. If I were testing a concrete object like you suggest, I might model the state such that if I put() something, then I expect to be able to get() it. However, if I am using that abstraction as a mock in a different test, then I would not. Functionality that I am testing from that perspective would either have expectations on get() because I need a value, or expectations on put() because I need to store a value, or an expectation on get() followed by an expectation on put() because I need to update a value. If I want to put() then get(), then that is a smell that I am testing too many states at once.
Just use as few mocks and other test doubles as possible and don't use any mocking libraries and don't write London style unit tests because they are terrible idea.
Don't know what's better, your content or your shirts :) Thanks for helping me be a better engineer. I've managed to successfully use a few of your videos to argue for better practices at work.
I'm a QA/Tester and I'm about to start doing exactly that. I've already sent the Dev Lead a selection to go through so he can spread the word through the department.
When adding new features, I always start with Chicago and then see how often I need London. London helps me to question my design if I need to mock too much. London helps me also to focus on the new logic I actually want to test. The final safety net is an integration/acceptance test that verifies the system as a whole. So it is actually a combination of both that helps me create better software.
I just use mocks for things that do IO. Guess that means I do something closer to Chicago? I find I have to put in place test data builders to help keep the testing of the unit flowing … Great video - thanks!
You can test IO with integration tests and keep it separated from business logic that you can test with unit tests without the need to mock anything - if you have a good design.
The best thing to do with London style unit test is to delete them. It is better to have no unit tests than to have bad unit test that London style promotes.
I didn't know that those approaches had a name, I use both but in my daily job, in fact, I use one of them for high level tests, and the other one for low level tests, sometimes a mix of the. XD. Thanks for sharing !!!
Longtime fan, just needed to drop in to say that, almost as good as the *content* of his videos, is the quality of Dave's t-shirts. 👕 Consistently awesome, keep 'em coming thanks Dave 💯
Thanks, and we did a deal with the place where I buy my shirts, so you can get a discount if you'd like some of them: www.qwertee.com/shop?sort=price&klear_link_id=1b56ff4d9b1e07eac057bed68d9e9851&Spooktacular%20Halloween%20Sale%202022&ref=1b56ff4d9b1e07eac057bed68d9e9851
As always with things like this, I am somewhere in between. In my mind, the London school promotes modularity and testability more explicitly than the Chicago school at the expense of sometimes leading to overengineering and over abstraction. But the Chicago school promotes simpler client interfaces and is more feature focused at the expense of sometimes stronger coupling among minor related classes. What is best is a balancing act.
Nicely put. I will try to edit what you said more to my taste :) London school promotes modularity and testability more explicitly and upfront. Chicago school promotes modularity and testability implicitly by giving an option to modularize. London school incentivizes deciding on your abstractions early, increasing the risk of getting them wrong due to insufficient data initially. Chicago school lets you design and change your mind later, based on evidence, increasing the risk of forgetting to do it at all.
I´m just doing TDD like validating data types on python (which is not really pushed in Python 🐍) and now I know there is more into it than to test assertions. Oh man. This is getting complicated really quickly.
Interesting video. Looks like I would call Chicago unit testing and London integration testing. Where I work we end up with both but lead with unit tests. I never thought of leading with integration (London) tests as part of TDD so that's an interesting idea 🤔. If I were in a situation where the powers that be aren't big on testing (its happened a couple of times) or the code is written in a way that unit tests are almost impossible (also happened to me ) I would go with the London style. As one test can check multiple units so to speak so you get more bang for your time spent putting them together. For untestable code you don't need to mock as much with the London style. But I will have to check out TDD with the London style in the future.
I think you have Chicago & London mixed up in your descriptions? :) Integration testing means different things to different people, check this great article out: martinfowler.com/articles/2021-test-shapes.html To me it means testing across multiple processes. I.e. testing that your code integrates with the actual database correctly, or integrates with another HTTP service over the wire (even if that service isn't the real one)
Don't use London style because it is terrible. Integration testing is testing outside the process like access to database and filesystem and when you test more units of functionality in a single test one after another. Example of integration test is: login, creating a record, changing the record, removing the record and logout in a single test.
This seems like a highly theoretical discussion that only the creators of either approach should be having with each other. For the rest of us....it should depend. If you have a complex piece of code (think threads, caching, expensive invocations, gnarly exceptions) which warrants more mocking (i.e. an unhandled exception is a disaster vs. more of an irregular hindrance) then this is the correct approach. Alternatively, if you have a simple scenario (e.g. CQR) then may lead to less mocking. The problem I have is that if we need testing to design our code/module/solution then perhaps we're going about things the wrong way. If the requirements need some thought, then perhaps a walk, a discussion with a colleague, a hastily scrawled diagram or even some old school diagramming may yield a better result than code first TDD can provide. Can we not live in a world where we can have both?
About mocking, I usually prefer to write my own "mock" code (so implement the BookWarehouse interface with my own BookWarehouseMock) rather than using a library, so the "when"/preparing methods are clearer and easier to use
I would love to see an episode regarding Database mocking. In my experience it's one of the most common pain points. A lot of the code in the kind of applications I work on (enterprise, db centric) builds and runs queries, at the point that we are left with two options, for testing: include the db in the test (in memory?), or build a super complicated mock. Currently, we are using option 3: do not test db code, but I'm not happy with that. Thanks as always for the excellent content!
I am by no means an expert in TDD or databases, but one idea comes to mind. You could build abstractions for the database interactions, a sort of API that the business logic uses to talk to the DB. This way you hide all of the code building queries from the main part of your code. Then you can mock away the API (query-building) logic when testing business logic, and test the real query-building code with integration tests of some sort.
@@ToadalChaos it's a very common suggestion, but it doesn't apply really well to my experience. What you usually end up with is a very anemic db layer that acts only as a persister, because in that scenario you want all your business logic in code. It can very well work, but it defeats the purpose of using a relational database in the first place. The bulk of the implementation of a "query" method (GetSomething) is a SQL query, in my applications. I find those difficult to test automatically, and pointless to mock.
If we are speaking of unit tests of a regular web app for example (controller > service > repository), you should be able to test the controller by mocking all the calls to the services. Then in your services, you should be able to mock the calls to the repository. If all the business logic lies in the controllers, then unit testing makes no sense (there is no unit) and if you try to test nonetheless you'll face the problems you are describing. (edit : if you are speaking about DB queries that would lie in the repository - or mappe r- in my example, then you just need to check that the calls are made and not necessarily the behavior of the DB unless you have specific logic in there which makes things more complicated)
@@Grouiiiiik It's not exactly a regular web app, more like Web API. Controllers should be very lightweight (in my apps they almost disappear). Your edit is spot on: given the power of SQL and my "stubborness" to leave the query logic in the (sql) query, there is no way to build a meaningful abstraction, without falling into the mock traps so well described in this video. In other words: I have a very, _very_ simple implementation, that just runs a query on a SQL db, but I would like to test that the query (a string compiled by my code) does what it's meant to do. Testing just the SQL string is not useful enough. Mocking the database is useless. Using a real db can be tricky. I'm currently evaluating In Memory db solutions, but I'm curious about what are other strategies, if any.
@@robocodeboy Then you are in the integration testing domain, so my best bet would be on containerized DBs (like testcontainers for java). You'll have the responsibility to put the schema in the source and to maintain it if it's not already the case.
I really like the Chicago school, but I noticed that sometimes the integration test gets really huge with output values when I use an entire book for example in a file. I did once use London school on a repo without tests, but found out the tests become brittle so the tests shouldn't test the code and I got better results recently when I wrote my tests based on the specification. It is nice to use mocks with Spring Boot for the rest controller Open Api specs and the repository db calls (before knowing which db to use).
16:10 I would like a video of you doing the pros and cons of: "you could spend your life testing each pipe individidually, or you could simply connect them together and pump water into the system to see where things spill". I believe that, given a minimal pipe reliability and that the spilling damage isn't too annoying, this assertion makes the case for not unit testing stronger than the other way around. (You could say, well "minimal pipe reliability" in that cases seems an awful lot like unit-test-passed, but it could also mean a reliable, battle-hardened software pattern for example.)
The biggest problem with just putting water into the system and seeing where things spill is that it doesn't tell whether the individual pipes do what you think they do - so if you come back and rearrange the pipes later it's hard to know whether they'll continue work in the new arrangement. But I agree sometimes a piece is so simple that it isn't worth testing it, especially if you any likely mistake would be detected as a compiler or static analysis error.
Yes London style tests are too granular and this can lead into huge troubles when changing/refactoring the implementation details so this is why classic tests are far better than London style tests.
OK, so implicitly, if you find that your (in the London school) mocks are horrible, that means that you aren't really KISSing! I'll make some experiments with this in one of my private projects, my semantic parser is the best candidate.
15:59 Would you consider testing those multiple units (which use mocks) to be AT tests or an unit tests? If you considered them unit tests, what would be the mock of UI in the REST app? It is easy to imagine mocking of DB/repository and external coms, but I think there are various ways of mocking UI part and I am not sure what should be used. Some create special test version of the REST controller with some tools and then send real JSON requests to it and receive JSON back. Others create unit tests for the controller and Request's DTO's validation parts, while do the (social) unit tests which envelope logic from command/query layer, through aggregate roots, down to the repository calls which are mocked/stubbed/faked like other externals. But what's the point in having such tests if we have ATs which are even higher level and don't need to use mocks for DB and Controllers? Seems redundant to an extent. Some good explanation of what kind of tests to use at what level would be very useful from you Dave. I don't know if it is going to be in the book I bought. Hopefully. Edit: Maybe you already told all that in other videos. There are so many of them! :) I will check. Thanks for the great content!
Unit tests. In TDD unit tests support the design, which is what the tests I describe here are all about. Acceptance tests are defined as determining the releasability of the change, this is a different problem, and so a different kind of test.
This is my full TDD course: courses.cd.training/courses/tdd-bdd-design-through-testing The free intro is here: courses.cd.training/courses/tdd-tutorial
We used both in a project when we were not very experienced, turned into a mess. I think the London school could be used for outlining the initial design, but when it is done and real implementation starts the Chicago school is more preferable and by the end of development all tests written in London style should be gotten rid off.
"if you're mock is complicated then your design sucks" this is similar with Zen of Python's "if you can't explain well the implementation then it's a bad idea"
London results in SOLID design without requiring a knowledge of SOLID principles. Likewise the result code quality of refactoring in Chicago solely depends on the developer's design skills. In other words, in Chicago everything may stay spaghetti as far as the tests are green.
Hi Dave. Thanks for the videos. I have bought your book (will arrive tomorrow). I hope I will have time and will, to read it :) I have one question. Assuming you use some on cloud database like Aurora and cannot stub/fake it on local dev machine, would you still use some in memory semi-compatible SQL database to run the ATs locally? or connect to in-cloud created (in ad-hoc manner) DB/Aurora with your ATs? or run your ATs only in the CD stage after CI is finished? or use a mix of these approaches (e.g. use in memory DB on local dev env, and then the real Aurora DB during CD)?
What are your thoughts about the argument that edge system mocks largely duplicate integration tests and have a lot of assumptions that don't actually confirm what those outer systems are actually doing? For instance, when people mock a data access layer, an argument could be made that it's more efficient just to spin a up a lightweight container running the DB when possible, as opposed to mocking a lot of assumptions about the system that have to be maintained.
I don't see integration tests as being a fundamental part of a good test strategy. They have a place, but are tactical. My preferred test strategy is TDD for fine-grained, detailed, verification of the code, and Acceptance Testing (BDD style), to verify that the system is releasable using production-like simulations. The acceptance testing is like a super-integration test as well as a lot more, so eliminates the need for integration testing. I also think that choosing to mock the edges of your system, applies a pressure on you to better abstract at those edges, and so you end up with a better design.
I am a bit disappointed at you Dave that after all this time you still didn't find out that London style unit tests are just terrible for projects. Unit as you said is not a class and it is also not a piece or pieces of code. Unit is a unit of functionality and it can be implemented by many objects of different classes or just by a single function. London style is a great example of how NOT to do unit tests. Tests know way too much about internal implementation of a public interface that users can see. You mentioned the injected objects but what about objects that are internally created by a class whose functionality we want to test? Should we mock all those internal objects and should unit tests know about them? Of course not. The question you should ask is what is the purpose of unit tests. 1.Unit tests are protecting the implemented functionality from bugs that can appear after we change the code - extend it or refactor/redesign it. The last thing we want is to get false failures of unit tests because we changed the internal implementation while the final functionality works as before - as expected. 2. The other purpose of unit tests is to get the documentation in code from which we can learn how to use the classes. It is much harder to understand unit tests that have mocks and even worse those that use mock libraries than just plain straightforward unit tests. So London style unit tests fail at achieving both goals. I can tell you that the best programmers at the top software companies like Google and Facebook don't write London style unit test - only "classical" unit test with very few mocks - less than 5% of unit tests have test doubles. Finally if there is piece of software that is hard to test with classical unit tests like web app that is using database then this is a sign of a bad design. We should separate business logic from database access right? We don't need mocks to test business logic that is properly separated from database or filesystem...
I am afraid it is not that simple, the "the best programmers" in other places do use the London style. You criticism is true if your design is overly coupled, but there are ways to design where the London school works fine, and gives some advantages over Chicago. As I said, I am not wholly in favour of, or against, either one. I think that they are both tools in the tool box.
Actually, Dave, there was a fight; One in which Beck himself took part. It was against that Ruby guy, I think. I think you can find it in video under the name "Is TDD Dead?".
That was about something else, not London vs Chicago. The “fight” was DHH say TDD was bad for design. I think Martin Fowler & Kent Beck were too polite 😉😎
@@ContinuousDelivery I guess I recalled it as a fight between London VS Chicago because, in the end, DHH was criticizing London and Fowler and Beck were defending Chicago.
@@alessandrob.g.4524 Heheh, it went something like: DHH - "TDD sucks because mocks and isolation" ... 3 hours of discussion ... Kent - "This is TDD? I don't do that, too much mocks and isolation" DHH - "Oh...." FIN
This feels like it dosent fit for many things. For instance in ml and hpc a lot of the time the stated interface lies. Usually for a hardware reason. For instance in pytorch u can't use data parallelism with an xpu. Even tho technicly each class should be able to run it. A lot of the time u r forced to test in production because preformance can not be mocked. Hardware can not be mocked. Only the real system on the real hardware with the real workload matters.
Tests at its core is sabotage protection, accidental or intentional. True, there is very little in the way of protecting any code from sabotage. But test is code, it is also subject to sabotage. Having 2 avenues for something bad to happen is... let's just say it can be bad. The entire industry is up in arms for test driven and test automation, but there are plenty of horror stories where this results in loss. Let me explain. 100% coverage is a parallel system (your statement, and its also a correct statement). Simplistically, "X should be Y" perfectly maps to "X = Y". The major difference between the 2 systems is that the tests have a different structure than the code it is testing. But they are 2 different sets of avenues for change. If both systems are build by master craftsmen, those 2 avenues and even the structure will eventually converge along the lines of the business context. There is a case to be made for irreducible complex problems. A unit of code which exhibits complex behavior. I have been searching for this for 15 years. I am beginning to think, code with irreducible complex behavior is just another word for shit code. Probably the best case for TDD is the ability to construct a system with complete disregard to the structure of the final product. This is a massive boost for programmers with less than 10 years of experience. It is like writing an app and rewriting it based on what you learned from the first iteration. This, for someone who does not have a lifetime of shameful experiences is a godsend. Then there is code review. I use that to eliminate both complexity and sabotage. Juniors handle the the fallout from this far better than I expected. Tho', probably I am a different kind of psychopath. I rarely test what the change implies. I usually immediately seek out edgecases provided the change already meets the elegance standards. In vast majority of cases is just a single glance but I often spin up the code and break it in the most grotesque ways possible. By elegance standards I mean the clarity of how it fits into the rather constricted pattern and the entire system. Can't find a single fuck to give about the number of spaces used in indentations. If you can't read code written in a different dialect you are just illiterate. But I digress. This is just an idea. There may be flaws in my reasoning. My coding career barely reached late puberty.
But what about the implementation details Sir? When we mock, even at the right abstraction levels, we leak some implementation details that will the tests fragile! So even small refactoring to the code for improving it's structure by changing the interaction while keeping the same observable behavior will make these tests raise a false alarm 🚨! Can you talk about this point please 🙏
I think this is really about the quality of the abstraction in design, these sorts of interactions, that you mock, should, intentionally, be more stable points in the design that hide-information, so they are a bit less fragile. You may still need to change them sometimes, but it should be easy. The problem usually comes when you don't practice ideas like "Tell, Don't Ask" in the design, and you end up with a nasty chain of things asking for data. zip = customer.getAddress().getZip() --- Yuk! I talk about some of these ideas here: ruclips.net/video/8GONv6jJsG0/видео.html
Love a video where this applied to client side applications. Single page app, fat client, etc. I’ve seen more dev not leverage some of these techniques since it’s all UI.
I hope Java and C# will someday have something which will allow to not define an interfaces when we only deal with one implementation of it. Though placing interface and the class in one file helps in such case. I would also like these languages to allow for functions directly in the namespace without having to create (static) classes with static methods when someone wants to use more functional (or rather procedural) approaches. It's often easier to think about name for the function while not having to think about a noun for the class it resides in. Not to mention that it is often more natural this way.
I cannot design software without writing tests, because i dont know if and how the software will really behave. As a software tester i just dont rely on my knowledge, i don't trust neither the programming language, nor the foreign code, nor my own code. Unit tests give me some bit of "at least the things i tested for may work the indended way" feeling.
Then you are mocking at the wrong level of abstraction. You still need to test the real thing somehow too. Mocks are how you test the core bits of your code that represent what it really does, the integration points where I/O happens needs different kinds of tests, and better design to isolate one from the other.
@@jimiscott every guru is advocating for mocks and they are useful in small use-cases. But most devs are working on applications that include some kind of database So, if you blindly follow advices for "gurus" about mocks then there is 99% chance that you will hit SQL grammar exception on production :)
Dave, I'm an avid consumer of your channel, but I want to point out an aspect which I've seen very often. At around 16:45 you say that the problem is in the design. I don't think so. The design is just fine. The problem is with the kids who are enamored with their mocking framework and try to test too much (London) instead of staying high-level (say at the boundary of a component or layer), that is, Chicago. I guess my question is: how do you manage to cool down the love heat for the Mock in front of an audience of 30- yo? Just like you, more often than not I see a hideous use of mocks and all the time the excuse for missing the deadline "because we also had to fix the tests". Also, I think we as an industry should introduce by name a new type of test that Feathers (and others) have mentioned in the past, including in one of your recent videos: the developer tests = the tests you write and execute only as a developer; if a developer test fails (after it used to work once), you remove it.
No I think the design IS a problem. If a class has too many dependencis that "need" to be mocked, something is wrong and should be abstracted away. Not sure if I agree on the "maximum 2" argument, but less is more. The mock-love can be reduced by showing the futility of certain mocking behaviors. Specifically when dealing with Mockito, there is the "when(MethodCall)" caveat, as the method call is being executed, so one can create examples that will sometimes fail when being mocked. Another example is mocked state - create a method that accesses the state multiple times, and when called in a different way, accesses it once more. The verifications on that tend to get REALLY annoying as the developers need to explicitly check for verify(mock, times(n)) where n is suddenly changing. Also, explain how "any()" as a verification argument does not work (because at that time, most people tend to verify with "verify(mock, atLeastOnce()).method(any())", which contains 2 levels of uncertainty (because "atLeastOnce" is not a good indicator of a verification, and "any" means that the verification has no value). Basically, demand that every mock _must_ be verified _exactly_. This quickly leads to a lot of overhead which can be avoided by either reducing the amount of dependencies or introducing stubs or using real objects.
In vladimir khorikov's book, if memory serves, he suggests only mocking the things not under our control, a third party API for example (via an abstraction).
@@ponypapa6785 I'm not sure what kind of code you have in mind when you say > If a class has too many dependencies that "need" to be mocked What I have in mind is that mocks are not even needed most of the time, but other test doubles. More often than not, those dependencies are either inputs or outputs. For inputs, you can use a stub. For outputs, a spy. So what you end up doing is things like: put some value in the input from which the SUT pulls its value, does its magic, then stores its output. Then I ask the output what that value is. I don't care about how that result was reached. Because if I did, I would have to refactor my tests every time I refactor the SUT. This is what Dave also alludes to. The only time I care about the interactions is between components with either different owners or different stakeholders who have an agreement in real life about how things should work. That is for instance "external systems not under your control" mentioned above.
You should study London and classical style unit tests in depth and then you should expose London style as a terrible idea that is killing many projects.
Normally, Dave is an absolute gold teacher, but in this moment I gotta stop. At 16:11 dave talks about mocking the boundries. This is probably breaking dependency inversion, you can learn more about it in Uncle Bob's "Principles of clean architecture".
Concerning the Warehouse-Bookstore-Mock-Situation, I have experienced a LOT of problems when configuring mocks this way, specifically as the example given does not at all verify the actual interactions - the implementation could still just return a "new Book(TITLE)" and never even touch the warehouse. As such, I tend to configure my mocks very generally - usually something akin to "doAnswer(invocation -> new Book(TITLE)).when(warehouse).find(any())" and then explicitly verify that the desired configuration was actually called, e.G "verify(warehouse).find(TITLE)". What do you think of this "configure general - verify specific" approach? And yes, this still takes into account that there should not be more than three mocked dependencies in a single class =)
I guess the question here is: why? Going to a lot of effort to prevent yourself from cheating only seems valuable if you think there's a risk you might actually cheat. There are times that you want to check a dependency has been called in a way which isn't obvious from the return, and then verifying it was indeed called sounds like a good idea. But there's a risk of over-programming mocks - you end up with noisy tests more concerned with components other than the system under test, and that complication might mean you make mistakes in your mocking which gives another vector for tests to fail (or pass) incorrectly. Now, it may be you prefer a fake which has a simple implementation - and often you're better off with such simple fakes (a TestWarehouse class, for example) than using a mocking framework. That'll also encapsulate your simple fake behaviour out of the line of sight of tests.
@@TARJohnson1979 Yes, Fakes and Stubs do often help, but in many cases, manually creating something to verify partial behavior (think an interface that requires 10 API methods, of which the current system under test uses 2, which have static returns) can be very tedious and time consuming. This tends to happen in existing codebases that have extractet a lot of "useful" interfaces into "common" modules. The reason why is simple - I had a lot of situations in which the developer expected something to happen, and programmed this expectation into the mock. In this case, the behavior was "accept string containing id, return object". The returned object was then to be examined and a result of that examination was to be returned. He then created a second test and configured the mock in the same way, but the returned object hat a single field set to null. This was to expose that, should any value be null, there would be an empty result object. His expectation on that method was not met, instead, the mock registered a call with a different input, which was unconfigured, so the mock returned null. The code however would only examine the object in question, if it was not null, if it was null, an empty result object would be returned. Which incidentally was what the developer expected. Sadly, shortly after launch, NullPointerExceptions started to happen, because the expected behavior was not implemented correctly - and changing the mock from "accept string containing id" to "accept anything" immediately led to the NullPointerExceptions that were found in production code. Of course, this was not created Test-driven, but I have seen this specific problem in many cases, even in cases where people clainm to have written the tests first. The problem is that, since many java developers are scared of null, they check for that at every turn and thereby hide bad behavior when their expectations fail - a generally configured mock accepts even bad values and will then either lead to exeptions as the value does not fit, or fail the test as the passed value is suddenly wrong. So in my experience, "configure general, verity specific" leads to safer tests when it comes to refactorings and transformations. However it does take due diligence to avoid creating mock-swamps.
Can we all just acknowledge how amazing Dave's shirt collection is?
I guess I'm a "Chicago School" fan. I like testing the actual subsystems, NOT a mocked version "abstraction." Doing so, will sometimes find bugs you otherwise would not find. A concrete example from a job long ago: My subsystem had inputs from various places, and would modify them in certain ways, then pass them on to its caller. I did NOT have full control of the input, as humans were involved. Using "focussed randomized data" for input, my tests ferreted-out just about everything as Jenkins did its thing every-hour-on-the-hour.
Occasionally, after several days of good builds, a failure would happen in my subsystem. An edge case was detected, and I could mitigate it.
Later in the project, ZERO errors in tests in my subsystem BUT the subsystem CALLING mine WOULD have various errors, as it didn't handle what I gave it correctly. MY system was debugging THEIR system. By the time the product shipped, there were ZERO errors reported on both our subsystems after 10 million interactions. Ours were two of only a very few subsystems that reported zero defects in the first six months.
In my latest assignment I started to appreciate unit tests even more. Despite not practicing pure TDD, I tried to think about producing different states and assert the results with either data or errors. To me tests give you feedback on your design. And when you refactor the code, you have a test guiding you.
Yes unit tests are great but you should know how to write them and you should also know that London style unit tests are terrible idea.
@@tongobong1 I don’t subscribe to any particular approach. But generally, I start with an interface. It simply depends on my knowledge about what I’m doing. I have no fixed focus. Just an idea of something fitting together with another part. The expected behavior will change as I write code. I would like to avoid dependencies whenever possible. Mock as little as possible. I have experienced code not being written for testing. Not having single responsibilities - taking multiple dependencies that make it hard to mock and thus difficult or even impossible to test.
@@marna_li top down from interface is a good way to start. Just don't let the top down approach spoil the design of your project. You should remove dependencies from lower level business logic as much as possible and put them to higher level simple controlling logic like GUI...
Throwing a little hype here for this quote: "If your mocking is complex your design sucks"
Super true and great video as always Dave!
it took me a while to grasp the london school.
the problem i had was, when a mock returns a value, it is basically duplicating knowledge which is in the mocked object’s implementation.
so the test implementation doesn’t just depend on the unit under test, but also on the objects being mocked. which can turn refactoring into a nightmare.
with chicago school your test implementation only interacts(directly) with the unit under test. (and to counter the question: but when it fails, how will i know where the bug is? answer: in the change you did a second earlier. because: tdd!)
when i finally found „growing object-oriented software, driven by tests“ they solved it with the „tell, don‘t ask rule“:
methods should not return values.
and i was like: a weird way of writing software just to do london school tdd?
a few months later i found a talk by alan holub, where he says that in object-oriented programming messages between objects don’t return values. (wait? what?)
because it would counter the information hiding principle. instead of asking for a value, tell the object to do something.
for alan kay each object was like a separate computer, communicating with other computers using asynchronous messages. sounds a lot like event driven microservices, erlang or the actor model.
it’s also interesting to listen to what „the grandfather of microservices“, fred george, has been able to do with this approach.
so after 30 years of doing c++ i am suddenly beginning to learn what object-orientation is. :)
Yes, it’s good isn’t it 😁😎
Interesting! I feel the pain of tests that only test that I wrote my code the way I wrote my code. Mocks always lead there for me. So I'm looking for ways to get rid of them.
Do you have documentation/videos you would recommend to learn the tell dont ask approach?
Can you find the name of that talk? This sounds very interesting.
i think it was in here ruclips.net/video/0kRCFVGpX7k/видео.html it was just a short mentioning within a long talk.
the book i now found is fowler’s “refactoring” book. and i even discovered that i also completely ignored the “use polymorphism instead of switch-case” in the clean code book because i failed to see it in it’s context.
Your analysis is very good. There is indeed a connection between the London school and the "pure OO". I'm still on the journey of discovering this though. I've practiced TDD, then learned about OO mostly from Yegor Bugayenko, who has very good explanations on how to practice true OO in Java.
My guess is that with the london school of design, you can create an OO solution faster. Whereas Chicago may or may not lead you there, it's up to you.
Thank you so much for your video.
After several years of tdd i've found that for me the best approach for tests on undeployed application is:
1. Chicago school for the central layer (the one with business logic: domain layer + application services) with tests doubles instead of third layer adapters (repositories, clients, publishers and so on)
2. Adapters in the first and third layers are tested in integration testing manner with the real technology (in-memory database, wiremock, spring webmvc for controllers, testcontainers and so on). In the first layer tests all the interactions with lower layers are mocked out so we test only technology integration.
This approach solves several problems:
1. Test doubles don't prevent refactoring: they are only between layers. Behavior usually doesn't move from one layer to another because central layer is only about business behavior + orchestration and border layers are only about interaction with io-technology.
2. These tests are really fast, especially the central ones that are always in memory. Adapter tests are usually not so fast but still it is much faster than deploying the whole application just to try simple idea how to fix something that can be wrong.
The comment about excessive mocks is spot on! I work with some products that are legacy where tests were added in later and new products where we practice TDD. The consideration of testabilty has lead to better designed code.
Always learning new things from Dave. This channel is absolute platinum!
Thanks 😎
It feels a lot like a comparison between unit testing & integration testing.
But at @14:40 Dave is clearly talking about unit tests... that are testing more than the units themselves.
I practice what we call an outside-in approach, which sounds very similar to the London approach. We create high level acceptance tests first. As abstractions are "discovered" we write tests for each "unit", mocking dependencies where it makes sense. While not all colleagues agree with my definition, to me a unit is a concrete implementation of a single contract/interface. I also follow the practice of abstracting edges and testing only my code and leaving integration for acceptance tests, which can run with both text doubles and actual dependencies.
If ATs can run as a part of CI (at least well enough to give you high level of confidence in your code), then in theory duplicating those tests with similar Unit Tests should not be needed. But usually ATs run during CD only. I always made my ATs running on local at least to boost the confidence in the code, but I did not make them run during CI (only during CD). I almost always felt that lower level unit tests were not needed then, except of those which tested aggregate roots (in DDD). But I guess more unit tests would sometimes help in case I forgot to run ATs on my local. It's also a bit more convenient and depending on your setup also faster to run unit tests locally comparing to ATs. My run in 20 sec for hundreds of tests so I did not complain.
I wonder what number of unit tests Dave would write when ATs were easy to run on local and gave high confidence in that code works.
I start with interaction testing and then add state tests. At that point I delete the interaction tests as the interaction is implied once you have state tests.
Love this channel but especially love a TDD video. Cheers Dave!
One issue I have with mocks, is that the expectations of the abstraction are fragmented and duplicated in the tests. For example, if abstraction has method put() and get(), the user of the code has expecation is that putting stuff in would mean getting it out later. But with mocks, this expectation is not clear, because from perspective of mocking framework and tests, these methods are completely unrelated.
With fakes, this expectation can easily be made clear in the code of the fake itself. And tests must all pass when running against that expectation. And when we change expectations in the fake, the tests should fail when that new expectation would conflict with code being tested. With mocks, when expectations against the abstraction changes, you need to analyze all tests using mocks to verify those mock setups are correct under the new expectation. Something which is tedious and error-prone.
In my opinion, if you have a dependency that utilized put() and get(), you should *never* mock that at all. Because state is not an interaction, and mocks serve to test interactions.
And, quite frankly, If I have a *state*, then I should not care how often that state is being called, I only need to know that it is the state that is there.
So - don't mock state.
In response to the second part, I see every interface, every interaction as an API, meaning it needs to be designed, documented and maintained. And this also means that, if I *change* the behavior of my API, then I *must* check every interaction anyway.
Yes, this is faster with fakes, but as soon as you have a multi-module setup, keeping the API in module A (say, program-api), the implementation in module B (program-implementation) and fakes or similar in module C (program-fakes), and then expose the API to another module (say. program-processing), you also need to pass the fakes-module to said processing module, to allow testing that with the automatically updated fakes, which is already more entanglement than necessary.
and should the API be exported to another proejct *outside* of the program-multi-module setup, this gets even more tedious.
So while there are upsides to using the "common fake" approach, defining an interface, describing its intricacies (like not accepting null, for example) and treating it as a full public API member can already save a lot of work, as one tends to think a lot more about the "correct" design and use, when considering that the API is public and changes are expensive.
I also tend to bounce between London and Chicago. If I were testing a concrete object like you suggest, I might model the state such that if I put() something, then I expect to be able to get() it.
However, if I am using that abstraction as a mock in a different test, then I would not. Functionality that I am testing from that perspective would either have expectations on get() because I need a value, or expectations on put() because I need to store a value, or an expectation on get() followed by an expectation on put() because I need to update a value. If I want to put() then get(), then that is a smell that I am testing too many states at once.
Just use as few mocks and other test doubles as possible and don't use any mocking libraries and don't write London style unit tests because they are terrible idea.
Don't know what's better, your content or your shirts :) Thanks for helping me be a better engineer. I've managed to successfully use a few of your videos to argue for better practices at work.
I am pleased that you like them, thanks.
I'm a QA/Tester and I'm about to start doing exactly that. I've already sent the Dev Lead a selection to go through so he can spread the word through the department.
When adding new features, I always start with Chicago and then see how often I need London. London helps me to question my design if I need to mock too much. London helps me also to focus on the new logic I actually want to test. The final safety net is an integration/acceptance test that verifies the system as a whole. So it is actually a combination of both that helps me create better software.
I just use mocks for things that do IO. Guess that means I do something closer to Chicago? I find I have to put in place test data builders to help keep the testing of the unit flowing … Great video - thanks!
You can test IO with integration tests and keep it separated from business logic that you can test with unit tests without the need to mock anything - if you have a good design.
The best thing to do with London style unit test is to delete them. It is better to have no unit tests than to have bad unit test that London style promotes.
I didn't know that those approaches had a name, I use both but in my daily job, in fact, I use one of them for high level tests, and the other one for low level tests, sometimes a mix of the. XD. Thanks for sharing !!!
That was a clarification I really needed, thank you!
Longtime fan, just needed to drop in to say that, almost as good as the *content* of his videos, is the quality of Dave's t-shirts. 👕 Consistently awesome, keep 'em coming thanks Dave 💯
Thanks, and we did a deal with the place where I buy my shirts, so you can get a discount if you'd like some of them: www.qwertee.com/shop?sort=price&klear_link_id=1b56ff4d9b1e07eac057bed68d9e9851&Spooktacular%20Halloween%20Sale%202022&ref=1b56ff4d9b1e07eac057bed68d9e9851
As always with things like this, I am somewhere in between.
In my mind, the London school promotes modularity and testability more explicitly than the Chicago school at the expense of sometimes leading to overengineering and over abstraction. But the Chicago school promotes simpler client interfaces and is more feature focused at the expense of sometimes stronger coupling among minor related classes.
What is best is a balancing act.
Nicely put. I will try to edit what you said more to my taste :) London school promotes modularity and testability more explicitly and upfront. Chicago school promotes modularity and testability implicitly by giving an option to modularize. London school incentivizes deciding on your abstractions early, increasing the risk of getting them wrong due to insufficient data initially. Chicago school lets you design and change your mind later, based on evidence, increasing the risk of forgetting to do it at all.
I´m just doing TDD like validating data types on python (which is not really pushed in Python 🐍) and now I know there is more into it than to test assertions. Oh man. This is getting complicated really quickly.
Interesting video. Looks like I would call Chicago unit testing and London integration testing. Where I work we end up with both but lead with unit tests. I never thought of leading with integration (London) tests as part of TDD so that's an interesting idea 🤔. If I were in a situation where the powers that be aren't big on testing (its happened a couple of times) or the code is written in a way that unit tests are almost impossible (also happened to me ) I would go with the London style. As one test can check multiple units so to speak so you get more bang for your time spent putting them together. For untestable code you don't need to mock as much with the London style. But I will have to check out TDD with the London style in the future.
I think you have Chicago & London mixed up in your descriptions? :)
Integration testing means different things to different people, check this great article out: martinfowler.com/articles/2021-test-shapes.html
To me it means testing across multiple processes. I.e. testing that your code integrates with the actual database correctly, or integrates with another HTTP service over the wire (even if that service isn't the real one)
Don't use London style because it is terrible. Integration testing is testing outside the process like access to database and filesystem and when you test more units of functionality in a single test one after another. Example of integration test is: login, creating a record, changing the record, removing the record and logout in a single test.
What??
This seems like a highly theoretical discussion that only the creators of either approach should be having with each other. For the rest of us....it should depend. If you have a complex piece of code (think threads, caching, expensive invocations, gnarly exceptions) which warrants more mocking (i.e. an unhandled exception is a disaster vs. more of an irregular hindrance) then this is the correct approach. Alternatively, if you have a simple scenario (e.g. CQR) then may lead to less mocking.
The problem I have is that if we need testing to design our code/module/solution then perhaps we're going about things the wrong way. If the requirements need some thought, then perhaps a walk, a discussion with a colleague, a hastily scrawled diagram or even some old school diagramming may yield a better result than code first TDD can provide.
Can we not live in a world where we can have both?
About mocking, I usually prefer to write my own "mock" code (so implement the BookWarehouse interface with my own BookWarehouseMock) rather than using a library, so the "when"/preparing methods are clearer and easier to use
Yes it is better to write your own mocks and to write as few of them as possible writing classic unit tests. London style is a terrible idea.
I would love to see an episode regarding Database mocking. In my experience it's one of the most common pain points. A lot of the code in the kind of applications I work on (enterprise, db centric) builds and runs queries, at the point that we are left with two options, for testing: include the db in the test (in memory?), or build a super complicated mock. Currently, we are using option 3: do not test db code, but I'm not happy with that.
Thanks as always for the excellent content!
I am by no means an expert in TDD or databases, but one idea comes to mind. You could build abstractions for the database interactions, a sort of API that the business logic uses to talk to the DB. This way you hide all of the code building queries from the main part of your code. Then you can mock away the API (query-building) logic when testing business logic, and test the real query-building code with integration tests of some sort.
@@ToadalChaos it's a very common suggestion, but it doesn't apply really well to my experience. What you usually end up with is a very anemic db layer that acts only as a persister, because in that scenario you want all your business logic in code. It can very well work, but it defeats the purpose of using a relational database in the first place. The bulk of the implementation of a "query" method (GetSomething) is a SQL query, in my applications. I find those difficult to test automatically, and pointless to mock.
If we are speaking of unit tests of a regular web app for example (controller > service > repository), you should be able to test the controller by mocking all the calls to the services.
Then in your services, you should be able to mock the calls to the repository.
If all the business logic lies in the controllers, then unit testing makes no sense (there is no unit) and if you try to test nonetheless you'll face the problems you are describing.
(edit : if you are speaking about DB queries that would lie in the repository - or mappe r- in my example, then you just need to check that the calls are made and not necessarily the behavior of the DB unless you have specific logic in there which makes things more complicated)
@@Grouiiiiik It's not exactly a regular web app, more like Web API. Controllers should be very lightweight (in my apps they almost disappear).
Your edit is spot on: given the power of SQL and my "stubborness" to leave the query logic in the (sql) query, there is no way to build a meaningful abstraction, without falling into the mock traps so well described in this video. In other words: I have a very, _very_ simple implementation, that just runs a query on a SQL db, but I would like to test that the query (a string compiled by my code) does what it's meant to do.
Testing just the SQL string is not useful enough.
Mocking the database is useless.
Using a real db can be tricky.
I'm currently evaluating In Memory db solutions, but I'm curious about what are other strategies, if any.
@@robocodeboy Then you are in the integration testing domain, so my best bet would be on containerized DBs (like testcontainers for java).
You'll have the responsibility to put the schema in the source and to maintain it if it's not already the case.
I really like the Chicago school, but I noticed that sometimes the integration test gets really huge with output values when I use an entire book for example in a file. I did once use London school on a repo without tests, but found out the tests become brittle so the tests shouldn't test the code and I got better results recently when I wrote my tests based on the specification. It is nice to use mocks with Spring Boot for the rest controller Open Api specs and the repository db calls (before knowing which db to use).
Dziękujemy.
16:10 I would like a video of you doing the pros and cons of: "you could spend your life testing each pipe individidually, or you could simply connect them together and pump water into the system to see where things spill". I believe that, given a minimal pipe reliability and that the spilling damage isn't too annoying, this assertion makes the case for not unit testing stronger than the other way around.
(You could say, well "minimal pipe reliability" in that cases seems an awful lot like unit-test-passed, but it could also mean a reliable, battle-hardened software pattern for example.)
The biggest problem with just putting water into the system and seeing where things spill is that it doesn't tell whether the individual pipes do what you think they do - so if you come back and rearrange the pipes later it's hard to know whether they'll continue work in the new arrangement.
But I agree sometimes a piece is so simple that it isn't worth testing it, especially if you any likely mistake would be detected as a compiler or static analysis error.
Yes London style tests are too granular and this can lead into huge troubles when changing/refactoring the implementation details so this is why classic tests are far better than London style tests.
OK, so implicitly, if you find that your (in the London school) mocks are horrible, that means that you aren't really KISSing! I'll make some experiments with this in one of my private projects, my semantic parser is the best candidate.
Now I want to listen to this video over again voiced in that mid-Atlantic accent they used for old black & white Hollywood movies.
That accent was beautiful! Too bad it went out of fashion eventually.
15:59 Would you consider testing those multiple units (which use mocks) to be AT tests or an unit tests? If you considered them unit tests, what would be the mock of UI in the REST app? It is easy to imagine mocking of DB/repository and external coms, but I think there are various ways of mocking UI part and I am not sure what should be used. Some create special test version of the REST controller with some tools and then send real JSON requests to it and receive JSON back. Others create unit tests for the controller and Request's DTO's validation parts, while do the (social) unit tests which envelope logic from command/query layer, through aggregate roots, down to the repository calls which are mocked/stubbed/faked like other externals. But what's the point in having such tests if we have ATs which are even higher level and don't need to use mocks for DB and Controllers? Seems redundant to an extent.
Some good explanation of what kind of tests to use at what level would be very useful from you Dave. I don't know if it is going to be in the book I bought. Hopefully. Edit: Maybe you already told all that in other videos. There are so many of them! :) I will check. Thanks for the great content!
Unit tests. In TDD unit tests support the design, which is what the tests I describe here are all about.
Acceptance tests are defined as determining the releasability of the change, this is a different problem, and so a different kind of test.
Amazing video I will probably enroll on those courses. Which one do you recommend for a TDD noobie
This is my full TDD course: courses.cd.training/courses/tdd-bdd-design-through-testing
The free intro is here: courses.cd.training/courses/tdd-tutorial
We used both in a project when we were not very experienced, turned into a mess. I think the London school could be used for outlining the initial design, but when it is done and real implementation starts the Chicago school is more preferable and by the end of development all tests written in London style should be gotten rid off.
Díky!
"if you're mock is complicated then your design sucks" this is similar with Zen of Python's "if you can't explain well the implementation then it's a bad idea"
Amazing explanation
London results in SOLID design without requiring a knowledge of SOLID principles. Likewise the result code quality of refactoring in Chicago solely depends on the developer's design skills. In other words, in Chicago everything may stay spaghetti as far as the tests are green.
Hi Dave. Thanks for the videos. I have bought your book (will arrive tomorrow). I hope I will have time and will, to read it :)
I have one question. Assuming you use some on cloud database like Aurora and cannot stub/fake it on local dev machine, would you still use some in memory semi-compatible SQL database to run the ATs locally? or connect to in-cloud created (in ad-hoc manner) DB/Aurora with your ATs? or run your ATs only in the CD stage after CI is finished? or use a mix of these approaches (e.g. use in memory DB on local dev env, and then the real Aurora DB during CD)?
i am pretty much stuck in chicago, been doing atdd as well but it tends to get messy - as a hobby dj chicago/detroit is close to home anyway ;)
But Jeff Mills ain't playing Frankie Knuckles records.
What are your thoughts about the argument that edge system mocks largely duplicate integration tests and have a lot of assumptions that don't actually confirm what those outer systems are actually doing?
For instance, when people mock a data access layer, an argument could be made that it's more efficient just to spin a up a lightweight container running the DB when possible, as opposed to mocking a lot of assumptions about the system that have to be maintained.
I don't see integration tests as being a fundamental part of a good test strategy. They have a place, but are tactical. My preferred test strategy is TDD for fine-grained, detailed, verification of the code, and Acceptance Testing (BDD style), to verify that the system is releasable using production-like simulations. The acceptance testing is like a super-integration test as well as a lot more, so eliminates the need for integration testing.
I also think that choosing to mock the edges of your system, applies a pressure on you to better abstract at those edges, and so you end up with a better design.
I am a bit disappointed at you Dave that after all this time you still didn't find out that London style unit tests are just terrible for projects. Unit as you said is not a class and it is also not a piece or pieces of code. Unit is a unit of functionality and it can be implemented by many objects of different classes or just by a single function.
London style is a great example of how NOT to do unit tests. Tests know way too much about internal implementation of a public interface that users can see. You mentioned the injected objects but what about objects that are internally created by a class whose functionality we want to test? Should we mock all those internal objects and should unit tests know about them? Of course not.
The question you should ask is what is the purpose of unit tests.
1.Unit tests are protecting the implemented functionality from bugs that can appear after we change the code - extend it or refactor/redesign it. The last thing we want is to get false failures of unit tests because we changed the internal implementation while the final functionality works as before - as expected.
2. The other purpose of unit tests is to get the documentation in code from which we can learn how to use the classes. It is much harder to understand unit tests that have mocks and even worse those that use mock libraries than just plain straightforward unit tests. So London style unit tests fail at achieving both goals.
I can tell you that the best programmers at the top software companies like Google and Facebook don't write London style unit test - only "classical" unit test with very few mocks - less than 5% of unit tests have test doubles.
Finally if there is piece of software that is hard to test with classical unit tests like web app that is using database then this is a sign of a bad design. We should separate business logic from database access right? We don't need mocks to test business logic that is properly separated from database or filesystem...
I am afraid it is not that simple, the "the best programmers" in other places do use the London style. You criticism is true if your design is overly coupled, but there are ways to design where the London school works fine, and gives some advantages over Chicago. As I said, I am not wholly in favour of, or against, either one. I think that they are both tools in the tool box.
The closest to a 'fight' I've seen is the odd jokey comment by Uncle Bob :)
Actually, Dave, there was a fight; One in which Beck himself took part. It was against that Ruby guy, I think. I think you can find it in video under the name "Is TDD Dead?".
That was about something else, not London vs Chicago. The “fight” was DHH say TDD was bad for design. I think Martin Fowler & Kent Beck were too polite 😉😎
@@ContinuousDelivery True
@@ContinuousDelivery I guess I recalled it as a fight between London VS Chicago because, in the end, DHH was criticizing London and Fowler and Beck were defending Chicago.
@@alessandrob.g.4524 Heheh, it went something like:
DHH - "TDD sucks because mocks and isolation"
... 3 hours of discussion ...
Kent - "This is TDD? I don't do that, too much mocks and isolation"
DHH - "Oh...."
FIN
@@michaelszymczak4245 Exactly lol
This feels like it dosent fit for many things.
For instance in ml and hpc a lot of the time the stated interface lies. Usually for a hardware reason.
For instance in pytorch u can't use data parallelism with an xpu. Even tho technicly each class should be able to run it.
A lot of the time u r forced to test in production because preformance can not be mocked. Hardware can not be mocked.
Only the real system on the real hardware with the real workload matters.
Tests
at its core is sabotage protection, accidental or intentional. True, there is very little in the way of protecting any code from sabotage. But test is code, it is also subject to sabotage. Having 2 avenues for something bad to happen is... let's just say it can be bad. The entire industry is up in arms for test driven and test automation, but there are plenty of horror stories where this results in loss. Let me explain.
100% coverage is a parallel system (your statement, and its also a correct statement). Simplistically, "X should be Y" perfectly maps to "X = Y". The major difference between the 2 systems is that the tests have a different structure than the code it is testing. But they are 2 different sets of avenues for change. If both systems are build by master craftsmen, those 2 avenues and even the structure will eventually converge along the lines of the business context.
There is a case to be made for irreducible complex problems. A unit of code which exhibits complex behavior. I have been searching for this for 15 years. I am beginning to think, code with irreducible complex behavior is just another word for shit code.
Probably the best case for TDD is the ability to construct a system with complete disregard to the structure of the final product. This is a massive boost for programmers with less than 10 years of experience. It is like writing an app and rewriting it based on what you learned from the first iteration. This, for someone who does not have a lifetime of shameful experiences is a godsend.
Then there is code review. I use that to eliminate both complexity and sabotage. Juniors handle the the fallout from this far better than I expected. Tho', probably I am a different kind of psychopath. I rarely test what the change implies. I usually immediately seek out edgecases provided the change already meets the elegance standards. In vast majority of cases is just a single glance but I often spin up the code and break it in the most grotesque ways possible.
By elegance standards I mean the clarity of how it fits into the rather constricted pattern and the entire system. Can't find a single fuck to give about the number of spaces used in indentations. If you can't read code written in a different dialect you are just illiterate. But I digress.
This is just an idea. There may be flaws in my reasoning. My coding career barely reached late puberty.
But what about the implementation details Sir? When we mock, even at the right abstraction levels, we leak some implementation details that will the tests fragile! So even small refactoring to the code for improving it's structure by changing the interaction while keeping the same observable behavior will make these tests raise a false alarm 🚨! Can you talk about this point please 🙏
I think this is really about the quality of the abstraction in design, these sorts of interactions, that you mock, should, intentionally, be more stable points in the design that hide-information, so they are a bit less fragile. You may still need to change them sometimes, but it should be easy. The problem usually comes when you don't practice ideas like "Tell, Don't Ask" in the design, and you end up with a nasty chain of things asking for data. zip = customer.getAddress().getZip() --- Yuk!
I talk about some of these ideas here: ruclips.net/video/8GONv6jJsG0/видео.html
@@ContinuousDelivery Thanks for the explanation!
If you had just said "and that's what we'll talk about today" at 17 seconds, you would have had a perfect Sabine Hossenfelder vibe :P
Thank you. Sabine is one of my favourite RUclipsrs!
Love a video where this applied to client side applications. Single page app, fat client, etc. I’ve seen more dev not leverage some of these techniques since it’s all UI.
I hope Java and C# will someday have something which will allow to not define an interfaces when we only deal with one implementation of it. Though placing interface and the class in one file helps in such case. I would also like these languages to allow for functions directly in the namespace without having to create (static) classes with static methods when someone wants to use more functional (or rather procedural) approaches. It's often easier to think about name for the function while not having to think about a noun for the class it resides in. Not to mention that it is often more natural this way.
Why don't Java programmers accept type inference??
I cannot design software without writing tests, because i dont know if and how the software will really behave. As a software tester i just dont rely on my knowledge, i don't trust neither the programming language, nor the foreign code, nor my own code. Unit tests give me some bit of "at least the things i tested for may work the indended way" feeling.
Mocks, Mocks, Mocks
and then you get SQL grammar exception on production
Then you are mocking at the wrong level of abstraction. You still need to test the real thing somehow too. Mocks are how you test the core bits of your code that represent what it really does, the integration points where I/O happens needs different kinds of tests, and better design to isolate one from the other.
If this is the rick then why not hit an actual database in your tests?
@@jimiscott
every guru is advocating for mocks and they are useful in small use-cases.
But most devs are working on applications that include some kind of database
So, if you blindly follow advices for "gurus" about mocks then there is 99% chance that you will hit SQL grammar exception on production :)
Dave, I'm an avid consumer of your channel, but I want to point out an aspect which I've seen very often.
At around 16:45 you say that the problem is in the design. I don't think so. The design is just fine. The problem is with the kids who are enamored with their mocking framework and try to test too much (London) instead of staying high-level (say at the boundary of a component or layer), that is, Chicago.
I guess my question is: how do you manage to cool down the love heat for the Mock in front of an audience of 30- yo?
Just like you, more often than not I see a hideous use of mocks and all the time the excuse for missing the deadline "because we also had to fix the tests".
Also, I think we as an industry should introduce by name a new type of test that Feathers (and others) have mentioned in the past, including in one of your recent videos: the developer tests = the tests you write and execute only as a developer; if a developer test fails (after it used to work once), you remove it.
No I think the design IS a problem. If a class has too many dependencis that "need" to be mocked, something is wrong and should be abstracted away. Not sure if I agree on the "maximum 2" argument, but less is more.
The mock-love can be reduced by showing the futility of certain mocking behaviors. Specifically when dealing with Mockito, there is the "when(MethodCall)" caveat, as the method call is being executed, so one can create examples that will sometimes fail when being mocked.
Another example is mocked state - create a method that accesses the state multiple times, and when called in a different way, accesses it once more. The verifications on that tend to get REALLY annoying as the developers need to explicitly check for verify(mock, times(n)) where n is suddenly changing.
Also, explain how "any()" as a verification argument does not work (because at that time, most people tend to verify with "verify(mock, atLeastOnce()).method(any())", which contains 2 levels of uncertainty (because "atLeastOnce" is not a good indicator of a verification, and "any" means that the verification has no value).
Basically, demand that every mock _must_ be verified _exactly_. This quickly leads to a lot of overhead which can be avoided by either reducing the amount of dependencies or introducing stubs or using real objects.
In vladimir khorikov's book, if memory serves, he suggests only mocking the things not under our control, a third party API for example (via an abstraction).
@@cartsp I totally agree.
@@ponypapa6785 I'm not sure what kind of code you have in mind when you say
> If a class has too many dependencies that "need" to be mocked
What I have in mind is that mocks are not even needed most of the time, but other test doubles.
More often than not, those dependencies are either inputs or outputs. For inputs, you can use a stub. For outputs, a spy.
So what you end up doing is things like: put some value in the input from which the SUT pulls its value, does its magic, then stores its output. Then I ask the output what that value is.
I don't care about how that result was reached. Because if I did, I would have to refactor my tests every time I refactor the SUT. This is what Dave also alludes to.
The only time I care about the interactions is between components with either different owners or different stakeholders who have an agreement in real life about how things should work.
That is for instance "external systems not under your control" mentioned above.
@@cartsp when we mock a third party API, should we not also have few integration tests that checks the interaction with the API?
You should study London and classical style unit tests in depth and then you should expose London style as a terrible idea that is killing many projects.
Normally, Dave is an absolute gold teacher, but in this moment I gotta stop. At 16:11 dave talks about mocking the boundries. This is probably breaking dependency inversion, you can learn more about it in Uncle Bob's "Principles of clean architecture".
Don't forget Tim Mackinnon!
Neither. I prefer london without going beyond the top level. I am testing behavior, not the implementation.
This misses the essential point
Concerning the Warehouse-Bookstore-Mock-Situation, I have experienced a LOT of problems when configuring mocks this way, specifically as the example given does not at all verify the actual interactions - the implementation could still just return a "new Book(TITLE)" and never even touch the warehouse.
As such, I tend to configure my mocks very generally - usually something akin to "doAnswer(invocation -> new Book(TITLE)).when(warehouse).find(any())" and then explicitly verify that the desired configuration was actually called, e.G "verify(warehouse).find(TITLE)".
What do you think of this "configure general - verify specific" approach?
And yes, this still takes into account that there should not be more than three mocked dependencies in a single class =)
I guess the question here is: why? Going to a lot of effort to prevent yourself from cheating only seems valuable if you think there's a risk you might actually cheat.
There are times that you want to check a dependency has been called in a way which isn't obvious from the return, and then verifying it was indeed called sounds like a good idea. But there's a risk of over-programming mocks - you end up with noisy tests more concerned with components other than the system under test, and that complication might mean you make mistakes in your mocking which gives another vector for tests to fail (or pass) incorrectly.
Now, it may be you prefer a fake which has a simple implementation - and often you're better off with such simple fakes (a TestWarehouse class, for example) than using a mocking framework. That'll also encapsulate your simple fake behaviour out of the line of sight of tests.
@@TARJohnson1979 Yes, Fakes and Stubs do often help, but in many cases, manually creating something to verify partial behavior (think an interface that requires 10 API methods, of which the current system under test uses 2, which have static returns) can be very tedious and time consuming. This tends to happen in existing codebases that have extractet a lot of "useful" interfaces into "common" modules.
The reason why is simple - I had a lot of situations in which the developer expected something to happen, and programmed this expectation into the mock. In this case, the behavior was "accept string containing id, return object". The returned object was then to be examined and a result of that examination was to be returned.
He then created a second test and configured the mock in the same way, but the returned object hat a single field set to null. This was to expose that, should any value be null, there would be an empty result object.
His expectation on that method was not met, instead, the mock registered a call with a different input, which was unconfigured, so the mock returned null.
The code however would only examine the object in question, if it was not null, if it was null, an empty result object would be returned.
Which incidentally was what the developer expected.
Sadly, shortly after launch, NullPointerExceptions started to happen, because the expected behavior was not implemented correctly - and changing the mock from "accept string containing id" to "accept anything" immediately led to the NullPointerExceptions that were found in production code.
Of course, this was not created Test-driven, but I have seen this specific problem in many cases, even in cases where people clainm to have written the tests first.
The problem is that, since many java developers are scared of null, they check for that at every turn and thereby hide bad behavior when their expectations fail - a generally configured mock accepts even bad values and will then either lead to exeptions as the value does not fit, or fail the test as the passed value is suddenly wrong.
So in my experience, "configure general, verity specific" leads to safer tests when it comes to refactorings and transformations. However it does take due diligence to avoid creating mock-swamps.