Testing like the TSA
37signals.com
37signals.com
I quickly realized that my job was futile - many of the "good" tests had already been written, while in other places, "bad" tests were entrenched and the "test dev" had the job of manning the scanner to check for nail clippers, or upgrading the scanner to find as many nail clippers as possible.
Here's a useful rule on the subject that I picked up from an Artificial Intelligence course back in the day: The value of a piece of information is proportional to the chance that you will act on it times the benefit of acting on it. We all realize there is no benefit in testing if you ignore failures rather than acting to fix the bugs, but in much the same way that doing nothing when tests fail has no benefit, doing nothing when tests pass also has no benefit - so tests which always pass are just as useless as failing tests you ignore, as are tests which only turn up corner-case bugs that you would have been comfortable with shipping.
If you're doing the right amount of testing, there should be a good chance, whenever you kick off a test run, that your actions for the next hour will change depending on the results of the run. If you typically don't change your actions based on the information from the tests, then the effort spent to write tests gathering that information was wasted.
That is the point of the "changing behavior" rule - you do not gather the benefit of running a test until it has failed at least once, and the benefit gathered is proportionate to the benefit of the action you take upon seeing the failure. The tricky part of the rule is that you must predict your actions in the future, since a test that might have a very important failure later could pass all the time right now. Knowing your own weaknesses and strengths is important, as is knowing the risks of your project.
There are possible design benefits to writing tests, since you must write code that is testable, and testable code tends to also be modular. However, once you have written testable code, you still gain those design benefits even if you never run your test suite, or even delete your tests entirely!
Based on your follow-up, it is clear that my reading was not what you intended.
For example, if you personally tend to write off-by-one errors a lot, it's a good idea to write tests which check that. On the other hand, if you almost never write off-by-one errors, you can skip those tests. If test is cheap to write, easy to investigate, and covers a piece of code that would cause catastrophic problems if it failed, it's worthwhile to write the test even if you can barely imagine a possible situation where it would fail - the degree of the cost matters as much as the degree of the benefit.
You don't "know" when it's OK to nuke a test just as you don't really "know" when it's safe to launch a product - you decide what you're doing based on experience, knowledge, and logic. The important step many don't take is developing the ability to distinguish between good tests and bad tests, rather than simply having an opinion on testing in general.
When we say that the future will be similar to the past, for code, we really mean that the probability of certain events occurring in the future will be similar to their prior probability of occurring in the past.
In my hypothetical example of testing an invariant that is unlikely to fail but damaging if it does, it might be valuable to keep that test around for five years even if it never fails. Imagine that the expected frequency of failure was initially <once per ten years>, and that the test hasn't failed after five years. If the expected frequency of failure, cost of failure, and gain from fixing a failure remain the same, we should keep the test even if it's never failed: the expected benefit is constant.
Not to say that we should test for every possible bug, but if something is important enough in the first place to test for it, and that doesn't change (as calculated by expected benefit minus expected cost of maintenance), we should keep the test whether or not it changes our behavior.
Thus, if we could estimate probabilities correctly, we really would know when it's OK to nuke a test.
I can see the usefulness of SDETs in doing system / end-to-end testing or testing of really obscure scenarios, but most of the test writing should belong to devs. I love the Rails approach to UT, functional and integration test split. The first time you try BDD, especially if you're coming from an after-the-fact testing culture like the one above, you almost want to cry from joy. I agree that Cucumber might a bit of an overkill, but perhaps I don't get it. For a non-prototypey project you should absolutely add other types horizontal testing like performance, security..
> We all realize there is no benefit in testing if you ignore failures rather than acting to fix the bugs, but in much the same way that doing nothing when tests fail has no benefit, doing nothing when tests pass also has no benefit - so tests which always pass are just as useless as failing tests you ignore, as are tests which only turn up corner-case bugs that you would have been comfortable with shipping.
On the contrary, my opinion is that we don't see this often enough. Our bug database shows the current list of defects, and there is very very very little data on what does work. What is test covering, and how many defects are they finding within that coverage?
If your bug trend is heading downward, is it because the test org is distracted by something, or because there are fewer bugs that they are encountering?
This is the danger of having a 'test org' separate from the 'dev org'. When writing tests is tied to writing production code, your progress in one is tied to progress in the other. If it's easy to write tests for a particular feature, then the developer stops writing tests and writes the feature once they're done with the easy tests. It's much easier to understand your coverage when you're actually working with and writing the code being covered, rather than working in some separate "test org" - you don't need to run a coverage tool, you just see what breaks if you comment out a chunk of code. If the answer is "nothing" then it wasn't covered!
At the end of the day, an automated test suite is a product for developers on your project in the same way that a car is a product for drivers. You will have a hard time making a car if nobody on your car-making team can drive, and you will have a hard time writing a test suite as a tool to develop Project Foo if nobody on your test-writing team develops Project Foo.
I now write a project where I handle both the code and the tests. In the same way that my production code is improved by writing tests, my tests are improved by writing production code. I know what is included in the test coverage in the same way that I know what I had for lunch today - because I was there and I remember what happened. Tools for checking coverage are an aid to my understanding; in a company with a separate test org, you don't know anything about coverage until you've run the tool.
I completely agree with you, in light of how MSFT categorizes their ICs these days. There used to be three test/dev disciplines when I started (in 2001): "Software Test Engineers", "Software Development Engineers in Test", and "Software Development Engineers" (you likely know this already, but it might be useful history for others to know). Around 8-9 years ago the STE discipline disappeared; the company hired for SDETs (like yourself) from that point on.
The big loss here was that now all STEs were expected to be SDETs - writing code on a regular basis to test the application. There were (notice the past tense) many STEs who knew our application very* very well, and knew how to break it, hard. STEs provided quality feedback at a higher level than what a unit test provides - does the application feel right? Are customers getting their solutions solved? If you have your eyeballs stuck inside a unit test it's difficult to step 30 feet back and ask yourself if you're even doing the right thing.
Nowadays I feel like the pendulum is slowly swinging back the other way, and there is less drive to have test write automation (and maybe it is because where we are in the product cycle). I understand that high-level STEs "back in the day" were probably concerned about potential career progression at the time, which might be why they eliminated STEs (I have no idea of the real reasons - they've been lost to Outlook's mailbox size limits), but all-in-all I think MSFT is poorer because of it.
(If you are designing a life-critical application, the scope of this article doesn't apply to you, as is stated in the article)
"Libraries should be mostly unit tested. Applications should be mostly (and lightly) integration tested. Naturally, some parts of a complex app will behave like a library..."
Agree or disagree?
The basic thing about tests is that once you have them passing, they represent statements about constraints on the program. In other words, they express your opinion of things that should not change. Unit tests are a bet that certain aspects of the implementation will not change. Integration tests are a bet that certain aspects of the externally visible behaviour will not change.
Libraries tend to be smaller and with well-defined responsibility. Applications tend to be bigger and have many responsibilities. In general, I think it’s true that the requirements for libraries change less often than the requirements for applications. I think this leads us to expect that applications may need to be rewired “under the hood” and have their implementations changed as responsibilities are added, removed, or changed.
This, I believe, leads us to want to unit test applications less, because a unit test expresses implementation semantics, and we expect application implementations to change. No what about integration tests? Well, if we’re unit testing less in the application, we need to make up for it by integration testing more, otherwise where do we get our confidence?
Now if we throw the words “library” and “application” away, this suggests to me that those parts of the code that are small and tight and with a single, clear responsibility should be unit tested, while those parts that involve a lot of what the AOP people call ‘scattering and tangling,’ should be integration tested.
Thoughts?
My integration tests serve two purposes:
1) Runs the code in an repeatable and isolated environment to verify I didn't do anything stupid like misname a variable, or treat something nil as an object.
2) Validate my unique application logic.
I don't think #2 goes away with other languages, but #1 changes dramatically. I've written Ruby for years, so this isn't an "outsider looking in" opinion.
Whether conscious or not, I believe move Rubyists gravitate to testing because of both #1 and #2.
#2 can be satisfied with unit tests, but in my experience, the suite becomes a lot more flexible when validating it in terms of integration tests.
I use this sort of stuff extensively when doing mathematical computing and statistics because there's usually a clear mathematical boundary. Once you're inside it, it's relatively easy to write down global properties (theorems) of your code's API.
The moment you cross that boundary your testing apparatuses have to get more complex and your tested properties less well-defined. Unit tests are hazier than quickcheck properties and integration tests hazier still.
This continuum seems to be precisely the same as the code reuse continuum. Highly abstracted, testable code with a shapely API is a highly reusable library whether you like it or not. Maybe it's being called by other code, maybe it's being called by your UI, maybe it's being called by the user themselves.
If a random testing framework is available in your language they really should be integrated as they are able to come up with some pathological examples.
I've seen teams that aggressively put all non-glue code in libraries, but I don't think that is what is meant here.
Hence I try to divide my projects into lots of application agnostic code (the libraries that need to be unit tested), and little application specific code (the glue code that needs to be integration tested).
In my opinion the point of building out a test suite isn't to reach 100% code coverage, it's to allow developers working on the application the ability to refactor quickly and aggressively during the development process while remaining confident that it will still perform correctly when they're ready to release. Poorly written tests (or over-testing) will slow down refactoring and hinder development. But, in my experience, not having the right tests can mean firing up your application and manually testing after each small code change to prevent regression. Understanding the trade-off is key to helping developers answer the ubiquitous question: "do I need to test this"?
This role for unit tests is described effectively in Chapter 1 of Martin Fowler's awesome Refactoring book (or Refactoring, Ruby Edition, if that suites you. That's the version that's on my desk right now.) This use case is also highlighted in Eric Evans' book Domain Driven Design, where he highlights one of my favourite application development techniques: "refactoring to deeper insight." Both books are among my favourites.
Adding features to an application isn't just about appending files and lines of code, it's also about changing existing code to allow the new features to fit comfortably into the application's domain model. If you can accomplish that goal with "light integration tests" go for it. That'll mean much less overhead during refactoring and will do a good job of enforcing correctness. If your application contains a robust domain model (which it very well might) you may find unit tests useful for maintaining model integrity during development. This is probably what's meant by "Naturally, some parts of a complex app will behave like a library". When you change the domain model you'll have to change tests for all the affected classes and that's a good thing. It explicitly highlights how your change affected the model, which you should certainly understand before committing your changes.
I will admit this distinction is hard to communicate to first-time TDDers. However, I've found that after having to eat their own dog food for a few iterations (adding features to your own code) smart developers quickly find the sweet spot. The less code you write, the less you have to maintain. But too few test or poorly written tests can slow you down just as much.
A problem that we ran into, though, is exactly what to test. The biggest basic distinction we ran into was testing to verify interface integrity (i.e., does this do what it says in the header documentation, pursuant to declared constraints and so on) as opposed to implementation integrity (does my ringbuffer properly move around elements, does my array resize move my elements, etc.).
The former is very useful to ensure that the library is good for users, the latter of course is much better for testing that changes to the containers don't do something stupid.
I would agree with unit test logic, integration test glue, but that is heading into truism country.
$container->build('app')->run;Thank God someone with a bullhorn finally said this. I was beginning to think I was alone in my hatred of Cucumber. (And my love of Test:Unit/Minitest.)
There's a group of devs that have fooled themselves into thinking anyone outside their group understands how they are testing. I've seen this firsthand. DHH has always been right, imo, in this regard; test what you think is important, use simple tools.
TestUnit still serves me well and it perfectly fits my needs of do more with less.
That being said, to a developer writing tests and doing TDD, Cucumber is a speed bump that doesn't need to be there. RSpec is great without lumping more crap on top of it.
I'm so tired of reading Rails job postings hammering down RSpec and usually also Cucumber experience as a prerequisite.
So let me add some nuances:
1) DO aim high though, just recognize that the work in getting there is probably better spent elsewhere in your app.
3) BUT ignore this advice if you don't write tests yet. When you learn to test, or start working on a new feature that you may not know how to test, it will take you as long to test it as to code it. From there on though, test cost of testing is pretty cheap, so the 1/2 or 1/3 ratios start to make sense.
4) Do test that you are correctly using features and libraries (yes, standard activerecord stuff is probably going overboard).
5) But dont forget that many bugs occur at the boundaries of functional units.
6) Do what works for you, and what makes sense for you code base and business priorities. I don't love cucumber myself, but when others swear by it I can see why they like it.
Kent Beck's quote at the end is lovely. The first and only book on TDD I read was Beck's, and it's good to know that he's not actually as dogmatic as the book makes you think.
Don't be afraid to unit test little complex things here and there. Are you writing a function to parse a string in a certain way? Pick that function, elevate its visibility if need be, write a simple unit test to make sure you didn't make a stupid off-by-one mistake. Does the rest of the class otherwise not loan itself to unit testing? That's OK, move on.
We've learned that each line of code is a liability, even if it's a configuration file, which is why we have come to appreciate things like DRY, convention over configuration, less verbose languages, less verbose APIs. Likewise, each line of test code is a liability, so each line better justify itself.
One thing that I've seen in inexperienced coders (including myself in the past) is that they tend to think of every bug as a fluke one-off mistake in an otherwise mostly flawless and awesome record. New coders tend to want to just fix a bug, then pretend it didn't happen.
This is exactly the wrong attitude to take. As a discipline, we programmers should be studying our mistakes and taking steps to prevent them in the future. As a craftsperson striving to improve, each of us should be studying our own mistakes and taking steps to prevent them in the future.
Also the way you quoted me above, "make sure you didn't make a stupid off-by-one mistake" looks like it's talking about writing just enough tests. However, in context, I'm actually referring to writing unit tests for small items where you might make a stupid off-by-one mistake.
So I was never referring to "one-off mistakes", as in mistakes that are flukes.
Your points are all good otherwise! Never rest on your laurels and always think about what you can do to catch your mistakes.
Thank you so much for making me aware of this.
I'm no Rails dev, so I'm curious about this one point from DHH:
> 6. Don't use Cucumber unless you live in the magic kingdom of non-programmers-writing-tests (and send me a bottle of fairy dust if you're there!)
I mostly do C#, and teams I've recently been on have found SpecFlow tests to be an excellent time saver in communicating requirements and acceptance test criteria with customers. Has Cucumber not been designed for the same purpose?
I might guess that David included the point because a product business such as 37signals has no non-programming stakeholders to communicate about requirements and acceptance criteria with.
Using BDD for having non-programmers write tests sounds far-fetched to me indeed. It's excellent to have them able to read and understand the tests, though. Any opinions? Is BDD as dead horse, or is DHH a little narrow minded here?
I thought what you thought - that this is DHH's working context speaking. For me Cuke-ish tests are about alignment and communication - not about having non-developers write tests.
(of course Cucumber isn't the only route for doing that)
I use test::unit and minitest and it gets the job done without having to keep up with the latest trends. It's simple and can be written in a way that correlates well to actual English requirements. It takes all of an hour to digest minitest from zero.
That said, Cucumber and rspec are very popular, so I may be the weird one.
We got stuck on some particular revision in the RSpec Subversion repository. The choice was re-write all the specs, or stick with that ancient version. We re-wrote all the specs -- to test/unit.
Several years later, and I have never picked up RSpec for my own use. However, I am working on another project that chose RSpec and it is working out pretty well. I have turned on render_views so I don't have to test those separately and am only using mocking for external services.
Cucumber, on the other hand, I do not understand at all. Why write tests in English when you have Ruby?
1) Although it's a Ruby tool, it works with a ton of languages. Someone can write code based off Cuke tests in Ruby or .NET or whatever without much trouble.
2) it makes web workflow testing cake
3) it keeps people strongly out of the "implementation" zone when they are thinking about how a program should be properly executed
4) it works with many spoken languages so if you're collaborating with an international team it could be useful there.
5) it has a whole bunch of report formats built in
If you don't find any of those features incredibly useful, I'm not sure you're going to ever see a need for it. I've played with it but for me it seems more hassle than anything. I do LIKE it but that's not enough to justify the time spent messing with it.
As for non-programmers reading tests, they could read the comments or README.md instead. Why force the documentation to test, and the test to document, if it decreases the efficiency of both?
Note, I'm not talking about your average "look, a fancy dropdown control on Github" type of project. I rather mean the "look, it's the entire software stack of a TV" or the "hey, this software computes taxes for all households in a country" type of thing.
The stakeholders in such domains typically know quite well what they want, in terms of their domain. The software people know how to turn that into working, maintainable and usable software. Getting the automated tests to also be the acceptance test spec saves a lot of double work, and, most importantly, a lot of human error (acceptance spec not matching acceptance test done, etc).
"No generalization is wholly true - not even this one" said Oliver Wendell Holmes.
What proportion of your time do you spend writing tests for a one-off bash script to fix some filenames? What proportion of your time do you spend if your writing the fly-by-wire code in a 777? You should spend "enough" time writing tests - where "enough" is extremely context dependent.
I also worry about this sort of advice because the reason many spend too long testing is that they're really bad a testing. I fear this group will use the quote as an excuse to do less testing, rather than get better at testing.
Also, to be honest, I'd be hard put to tell anybody how much time I spend writing tests vs. writing code. I've been practicing TDD for about ten years now - and I just don't think about "testing" vs "coding". I'm developing - which involves writing tests and writing code. Trying to separate them out and time them makes about as much sense to me as worrying about whether I type more with my left or right hand.
That's a good point. Test writing versus code writing is not black and white. Spending time writing tests is also time spent thinking about what's going to be coded. Writing tests should make the coding process go quicker such that there exists a lot of overlap in test writing and coding.
Here's a question to the HN community... For a library you write yourself to, say, access a Web service, how do you go about testing it? (For example, if you want to write tests against an Akismet gem)
I tend to write both unit tests and integration tests for it. My unit tests mock out the HTTP calls made by the library and only test that the library is able to handle both good and bad inputs.
My integration tests allow the library to speak directly to the web service in question, to test that the correct connection is being made and the service is providing the correct data back to the library.
Is this overkill?
1) Config, connections, or whatnot are one-time only. You're not going to screw up many times.
2) Not all WebServices have their own test-server so count yourself lucky if the ones you use have them :)
3) You're testing their code instead of yours if you have to make sure correct data etc comes back.
I usually only write unit-tests if they came back with bad data and we did not anticipate it (edge-case situation).
We may just phrase the same idea in 2 different ways, but I'd go with "don't force yourself to do 100% coverage if it's not that relevant" / "don't add a test for a simple getter if you have more important things to do". If you can do 100% and have time for it - you should definitely do that. For example in case someone rewrites your getter, but it's not that simple anymore.
For me the point of that one is that test coverage isn't the goal - good code is.
I've seen folk disappearing down a rabbit hole focusing on getting that last 2% of branch coverage using some baroque mock object monkey patched into the system. Their focus was on test coverage. What people who focus on test coverage get is an evil complex test suite that's very brittle in the face of change.
Other, smarter, folk go "damn - I can't test that easily - this code sucks", factor out unrelated functionality into appropriate classes, add some code that makes some duplication between branches obvious, factor out the duplication and end up with something that's better code, with simpler tests - and better test coverage too. Their focus is on the code - not the test coverage.
[Edited for... erm... English]
Using a tool (or a medicine) to do something else than what it was designed for rarely has a positive effect. Cucumber was not designed to be a testing tool. Most people don't realise this, and end up with a verbose, unmaintainable mess. They blame the mess on the tool without realising that they used the tool wrong.
I don't use Cucumber to test my code. I use it to discover what code I need to write. This is a subtle, but important difference. This approach can work whether there are non-technical people involved or not.
I typically start out with a single Cucumber scenario that describes from 10.000 feet how I want a certain feature to work - without getting bogged down in details.
This allows me to reason and think about the domain in a way that helps me write the simplest possible code. What usually happens (to me at least) is that I end up with a simple design that reflects the domain.
Last week I wrote a small application like this. It is 1000 lines of Java code. I have 6 Cucumber scenarios - a total of 100 lines of Cucumber "code" (Gherkin) and about 200 lines of Step Definitions code (the glue code that sits between the Cucumber scenarios and the app).
I could have used JUnit instead - or I could have just written the code without any tests at all. For me, this would have made it harder to start coding. I would have started off with a much more muddy picture about how the app needs to behave and how it should be designed internally. I would have spent more time experimenting.
If you use Cucumber as a starting point to discover the code you need to write - and keep your scenarios few and high level - then you're more likely to reap benefits instead of pain.
And never fall for the temptation to use Cucumber to test details. That's what unit testing tools are for.
But the only thing I use mocking/stubbing for is to mock out external services that make tests unreliable and slow (like HTTP requests).
Expectation based tests seem particularly easy to mis-use. Who cares how many times a method is called?
How do you test failure handling code when the failure can not be reproduced in a known way by the current code base?
I agree these tools are often overused, however they do have their roles.
I do use test doubles, including mocks, where things become difficult or slow to test (generally application boundaries). But keep in mind that every line of mocking code costs more than an equivalent line of the alternative.
Test x but don't test y can't be universal. Risks are (often radically) different in every app/team.
(from https://twitter.com/#!/dchelimsky/status/190096365794230274 )
@dhh and 37Signals have a different perspective on acceptable risk for their own product (which they maintain every day) vs developers writing software for someone else/handing it off to someone else. 1 dev to 1 project vs 1 dev to x projects. As a consultant, my acceptable risk level is very different than an entrepreneur trying to push out a MVP and I think testing will reflect that.
I think this depends on the domain. Some code ends up with a metric shed load of important edge cases because it's modelling something with a metric shed load of important edge cases - not just because your code sucks.
For example one project I worked on involved a lot of code to manage building estimates. It involved a stack of special cases related to building codes, different environments, the heuristics that the human estimators used, etc.
There wasn't any sane way to remove the special cases - the domain caused them. There wasn't a way to sensibly avoid writing tests - since we didn't really want estimates for the amount of drywall in a multi-million pound skyscraper to be wrong :-)
...in general, of course, there's always exceptions, don't be dogmatic.
In short, it's better to think of unit testing as bug-prevention mechanism. Thinking of it as an ultimate design mechanism doesn't end up well.
For decades, we've been conditioned to think that by the time a feature is written down on paper, someone else has crossed all the Ts and dotted all the Is to determine that the feature has value and should be built.
It's far easier to focus on low value testing issues than attempt the harder work of convincing the customer/product owner that certain features should not be built.
I think this is stated backwards. A code-to-test ratio of 1:3 is lower than 1:2, and based on the wording (smell, stink), it sounds like David is saying it's higher.
I also am utterly opposed to the Cucumber-style programming-in-English-via-regexp testing approach. Unless someone who doesn't know how to code is writing those step files, why subject yourself to that?!
First, I've found tests to be an excellent way to debug, much better than trying to manually get the app into the state I want and then stepping into the debugger.
Second, having a good coverage lets you refactor with a lot more confidence. I often open some file to fix a bug/add a feature and notice some piece of code that is horrible. I can comfortably fix that code too and if the tests pass (including the one that failed before fixing the initial bug/adding the feature) I'm done.
These metrics don't mean anything.
[1] http://googletesting.blogspot.com/2010/07/code-coverage-goal...
Here's the Stack Overflow post that I think triggered this post:
http://stackoverflow.com/questions/153234/how-deep-are-your-...
Yes, he said "fuck". Let's get past that, because it's a super boring discussion to have on HN.
Also: pointing out specific 37signals or Rails bugs as evidence that this view is flawed? Also a super super boring way to argue. Argue with the idea, not the proponent of the idea.
Ok, feel better? Now comment on this story.
The only part I agree about is the view testing and partially cucumber.
Then again I dont think 37s has a large amount of business logic to test (compared to some of the other commenters in here) and their testing is mostly front end, and testing front end ajax-y stuff with cucumber, does indeed suck.
It means the unit tests will have to be deleted, and or updated. If each takes 10 Minutes that is 6000 Minutes.
On the other hand, seeing one unit test fail in another module you didn't expect to fail can save you a week of work, so it's all in how brittle and organized the tests are.
If there is one "concept" that you're asserting in your test suite, you want that concept to only be repeated once or as close to once as you can get. Often times, people copy paste sets of assertions that have mixed concepts. Then, when a requirement changes, hundreds of tests end up having to be updated.
This is why many large companies, that contract out test automation development or train manual QA people with enough programming skills to write tests, end up with brittle test suites. One look at those test suites by an experienced developer and it's no wonder: there's conceptual/semantic duplication everywhere.
https://github.com/rails/rails/issues/5744
That said, I generally agree with DHH that many developers write too many of the wrong kinds of tests and this can make products brittle.
I share his disdain for Cucumber and rspec. I prefer clarity over syntactic sugar and non-programmer readability.
My primary objection to TDD is that it doesn't seem to work, because when I've tried it, the tests caught no bugs at all. I believe that tests can be important for APIs to prevent regressions when you have to work with external components, but its frustrating to put time into tests and then never have the tests fail.
Its not that I'm a perfect programmer, its the kind of bugs I make. The kind of bugs I make are caught by the compiler. This may be because, over the years of my career (many of which occurred long before the idea of "test first" was widely heard) I've trained myself in a style of programming where I can trust the compiler to catch my mistakes (most of which are typos, frankly.)
I don't know if others can do this, but for me, it came about by doing things like:
Old way: if (variable == 1) then whatever New way: if (1 == variable) then whatever
Every time I mistype that as "(1 = variable) the compiler catches it because I can't redefine 1.
TSA logic - somebody tried to get on a plane with a bomb in their show so everyone everywhere has to remove their shoes
PHB logic - somebody somewhere had a bug because of a misuse of inheritance so you aren't allowed to use any inheritance in your C++ code anywhere.