When you have no tests your problems go away because you don’t see any test failures.
Never have I tested anything and NOT found a bug, and most things I tested I thought were already OK to ship.
Thus, when you delete your tests, the only person you are fooling is probably yourself :(
From reading your page I get the impression you are more burnt out from variation/configuration management which is completely relatable… I am too. This is a hard problem. But user volume is required to make $$. If the problem was easy then the market would be saturated with one size fits all solutions for everything.
I think this is highly domain dependent. I currently am working on codebase that has tests for a part of it that are an incredibly useful tool at helping me refactor that particular part. Other parts are so much UI behavior that it is significantly faster to catch bugs by manual testing because the UI/workflow either changes so fast that you don’t write tests for it (knowing they’ll be useless when the workflow is redesigned in the next iteration) or so slow that that particular UI/workflow just doesn’t get touched again so refactors don’t happen to it to introduce more bugs.
I have never found tests to be universally necessary or helpful (just like types). They are a tool for a job, not a holy grail. I have also never met a codebase that had good test coverage and yet was free of bugs that aren’t then found with either manual testing or usage.
Somewhat hyperbolically and sarcastically: if you are good enough to write perfect tests for your code, just write perfect code. If you aren’t perfect at writing tests, how do you know the tests are complete, bug free, and actually useful? :)
I did like the rest of the post, but this is not hyperbole. It's just a disingenuous argument, and one that looks orthogonal to your point that "tests are a tool for a job".
If you aren't perfect at magnetizing iron, and you need a working compass, you better magnetize two needles and use one to test the other. The worse you are at magnetizing iron, the more important it is that you do this if you want to end up with a working compass.
This is modern testing in a nutshell - it's ineffective but the author of the test can't actually tell that!
Using this analogy, if you created 10 magnetised needles using the wrong process and getting the wrong result, then all 10 would agree with each other and your test passes, but your needle is still broken.
I don't think you understand how magnets work.
Hint: if you think the way to test whether a needle is magnetized using another possibly magnetized needle is by building both needles into two separate compasses, you're nowhere close.
I thought it was clear from my post that I do not think this.
I also think you are missing the point.
You wrote:
This suggests that you do think soemthing like this. Again, the way you test wheher you successfully magnetized a needle using another potentially magnetized needle is not by checking whether they "agree with each other" in the end application.
Or it suggests they’re continuing the analogy (which isn’t perfect) to make a different point.
Twice you’ve spent the majority of words in your post telling someone they’re wrong without explaining the correct methodology. That does not advance the conversation, it’s the equivalent of saying “nuh-uh” and leaving. If you disagree, it’s good form to explain why.
It doesn’t take long to say the failed magnetisation would leave all needles pointing in disparate directions, not the same consistent wrong direction. Unless there’s something else in your test that is so strong and wrong that it causes that problem, in which case the analogy starts working again.
I don't get this analogy.
Apart from the fact that in your example the produce is validated using the exact same produce, you are actually describing the perfect test:
Two magnetized needles will validate each other, so both the product (needle#1) and the test-setup (needle#2) will be confirmed as valid in one step. If one is not working, the other will self-validate by pointing at the earth magnetic field instead...
IMO if your implementation is that unstable (you mentioned the UI/workflow changes fast) it isn't worth writing a test for it, but also, I don't think it shoud be released to end-users because (and this is making a big assumption, granted), it sounds like the product is trying to figure out what it wants to be.
I am a proponent of having the UI/UX design of a feature be done before development gets started on it. In an ideal XP/agile environment the designers and developers work closely together and constantly iterate, but in practice there are so many variables involved in UX design and so many parties that have an opinion, that it'll be a moving target in that case, which makes development work (and automated tests) an exercise in rework.
Chiming in as an end-user of software: please try to minimize the amount of times I need to re-learn the user interface you put in front of me.
Aaaaaaand I replied to the wrong comment, mea culpa!
I agree with you that the ideal is to have UI/UX work resolved before starting dev work.
In my experience, this has never happened. I’ve moved around hoping that somewhere, leadership has fixed this problem and nope. It never happens.
There are just too many unknowns and never enough time for design to stabilize. It’s always a mad dash to collect whatever user info you can before you slap together a final mock-up of the interface and expected behaviour.
I think there's a great balance here in these environments:
- write tests as part of figuring out the implementation (basically: automate the random clicking you're doing to test things anyways)
- Make these tests really loose
- Just check them in
Being unprecious about test coverage means you just write a handful of "don't blow up" tests for features, that help you get the ball rolling and establish at least a baseline of functionality, without really getting in the way.
Types are there to ensure against human error and reduce the amount of code we need to write.
Tests exist to guarantee functionality and increase productivity (by ensuring intended functionality remains as we refactor/change our code/UI).
There may be cases where some tests are too expensive to write, but I have never come across this myself. For example, in functional tests you would attempt to find a secure way to distinguish elements regardless of future changes to that UI. If your UI changes so much between iterations that this is impossible it sounds like you need to consider the layout a little more before building anything. I’m saying that based on experience, having been involved in several projects where this was a problem.
Having said that, I’m myself horrible at writing tests for UI, an area I’m trying to improve myself, it really bothers me :)
Tests can be expensive to write if they are an afterthought, and/or the code is not written in way that is easy to test.
UI tests can be cheap but they require some experience in knowing how to write a testable UI. One way of achieving that is writing them as early as possible, of course. Which is not always possible :/
Which isn't devoid of downsides either
That's a good point. Sometimes more ergonomic APIs can be harder to test.
Well obviously, you just write tests for the tests. :3
It's called induction.
It's actually called mutation testing. And luckily it's almost fully automated.
> Well obviously, you just write tests for the tests. :3
I had a friend whose first job out of school (many years ago) was part of a team at Intel assigned to write tests for their testing tools. When it matters enough economically, it will happen. As you can see from recent news, that is still not enough to guarantee a good result.
Qui testet ipsos tests?
I have yet to see anyone claim they write perfect tests.
I never claimed to produce or seen complete tests. I never claimed or seen bug free tests.
I know that whenever I fix something or refactor, test fails and I found a bug in code. I know that when we do not have have the same bag again and then again the same bug and again the same bug.
I know when testers time is saved and they dont have to test repetitive basic stuff anymore and can focuse on more complicated stuff.
watwut wrote:
Well, username checks out :-)
This sentence makes no sense. Tests are infinitely more straightforward than code. I always go back to my dad's work as a winder before he retired:
After repairing a generator, they'd test it can handle the current that it's expected to take by putting it in a platform and... running electricity through it. They'd occasionally melt all the wiring on the generator and have to rewind it.
By your logic, since they weren't "good enough" to fix it perfectly, how could they know their test even worked? Should they have just shipped the generator back to the customer without testing it?
No, they often aren't, and UI can be complex to test.
This is my number one pet peeve in software. Every aspect of every interface is subject to change always; not to mention the bonanza of dickbars and other dark patterns. Interfaces are a minefield of "operator error" but really it's an operational error.
People are building multimodal transformers, that try to simulate users.
No matter how stupid the ai, if it can break your ai code, you have a bug.
Tests are just a way of providing evidence that your software does what it's supposed to. If you're not providing evidence, you're just saying "trust me, I'm a programmer."
Think back to grade school math class and your teacher has given you a question about trains with the requirement "show your work." Now, I know a lot of kids will complain about that requirement and just give the answer because "I did it in my head" or something. They fail. Here's the fact: the teacher already knows the trains will meet in Peoria at 12:15. What they're looking for is evidence that you have learned the lesson of how to solve a certain class of problems using the method taught.
If you're a professional software developer, it is often necessary to provide evidence of correctness of your code. In a world where dollars or even human lives are on the line, arrogance is rarely a successful defense in a court of law.
Not quite. Tests are just a way to document your software's behaviour, mostly so that future people (including future you) working with the software know what the software is intended to do – to not leave them to guess based on observation of how undefined behaviour plays out.
That the documentation is self-validating is merely icing on the cake.
I feel like our industry kinda went the wrong way wrt UI frontend tests.
It should be much less focused on unit testing and more about flow and state representation, both of which can only be tested visually. And if a flow or state representation changed, that should equate to a simple warning which automatically approves the new representation as the default.
So a good testing framework would make it trivial to mock the API responses to create such a flow, and then automatically do a visual regression of the process.
Cypress component tests do some of this, but it's still a lackluster developer experience, honestly
This is specifically about UI frontend tests. Code that doesn't end up in the DOM are great for unit tests.
A test will only catch an edge case you already thought of. If you thought of it anyway why just not fix the bug instead?
Tests have burned out software engineers who waste the majority of their time deriving tests that will pass anyway. And then a significant code change will render them useless, at which point they have to be rewritten from scratch.
No your program will not be more correct with more tests. Deal with it.
The reason I do this is to prevent the bug from re-occurring with future changes. The alternative is to just remember for every part of the system I work on all edge cases and past bugs, but sadly I simply do not have the mental capacity to do this, and honestly doubt if anyone does.
If a future change is relevant to an existing piece of code then the logic needs to be rethought from scratch. Your past tests have no guarantee that will be still relevant or comprehensive.
So skip the tests and work more on the code instead.
To me, advice like "just write your code in a way that you will only ever extend it, not change it" is about as realistic as "just don't write bugs".
If a requirement changes, the test for that requirement obviously has to change. These tests breaking is normal (you had a requirement that "this is red", and a test ensuring "this is red", but now suddenly higher ups decide that "this is not red", so it's obvious why this test breaking is normal).
If a requirement doesn't change, the test for those requirements should not change, no matter what you change. If these tests break, it likely means they are at the wrong abstraction level or just plainly wrong.
Those are the things I look at. I don't even care if people call stuff "unit tests", "integration tests". I don't care about what should be mocked/faked/stubbed. I don't care about whatever other bikeshedding people want to go on.
E.g. if your app is an HTTP API, then you should be able to change your database engine without breaking tests like "user shouldn't be able to change the email of another user". And you should also be able to change your programming language without breaking any tests for user-facing behavior (e.g. "`GET /preferences` returns the preferences for the authenticated user").
E.g. if your code is a compiler, you should be able to add and remove optimizations without changing any tests, other than those specific to those optimizations (e.g. the test for "code with optimizations should behave the same as code without optimizations" shouldn't change, except for specific cases like compiling only with that optimization enabled or with some specific set of optimizations that includes this optimization).
For me at least, designing a test will usually let me discover problems with my code which may otherwise gone unnoticed.
Leaving the tests there once written to help us in future refactoring costs nothing.
Granted, in some languages tests are more complicated to write compared to others. In PHP it’s a nightmare, in Rust it’s so easy it’s hard to avoid doing.
I hear what you are saying though, sometimes writing tests consume more time then is necessary.
I completely agree with what you're saying - tests help me ensure nothing breaks and change stuff fast. But leaving EVERY code behind is a liability. In the best case, it's free, otherwise, it's another point of failure and other engineers might spend time understanding it.
Code is a liability. It has to have a good reason to be there in the first place - in the case of tests, it's worth it because it saves more time on bugs, but this can easily turn into a premature optimization.
Very well put! Couldn’t have said it better myself
Will all your team members also think about those edge cases when changing that part of the code? Will they ensure the behavior is the same when a library dependency is updated?
So, tests catch edge cases that someone else thought of but not everyone might have. This "not everyone" includes yourself, either yourself from the future (e.g. because some parts of the product are not so fresh in your mind), or yourself from now (e.g. because you didn't even know there was a requirement that must be met and your change here broke a requirement over there).
To put an easy to understand example, vulnerability checkers are still tests (and so are linters and similar tools, but let's focus on vulnerabilities). Your post implies you don't need them because you can perfectly prevent a vulnerability from ever happening again once you know about it, both because you write code that doesn't have that vulnerability and because you check that your dependencies don't have that vulnerability.
So, think of tests more like assertions or checksums.
Property-based tests and model-based tests can catch edge cases I never thought of.
Burn, baby, burn! We don't need programmers who can't handle testing.
I assume you are talking about unit tests here.
Thinking of edge cases is exactly what unit tests are for. They are, when used properly, a way to think about various edge cases *before* you write your code. And then, once you have written your code, validate that it indeed does what you expected to do so beforehand.
The issue I am seeing, more often than not, is that people try to write unit tests after the fact. Which means that a lot of the value of them will be lost.
In addition to that, if you rewrite your code so often that it renders many of your tests invalid I'd argue that there is a fundamental issue elsewhere.
In more stable environments, unit tests help document the behavior of your code, which in turn helps when rewriting your code.
Basically, if you are just writing tests because people told you to write tests, it is no surprise you burn out over them. To be fair, this happens all too often. Certainly with the idiotic requirement added to it that you need 80% coverage without any other context.
If you write tests while understanding where they fit in the process, they can actually be valuable for you.
Writing a test is often the best way to reproduce and make sure you fixed a bug.
Keeping them for a while lets you make sure it doesn't pop up again.
10 years later, they probably don't add much value.
Tests are tools, that's like saying 'No, your food won't taste better with more salt.', it depends.
You write the test to prevent the bug from being accidentally reintroduced in the future. I have seen showstopper bugs reintroduced into production multiple times after they were fixed.
There are things that are easier to verify than to do correctly. Almost anything that vaguely looks like a proper algorithm has that property. Sorting, balanced trees, hashtables, some kinds of data splicing, even some slightly more complicated string processing.
Sometimes it's also possible to do exhaustive testing. I once did that with a state machine-like piece of code, test transitions from all states to all other states.
Do you think the test is written and the bug left in? What a weird take.
And then, you write the test so that future changes (small or big) that causes regressions get noticed before the regression is put into production again. Especially in complex systems, you can define the end result and test if all your cases are covered. You do this anyway manually, so why not just write a test instead?
The focus on automated unit/integrations tests is a relatively modern thing (late 90's?). There was some pretty large and extremely reliable software shipped before that focus. Random example is that the Linux kernel didn't have much tests (I think these days there is more testing). Unix likely didn't have a lot of "tests". Compilers tended to have them. Operating systems less so. Games (e.g. I'm sure Doom) didn't tend to have tests.
You need to find a balance point.
I think we know that (some) automated tests (unit, integration, end to end) can help build quality software. We also know good tests aren't always easy to write, bad tests make for harder refactoring and flaky tests can suck a lot of time on large projects. At the same time it's always interesting to try different things and find out what works, especially for you if you're a solo developers.
"Unit-Testing" became popular about the time of Extreme Programming. The reason I think it became so popular was that its proponents programmed in dynamically typed languages like Smalltalk, and later JavaScript. It seems to me that synamic languages needs testing more than statically typed ones.
Beck's first xUnit framework was SUnit, for Smalltalk, but Beck's second take was JUnit, which is for Java. Java was and still is a statically typed language.
Tests are there to detect logical correctness of the unit under test, very few type systems can catch errors like using - instead of + in a mathematical formula, for instance. You either need to go into dependently typed languages or languages that otherwise permit embedding proofs (SPARK/Ada).
In dynamic languages tests also tend to fill the role of the compiler is I think the parent's point. Dynamic/interpreted language code might have syntax errors or be otherwise incorrect (including type errors) and you often don't find those until they code is run.
When this buggy method is compiled (not run) with Smalltalk, errors and warnings are shown. The code cannot be run because it failed to compile.
hnQuestion
| list max |
list := #(1 8 4 5 3).
! Syntax Error: Nothing more expected
1 to: list size do: [:i |
max < (list at: i)
? Uses ifTrue:/ifFalse: instead of min: or max:
ifTrue: [max := (list at: i)].
ifFalse: [max := max].
].
That’s a fair point. However dynamic languages tend to have very good linting that catch many basic type errors.
They can also run way more often during development, down to the function/expression level.
Most video games have a full team of QA testers doing functional testing on the games as they go along.
Same thing for the kernel, plus some versions are fully certified for various contexts so you can be sure fully formalised tests suites exists. And that’s on top of all the testing tools which are provided (Kunit, tests from user spaces, an array of dynamic and static testing tools).
But I would like to thank all the people here who think testing is useless for their attitude. You make my job easier while hiring.
That's fine.
I've never written a test in my life. Have my programs ever had bugs? Sure. But I sleep very well at night knowing that I spent all my brain power and time writing actual code that Does Useful Work rather than have wasted significant lengths of my time on this planet on writing test code to test the code that does the Useful Work.
You speak of attitude and smugly "thank" those who don't write tests as that acts as your hire-or-not filter. With an attitude like that, I'd 100% not work for anyone with that attitude anyway.
And that’s why I never want to have to work with you on anything shipping to a user ever.
Don’t get me wrong, the field is riddled with people who think testing is beside them and wash their hand with the quality of what they ship and what they put their users through. That’s an issue to fix not a situation we should tolerate.
See, this is my point. It's not that testing is beside me, it's that my stuff gets tested anyway.
Here's the test: Does it fucking work or not?
You do that by running the thing. If it explodes, find out why and fix it. Job done. No thought or line of code was wasted in writing tests, all brain power was used to initially write a piece of code - which initially had a bug of course - and then said bug was fixed.
My code gets tested. By people using it. Or by me testing it as I write it ("does it fucking work").
There is really only one test.
You can choose to expend your brainpower and time on this planet writing code that will never actually be run by an end-user, or you can just write the fucking code that the end-user will run. That's how I work. Write it and run it. That's the test.
Test code written to test Useful Working Code is time wasted. It's like putting stabiliser wheels on bicycles - you're either gonna be stuck forever riding a bike with stabilisers, or you grow up and rip them off and have a few falls on the bike then become confident and competent enough to ride that bike without them. And have more freedom and time to experiment and go places you couldn't when they were put on.
So yeah. I definitely wouldn't work with people who like wasting my and their time on this Earth.
Write it. Run it. It either does what it's supposed to or not. If it doesn't, find out why and fix it. Or discover that your function/code abstraction/thought was shit in the first place then write it differently - oh and that's the worst part about writing code that tests the Code That Does The Work; say you discover that the function you're writing was a load of bollocks and needs to be highlighted and simply erased - there goes all that test code you spent brainpower and time, with it, too. And now you have to spend even more time writing new test code to test the Code That Actually Does Useful Work.
No thanks. And goodbye.
Users are not guinea pigs. They deserve better.
That's called functional testing and that's actually testing. You are one step removed from actually formalising what you do and getting non regression testing for free. At that point, I think you are either arguing fot the sake of it and do actually realise that testing is important or somehow confuse testing with unit testing which is only a narrow subset of it.
As the author of many of Linux’s x86 tests: many of those tests would fail on old kernels, and a decent number of those failures are related to very severe bugs. Linux has worked well for many years, but working well didn’t mean it wasn’t buggy.
As was said in another comment, tests don't prove the lack of bugs. There is no software of enough complexity without bugs.
Working is something ;) Lots of software barely does that and there is certainly plenty of software with tests that doesn't meet the no-test Linux quality bar.
That said, tests certainly have their place in the world of software quality, so thanks for your work!
My old man who will always gladly mention that „we did this already in the 80‘s and it was called frobniz“ whenever I bring up a technique, architecture etc. would beg to differ.
When I asked him about TDD he said they did practically the same thing. Forgot how it was called though.
One recent gem was when he shared a video where they explained the recent crowdstrike debacle: „Look they’re making the same mistakes as 40 years ago. I remember when we dynamically patched a kernel and it exploded haha…“.
In any case, writing tests before writing the implementation was a thing during the 80‘s as well for certain projects.
Of course there were tests, just not automated tests!
In better run organisations they had test protocols, that is, long lists of tests that had to be run by manual testers before any new version could be released. Your manager had to make sure these testers were scheduled well in advance before the bi-annual release date of your latest version of the software.
So that listing old software and claim that they didn't have much tests is misleading, to say the least.
The flip side of this is the quote that "tests can show the presence of bugs, but never their absence". It better fits my experience here; every few months I'd find a new bug and diligently write a test for it. But then there was a new bug in a few months, discovered by someone in the first 10 minutes of using it.
I'm sure I have bugs to discover in the new version. But the data structures I chose make many of the old tests obsolete by construction. So I'm hopeful that I'm a few bugs away from something fairly stable at least for idle use.
Tests are definitely invaluable for a large team constantly making changes to a codebase. But here I'm trying to build something with a frozen feature set.
If your tests break or go away when your implementation changes, aren’t those bad tests by definition?
A lot of tests don't survive implementation changes, that doesn't make them "bad tests by definition". It means their value came and went. Think of it like scaffolding. You need it for a time, then the time is up, and it's removed. That doesn't make it bad, it was still necessary (or at least useful) for a time.
When there's an implementation change you'll likely end up discarding a fair number of unit tests and creating new ones that reflect the new implementation details. That's just natural.
A lot of tests, especially unit tests, are just change detectors and get updated/go away when change happens, that is just WAI. It is fairly hard to write non change detection tests, it requires for you to really reason abstractly about the contract of your module, or to write integration tests that are moving a bunch of things at once.
small, fine-grained black box tests can be really good for this. in my last project, a type checker, the vast majority of the test suite was code snippets and assertions about expected errors the checker needed to catch, and it was an invaluable aid when making complex changes to the implementation.
Anything that transforms or processes text, like a compiler or type checker, is pretty easy to test. You get into trouble with user interfaces, however.
If that is the case too often, I ditch them and write integration tests for that part.
Yeah, especially when you're exploring new ground.
Unit tests are awesome for fleshing out APIs; but once the fundamentals are in place, the tests no longer add any value.
I have two answers:
1. Yes. To the same extent that we are all bad people by definition, made of base material and unworthy urges.
I'd love to have better programmers show me how I can make my tests better. The code is out there.
2. Even if I have good tests "by definition", a radical rewrite might make old tests look like "assert(2x1 == 2), assert (2x2 == 4)". Tests exist in a context, and radically changing the context can change the tests you need.
---
This is not in OP, but I do also have a problem of brittle tests in my editor. In this case I need to test a word-wrapping algorithm. This depends intimately on pixel-precise details of the font. I'd love for better programmers than me to suggest how I can write tests that are robust and also self-evidently correct without magic constants that don't communicate anything to the reader. "Failure: 'x' started at x=67 rather than x=68." Reader's thought: "Why is this a problem?" etc. Comments appreciated on https://git.sr.ht/~akkartik/lines.love/tree/main/item/text_t.... The summary at https://git.sr.ht/~akkartik/lines.love/tree/main/item/text_t... might help orient readers.
Good and bad are forms of judgement, so let's eschew judgement for the purposes of this reply :-).
Better is also a form of judgement and, so, I will not claim I am or am not. What I will claim to do is offer my perspective regarding:
Unfortunately, brittle tests are the result of being overly specific. This is usually due to tests enforcing implementation knowledge instead of verifying a usage contract. The example assertions above are good examples of this (consider "assert (canMultiply ...)" as a conceptual alternative). What helps mitigate this situation is use of key abstractions relevant to the problem domain along with insulating implementation logic (note that this is not the same as encapsulation, as insulation makes the implementation opaque to collaborators).
In your post, you posit:
I suggest they serve a purpose beyond when "in unfamiliar terrain." Specifically, these tools provide confidence in system correctness in the presence of change. They also allow people to reason about the nature of a system, including your future-self.
Perhaps most relevant to "brittle tests" are the first two you enumerated - types and abstractions. Having them can allow test suites to be defined against the public contract they provide. And as you rightly point out in your post, having the wrong ones can lead to problems.
The trick is, when incorrect types and/or abstractions are identified, this presents an opportunity to refine understanding of the problem domain and improve key abstractions/collaborations accordingly. Functional testing[0] is really handy to do this fairly rapidly when employed early and often.
HTH
0 - https://en.wikipedia.org/wiki/Functional_testing
Automated tests ideally don't entirely replace manually executed tests. What they do replace is repetitive regression tests that don't need to be executed manually.
In an ideal world this opens up room for exploratory testing where someone goes "off-script" and focuses specifically on those areas that are not covered by your automated tests.
The thing is that automated tests aren't really tests, even though we call them that. They are automated checks at specified points, so they only check the outcome at those point in time. So yeah, they are also completely blind from the sort of thing a human* might easily spot while using the application.
*Just to be ahead of the AI bros, we are not there yet, hold your horses.
I'm puzzled by people debating tests. why such hate? They catch bugs, prevent breaking changes, and ensure API stability. I have never seen tests preventing me from refactoring anything. I guess it depends on the company and the processes :thinking:
Because writing good tests is very hard and many engineers are simply mediocre so they write brittle tests that require a lot of time to fix and don't actually test the right things (e.g too many mocks) or simply overconfident (like some people in the thread) that their code will always work.
Also the TDD cultists are partially to blame for this attitude as well. Instead of focusing on teaching people how to write valuable tests, they decided to preach dogma and that frustrated many engineers.
I'm firmly in the circle of writing tests of course, I don't think a system that is not tested should ever be in production (and no, you opening your browser on a local machine to see if it works is not sufficient testing for production..).
Tests are tools - you won't be using screwdriver for everything, even though it's a tool that useful in many things.
Having said that - tests, codebase and data consistency, static types are things I'd not want to be without
There are different kinds of tests.
Integration tests at the outer edges often gives you most bang for buck.
Granular, mocked unit tests often add little value and will become a maintenance burden sooner or later.
And some of it is unconscious; maybe having that big, comfy test suite is preventing the software from evolving in optimal directions; because it would just be too much work and risk.
I think there is a mostly psychological "problem": tests are not perceived as progress (unless you are mature enough to treat quality assurance as an objective) and finding them fun to write or satisfying to run is an unusual acquired taste.
It's a trade-off. Most of the business world ran on, and to some extent still runs on, Excel programs.
There are no tests there, but for the non-tech types who created these monsters, spending time on writing a test suite has a very real cost - there's less time to do the actual job they were hired for!
So, yeah, each test you write means one less piece of functionality you add. You gotta make the trade-off between "acceptably (in frequency and period) buggy" and "absolutely bullet-proof no matter what input is thrown at it".
With Excel programs, for example, if the user sees an error in the output, they fix the input data, they don't typically fix the program. It has to be a dealbreaker bug before they will dive into their code again to fix the program.
And that is acceptable to them.
Not spending time on writing tests has a very real cost - a lot of time is spent on figuring out why your forecast was way off, or your year end figures don't add up.
Not to mention how big parts of the world are thrown into austerity, causing hundred of thousand dead, due to errors in your published research [0].
[0] https://en.wikipedia.org/wiki/Growth_in_a_Time_of_Debt#Metho...
Yes. That's what "trade-off" means.
My point is that there isn't a tradeoff between getting "real work" done or writing tests. Either you write tests, or you spend the even more time mitigating the consequences of not writing tests. You can't save time by not writing tests (except for the most trivial cases).
Tests written for pure functions are great. Tests written for everything else may be helpful but might not be.
You need tests for all part of the functionality you care about. I write tests for making sure that what is persisted is what we get back. Just the other day I found a bug due to our database didn't care about the timezone offset for our timestamps.
Not suggesting that testing other things isn't useful but not as straightforward and not as obviously beneficial as pure function testing. It is easy to just dogmatically pile on tests but they may not be helpful.
I wasn't a very fast typist, I could do about 180 strokes per minute. My teacher, a tiny 80 year old lady, talked the whole time to intentionally distract her 5-6 students. It was a hilarious experience. One time, when I had an extra slow day, the monologue was about her learning to type, the teaching diploma required 300 strokes per minute, from print, hand writing and dictation. Not on such a fancy electronic type writer! We had mechanical type writers! And no correction lint! She was not the fastest in her class by far and many had band-aids around smashed fingers. Trying to read type, not listen and not burst out in laughter I think she forced me down to 80 strokes per minute. Sometimes she had me sit next to a girl doing 450 strokes per minute. Sounded like a machine gun. They would have casual conversation with eye contact. I should not have noticed it, I was suppose to be typing.
When writing code and think about those "inevitable" bugs I always think of the old lady, who had 1000 ways of saying: you only think you are trying hard enough... and: we had no correction lint....
Take a piano, there is no backspace. You are suppose to get it right without mistakes.
If you have all of those fancy tools to find bugs, test code, the ability to quickly go back and forwards, of course there will be plenty mistakes.
If they need to be there no one knows.
World class best in the world gymnasts still fall off a balance beam from time to time.
Mistakes are inevitable, it’s why whiteout and then word processors were made
Pain is a great teacher.
> Never have I tested anything and NOT found a bug, and most things I tested I thought were already OK to ship.
I have found that, in my own case, every time I’ve written a unit test, it has exposed bugs.
I don’t usually do the TDD thing, where I write failing tests first (but I do it, occasionally), so these tests are usually against code that I already think works.
That said, I generally prefer test harnesses to unit tests[0]. They still find bugs, but the workflow is less straightforward. They also cause me to do more testing, as I develop, so the bugs are fixed in situ, so to speak.
[0] https://littlegreenviper.com/testing-harness-vs-unit/
That's a strange redefinition of harness.
The larger-scoped tests are more often called integration or even system tests.
And while I'm here, those are slow tests that are harder to debug and require more maintenance (often maintenance of an entire environment to run them in!). Unit tests are closer to what they test, fast, and aren't tied to an environment - they can be run on every push.
Completely agree on tests. It's much more enjoyable for me to write some automated tests (unit or integration) and be able to re-run them over and over again than it is for me to manually run some HTTP requests against the server or something. While more work up front, they stay consistent and I can feel more comfortable with my code when I release.
It's also just more fun to write code (even a test) than it is to manually run some tests over and over again, at which point I eventually get lazy and skip it for that last "simple, inconsequential" commit.
Coming from a place where we never wrote tests, I introduce way fewer bugs and feel way more confident every day, especially when I change code in an existing place. One trick is to not go overboard and to strike an 80/20 balance for tests.
I watched a video by Russ Cox that was recommended in a recent thread, Go Testing By Example:
https://www.youtube.com/watch?v=X4rxi9jStLo
There's _a lot_ of useful advice in there. But what I wanted to mention specifically is this:
One of the things he's saying is that you can sometimes test against a simpler (let's say brute force) implementation that is easier to verify than what you want to test.
There's a deeper wisdom implied in there:
The usefulness of tests is dependent on the simplicity of their implementation relative to the simplicity of the implementation of what they are testing.
Or said more strongly, tests are only useful if they are simpler than what they test. No matter how many tests are written, in the end we need to reason about code. Something being a "test", doesn't necessarily imply anything useful by itself.
This is why I think a lot of programmers are wary of:
- Splitting up functions into pieces, which don't represent a useful interface, just so the tests are easier to write.
- Testing simple/trivial functions (helpers, small queries etc.) just for coverage. The tests are not any simpler than these functions.
- Dependency inversion and mocking, especially if they introduce abstractions just in order to write those tests.
I don't think of those things in absolute terms though, one can have reasons for each. The point is to not lose the plot.
It depends a lot on what you work on and how you program. Virtually none of our software has actual coding errors, and when developers write new parts or change them, it’s always very obvious if something breaks. Partly because of how few abstractions we use, partly because of how short we keep our chains. Letting every function live in isolation and almost never being used by multiple parts of the software. Both the lack of abstractions and the lack of reuse is against a lot of principles, and it’s not exactly like we refuse to do either religiously, but the only real principle we have is YAGNI, and if you build and abstraction before you need it you’re never going to pass a code review. As far as code reuse goes, well, in the perfect world it’s sort of stupid to have a lot of duplicate code. In a world where a lot of code is written on a Thursday afternoon by people who are tired, their babies kept them awake, the meetings were horrible, management doesn’t do the right things and so on. Well, in that world it’s almost always better to duplicate code so that it doesn’t eventually become a complicated abstract mess. It shouldn’t, and I’m sure it doesn’t in some places, I’ve just never worked in such a place. I have worked with a lot of people who followed things like clean code religiously and the results were always unwieldy code where even small changes would take weeks to implement. Which is completely counterproductive to what the actual business needs. The benefit of YAGNI is that it mostly applies to tests as well, exactly because it’s basically impossible to make changes without knowing exactly what impact you’re having on the entire system.
What isn’t easy is business logic, and here I think tests are useful. Or at least they can be. Because far too often, the business doesn’t have a clue what they want up front. Even more often the business logic will change so rapidly that tests automated tests become virtually useless since you’re going to rely on acceptance tests anyway.
Like I said, I’m not religious about it. I sometimes write tests, but in my anecdotal experience things like full test-coverage is an insane waste of time over a long period.
He was basically starting over. Definitely need to delete the tests. One of the issues with enterprise development is choking the project with tests and other compliance shit as soon as people start coding. Any project should be in a workable/deployable state before you commit to tests.