Worth noting is that book also says in 9.2.1: "Mocks are for integration tests only". I agree.
In my opinion this book is the best comprehensive take on testing I ever encountered.
330 karma · joined September 12, 2013
Worth noting is that book also says in 9.2.1: "Mocks are for integration tests only". I agree.
In my opinion this book is the best comprehensive take on testing I ever encountered.
https://docs.microsoft.com/en-us/visualstudio/test/generate-...
Also known as its legacy / research project name, "Pex". The underlying algorithm technique is called "dynamic symbolic execution".
There was also similar tool for Java called "Agitar Agitator".
The core idea is to make a contract with oneself to not exceed a daily budget of procrastination and implement it. Say, 4 hours per day, on average. Everything else follows from it.
There are few critical components to make this work:
1. You need to admit you have a problem that needs fixing. If you procrastinate so much, quite possibly you are depressed. Like, for realz. That's OK.
2. Self-understanding that this is a matter of survival. Either you will make this work, or your life will be truly miserable, up to and including destroying your career, relationships and health.
3. Comprehensive time tracking. If you were watching a YouTube video while eating and spent extra 5 minutes to finish off the video after you were done with your meal, you need to track these 5 minutes. If you pick up your phone in bed just to quickly scroll through pages for 3 minutes, you need to track it. I use Excel for that - very fast & easy if you know the right keyboard shortcuts.
4. You need to do the tracking _yourself_. This is to (a) increase awareness you procrastinated and (b) introduce friction to doing it.
5. Social accountability. I have a weekly session with a coach where I report if I stayed within the quota, or not. But anybody you don't want to disappoint will do.
6. You are good as long as you stay within the budget. If you spend the time you reclaimed by laying on the couch and looking at the ceiling for 3 hours - that's a huge win. More on that below.
7. As always, you cannot neglect your fundamentals: sleep, exercise, proper diet, socializing. But now you will have time to deal with it.
Once you implement this process, few observation immediately come to mind:
1. You have so much more time and mental energy. Wow.
2. If you fail to track these short 3-5 minute burst of procrastination, they quickly add up to half an hour, an hour, two hours. Hence, you really cannot slip on that. If you slip, you also won’t be able to trust yourself, which is critical.
3. You become bored with that extra time, meaning you finally have time to think and talk to yourself. I cannot overstate how important that is. You can use this time to direct your thoughts / visualize better outcomes and hence motivate yourself to do the right thing (aka Cognitive Behavioral Therapy).
4. Once you keep doing it successfully for some time, you can now trust yourself that you are in control. This is huge, because your emotions now will be on your side.
5. You become less afraid of doing productive things, because you actually have time to do them, instead of thinking "what's the point, I have 1 hour in the day left after wasting all of it, I am tired, and I will give into my procrastination cravings anyway".
6. You will sleep way more because now you cannot procrastinate in bed. This will repay your sleep debt and restore your energy levels, making everything else much easier to tackle.
Overall, this strategy works for me very well because (a) I admitted to myself I have a problem that is very serious. This means I need to seek help and develop a process to solve it. And (b) I don’t mind doing comprehensive time tracking of my procrastination time, which is a critical part of the entire process.
Further reading:
"Digital Minimalism" by Cal Newport
"Feeling Good" by David D. Burns
In the examples you gave the "decision record" would be fairly simple - we assume that human rights [1] are a given.
I wonder if this would change their attitude to the problem.
What I am trying to say is that the golden rule and its applicability is far from obvious. You can consider interpreting it correctly the most basic "advanced morality" you asked about.
https://martinfowler.com/bliki/BoundedContext.html
It is a huge topic. Hard for me to discuss this further without knowing the specifics. But intuitively, if it was as simple as "Really all I need is a simple string and a validation function." then you wouldn't need a class with several hundred lines of code.
Or you could have UsaPhoneNumber, JapanesePhoneNumber, etc.
And instead of doing:
IsValidUsaPhoneNumber(str);
you could do:
var usaPhoneNumber = new UsaPhoneNumber(str);
and now you have the validness captured as a type:
https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-va...
The critical thing here is: these classes reflect terms from the domain model that can be easily intuitively understood, not some made-up abstractions adding to incidental complexity, like -Manager, -Util, -Provider, -Context, -Wrapper, and so on.
Conversely, you can imagine that you wrote a sentence, took some words from it, assumed these words don't exist, and replaced them with their dictionary definitions. Repeat a couple of times and you have your one big class. Extracting classes to capture domain language terms is like reversing this obfuscation/serialization process.
Cool article on the topic: https://www.yegor256.com/2014/11/20/seven-virtues-of-good-ob...
If you detonate all nukes you could destroy every city with population of over 100'000, worldwide. About 4'500 cities in total. You would still have around 1'500 nukes left. You would kill at least 3 billion people.
Let me clarify my point with a more precise example.
Let's say we want to test this:
ParseUrlDomainAndPath(string url) { string validatedUrl = Validate(url); (domain, path) = /* inline logic to extract domain and path from the validatedUrl */; return (domain, path) }
Now I have few options to unit test it:
1. I could mock out "Validate" to return the "url" passed as input, call in the test ParseUrlDomainAndPath("___:||testDomain|testPath"), and assert it returns (testDomain, testPath). Such test might even pass, if the inline logic for extracting domain and path is not too fussy about the delimiters. In such case I will end up with a test telling me "if you call ParseUrlDomainAndPath with URL that has | instead of / and some weird schema of ___, it will successfully return". This is a lie. If you call it in production, it will fail validation. So the test gives you false impression on how the system behaves. You are testing how parsing of domain and path works on URL that has | instead of /, but this won't happen in production. Thus, you are testing made-up behavior. Waste of time.
2. As 1., but instead I could mock out Validate to return whatever validatedUrl I want, completely disregarding url. In such case, what is the point of even having Validate involved in the test here? Instead, let's refactor to functional core - let's take the inline logic, capture it in a method called "ExtractDomainAndPathFromUrl(string validatedUrl)" and pass validatedUrl to it directly. No need to deal with Validate at all, no need to mock anything, no need to fixup any broken mocks. Great!
3. As 1., but as input I pass "foo". The mocked out Validate returns "foo", and we are now trying to extract domain and path from it. This will either throw an exception or return garbage. So our test now has failed. But we don't care about this failure, at all. In actual production behavior the domain and path extraction logic would never even execute, because Validate would fail beforehand. So here we have reverse situation to 1.: In 1. we have a test telling us production will work while in reality it won't (as Validate will throw), and here we "found a bug" (the test fails) that doesn't matter as it is impossible to happen in production. Again, waste of time.
So with 1. and 3. being waste of time, the only option that is left is 2. You have one test that a) checks that Validate behaves correctly and b) checks that ExtractDomainAndPath behaves correctly and c) checks that both of these methods collaborate with each other correctly (aka "mini integration test").
You could now argue that this is wrong, I should have one test for Validate, one test for ExtractDomainAndPath and one test for ParseUrlDomainAndPath that mocks out Validate and mocks out ExtactDomainAndPath. But let me ask: why? In such case you lose the benefit of having "mini-integration" test. When you test ParseUrlDomainAndPath with everything mocked out you test an empty husk of logic, only if the calls are made in proper sequence. You cannot even really assert anything meaningful! (aka "mockery" anti-pattern). You end up with 3 tests instead of 1 and cr*pton of unreadable, brittle mock logic. And chances are, the one test of ParseUrlDomainAndPath that doesn't use any mocks of internal business logic, will already cover significant parts of ValidateUrl and ExtactDomainAndPath, and so will reduce the need of additional "corner case" tests testing these methods directly. Having 1 test with proper in-process dependencies instead of having mocks and 3 tests is just win all over he place: less testing logic, better ability to catch bugs (due to bonus mini-integration testing and realistic data), executable specification aiding in program comprehension, less brittle tests, no misleading green tests, and no made-up, irrelevant failing tests.
There is one more, very important benefit: you can step such test with a debugger and see how all the components collaborate with each other, on real data. But if you use mocks and fake oversimplified data, you get very shallow slices of code and cannot reason about anything relevant.
Let's take your example of "shouldExecute". I assume your unit test operates on some inputs (with the values provided inside the unit test itself, naturally), and "shouldExecute" has potentially some nontrivial logic in it. Say, it reads value of some environment variable and if it is right, returns "true".
Now there are two possibilities:
a) your test inputs are made up. For example, you never set the environment variable value, and coerce "shouldExecute" to always return "true" anyway. The problem with such a test is that it is fiction, not a test of an actual production behavior. Sure, you will test what would happen if the logic would determine it should execute given no environment variable is set, but this will never happen in production. In production lack of environment variable would result in "shouldExecute" returning "false" and you should test for *that*. So you do care about the details of "shouldExecute", because you need to be aware that it returns "true" only if appropriate environment variable is set. And if you don't care if the "shouldExecute" returns true or false, then why do you call it in the first place? What are you even testing? I hope your "shouldExecute" doesn't have any side effects you depend on, and I do hope you do not use code coverage as a goal unto itself.
Plus, such a test cannot be used as "executable documentation", because the inputs are simplified to the point of irrelevance, and cannot help in understanding actual program behavior.
b) your test inputs are realistic, and reflect actual production behavior. This means you will have properly set up the environment variable value so that "shouldExecute" returns true. With that, the test is similar to actual production behavior, has good bug-catching ability, and can serve as executable specification. But here again you will have to worry about "shouldExecute" implementation.
---
Let me offer you another example. Imagine we want to test:
Compile(SourceCode sourceCode) { ValidateSyntax(sourceCode); /* complex logic post-processing the sourceCode here */ }
I could say here "I am testing Compile, and I do not care if ValidateSyntax throws an exception; I will just coerce it to return without throwing an exception".
And then I can write a test that takes as input some simple sourceCode like "blablabla" and claim I have somehow tested the "/* complex logic post-processing the sourceCode here */". But this is silly, in reality such fake input would never survive the validation, and thus testing what happens after is just waste of time. Hence I need to ensure sourceCode passes the *actual* validation, and hence I need to understand the logic inside the "ValidateSyntax" method.
It gets worse. Now imagine we have thoroughly tested the complex logic while using mocked out ValidateSyntax, but now the syntax has changed and thus the validate method behaves differently. If I have a mock of the "ValidateSyntax" method I might still feel good - the test is green, the coverage is still high. Except it is all I lie. I run "Compile" on some production data and it blows up. Why? Because the test was working against a "ValidateSyntax" mock that was mocking the result of validating obsolete syntax, which, now, with the changed behavior, would actually not pass validation and blow up. Basically my test was telling my "Compile" method works IF I assume syntax is still the obsolete one, but it doesn't tell me how my code behaves with the current syntax.
So every time program behavior changes, I need to go through all my mocks that were duplicating that behavior, and see if they are still faithfully reflecting it. Otherwise I risk ending up with a green test suite that tests nonexistent, impossible program executions.
No. As you say, the moment you have a real out-of-process dependency, you no longer have a unit test, but an "integration-with-external world" test.
The heart of the argument is to not mock out internal (in-process) business logic. Instead:
a) do not use mocks (e.g. leveraging Moq); use fakes (aka "simulators"), i.e. proper classes having in-memory implementations of the out-of-process dependencies and crucially
b) replace only out-of-process dependencies, which usually are flaky/nondeterministic. Never replace internal business logic.
If you feel you need to replace internal business logic, you are not following the functional core architectural pattern. After you refactor to functional core you will no longer need to replace any dependencies in your unit test, as there won't be any - you will just call your tested pure method and make assertions on the returned value.
Special case here is testing "top-level/controller" logic. Here you need to use the in-memory fakes, but there will be only few, reused across all tests, and such test will be an "end-to-end internal-business-logic integration test", but still it will have all the properties of a unit test - it will be fast, deterministic, and you will be able to run it as part of a unit test suite out-of-the-box, with no environment setup necessary.
Further reading: https://enterprisecraftsmanship.com/posts/when-to-mock/
> If you want to change code you will always measure the effect it will have on the code and tests are incidental, not the primary concern
Exactly. Mocks are very brittle and break all the time, causing unnecessary rework. If you instead rely on a small set of fakes of out-of-process dependencies, you will drastically reduce test suite rework and improve signal-to-noise ratio.
> Using a real dependency, I would be left in the unfortunate situation of depending on knowledge of the internals of that dependency.
And what happens if the real dependency changes behavior, but you forget to update your mock? You will have a test that will be running against a mock that simulates obsolete behavior, no longer present in production. In worst case scenario the test will be green/passing, while there is a bug in production. Will you remember to always comb over your entire test suite and review all mocks to faithfully simulate actual production behavior?
> It may not allow me to execute specific paths via pure configuration at all.
If you write fakes, you will have full control over how to configure them.
For me that's one of the main failing points of NVC as I see it represented here - it starts with "I am right, and you need to adjust" instead of "let's find the underlying root cause of the problem together".
Honestly, this sounds even worse to me than just the observation. IMO a good test is "Would I say this to my boss? If not, then probably I shouldn't say it at all.". I would not say this to my boss. Not even close.
How about instead:
"Hey, I noticed you were a little bit late, which is unusual for you. Is everything OK?"
And if the pattern continues:
"Hey, I noticed you were a bit late in the last couple of meetings. I think it is somewhat important to be on time. Is there anything I can do to help you avoid being late next time?"
I do think the policy should be explicit about it. It doesn't help to say "We forbid X" if one side's prominent tactic is to redefine X as they see fit. By analogy, it is not enough to say "We forbid discussions about religious beliefs". One also needs to say "Theory of evolution is accepted here as true".
Re 2): Again from [1], I've heard executions are actually quite expensive. Thus, it doesn't scale well.
Re 3): It doesn't make me feel safe if somebody can frame me and get me killed. It makes me feel safer knowing I have time to prove my innocence.
Re 4): I am very skeptical of this claim. Any solid evidence for this being true? Anecdotal evidence shows the opposite. Most criminals don't have good long-term thinking skills, so they won't recognize the tradeoff between 40 years in prison and death, and use that to not commit a crime.
[1] John Oliver on death penalty: https://www.youtube.com/watch?v=Kye2oX-b39E
> Too bad we can't understand this in our politics
Unless you have a very good reason to not get a vaccine and not wear a mask I will expect from you to do these things. Not because I don't understand what's not great for you. It's because I understand the objective reality of the disease.
https://www.microsoft.com/en-us/research/blog/lambda-the-ult...
> By Calc Intelligence, we mean the research goal of bringing intelligence to end-user programming. The spreadsheet of the future is a trustworthy collaborator and teacher.
> In December 2020, we announced LAMBDA, which allows users to define new functions written in Excel’s own formula language, directly addressing our second challenge. These newly defined functions can call other LAMBDA-defined functions, to arbitrary depth, even recursively. With LAMBDA, Excel has become Turing-complete. You can now, in principle, write any computation in the Excel formula language.
https://en.wikipedia.org/wiki/North_Crimean_Canal#2014_Crime...
A video with some narrative around it: https://www.youtube.com/watch?v=Aqq8clIceys
Your first pick had 1/3 chance of winning. So switching has 1/3 chance of losing. Thus it has 1 - 1/3 == 2/3 chance of winning.
Good article and book about it:
- https://enterprisecraftsmanship.com/posts/unit-testing-depen...
- Unit Testing Principles, Practices, and Patterns