Live data from Hacker News

The day I started believing in unit tests

mental-reverb.com

141–150 of 269 posts

Re: The day I started believing in unit tests

#141
post #86

Earlier quoted context omitted.

> if we want to call ourselves engineers we need to hold ourselves to engineering standards. Bridge builders do not get to skip tests. Bravo. We need more of this mindset in the world, and also more collective will to encourage it in one another. YOU are the kind of engineer I want writing the code that goes in my Dad's pacemaker or the cruise control in my wife's car.

If you have worked in places where safety is critical, you wouldn’t say something so shallow. In those places they place human verification above all else. They have a thick book where you do a full run and is double checked, they don’t f around with unit tests and say this is good to go

Human verification is very expensive, compared to unit tests. It costs money to pay that human to do it, time for them to test it, time to describe issues found, time to send it back for a fix.

Unit tests - actually, all automated tests - are comparatively cheap. The developer can run them immediately.

All code will have bugs. The "trick" to building a productive development pipeline is to catch as many of those bugs as possible as early as possible, and thereby reduce both the temporal and monetary cost of resolving them.

Re: The day I started believing in unit tests

#143
post #134

Earlier quoted context omitted.

> Well, I certainly don't end up doing that. How else are you "unit testing every single function and method"? > it turns out to be very difficult to test a specific behaviour 5-10 layers deep from an external interface. If you don't test it, how do you know your system works for that specific behaviour? Just because you've tested every single function and method in isolation doesn't mean they actually work with each…

> How else are you "unit testing every single function and method"? There are many ways to write unit tests, as well as writing code that is easy to test. I don't know how my ways differs from yours, but I don't have much of the problems you mention. > Otherwise how do you know that your system still works? We do have some integration test, of course. But it's a small part of the total test suite.

> I don't know how my ways differs from yours, but I don't have much of the problems you mention.

You haven't answered "How else are you "unit testing every single function and method"?"

Given a medium-sized project and at least a passing and a failing test case for each function and method, you end up if not with hundreds, but with dozens of tests largely doing the same thing.

> We do have some integration test, of course. But it's a small part of the total test suite.

So what does your test suite contain? Lots of unit tests for each method and function. What else?

Re: The day I started believing in unit tests

#144

Unit tests are not even well defined. What is a unit?

Something needn't be well-defined to be valuable. :)

If it helps, think of "unit tests" and "atomic tests". Your goal in writing a unit test is to test the smallest possible amount of logic at a time, with the least possible overhead (i.e., mocking).

The advantages of this approach are many: it helps keep the level of complexity of individual methods low enough to be quickly understandable, documents the interface provided by your methods, ensures that the tests run quickly, and allows new tests to be written with minimal effort.

Obviously there are disadvantages, too. Unit tests - any tests - take time to write. This is sometimes offset by the time saved by catching issues as early in the development cycle as possible, but not always.

For "greenfield" projects especially, I tend to take a different approach than in my other work. For those, I start by "writing the README". It doesn't matter if it's an actual README.md; the point is to write down some examples showing how you think the new functionality should be used. Once that's done, I'll stub out an implementation of that, then refining it with increasing granularity until the overall architecture of the project begins to be defined. Sometimes, that architecture is complex enough that it's worthwhile to break it into smaller pieces and start the process over for those. Other times, I get to a working "happy path" pretty quickly.

Once I have a minimally working feature, I write tests for the public-facing interface. Then the interfaces between domains inside the project. Then unit tests for individual methods. I mostly work in Python, so this is also the point where I pause and apply type annotations, write/expand my docstrings, ensure that my `__all__` objects are set properly, make sure any "internal use" methods of publicly exported types are prefixed with `_`, etc.

On the other hand, when I'm writing a feature or making a change to a more mature codebase, I often _start_ by writing tests. Sometimes that's a new interface that I'll be using elsewhere, so I'll write tests defining that. Sometimes it's a change in behavior on an existing implementation, so I'll write tests for that. Either way, from that point on I repeatedly run _only_ the new tests that I've written as I build out the feature. Only once the feature works and those tests pass do I re-run the whole test suite to check that I've not broken something I hadn't considered. When those pass, I'll go back over my code one more time to make sure that I've added tests for all of the relevant internal stuff before submitting the patch.

Re: The day I started believing in unit tests

#145
post #73

It's all degrees. Unit tests are great at finding examples of errors or correct behaviours. However they prove nothing and they definitely do not demonstrate the absence of errors. They are often sufficient for a great deal of projects. If all it takes to convince you it's "good enough," are a handful of examples then that's it. As much as you need and no less. However I find we programmers tend to be a dogmatic bunc…

> they prove nothing If they fail, they prove there's a bug (in either the test or the code.) This is like literally any other kind of test.

I meant "prove" as in, "mathematically proven." That is, for all possible inputs your theorem holds. A unit test is only an example of one such input. They don't prove there are no bad inputs.

There are many places in programming where you don't care to prove properties of your program to this level of rigor; that's fine -- sufficiency is an important distinction: if a handful of examples are enough to convince you that your implementation is correct with regards to your specifications, then it's good enough. Does the file get copied to the right place? Cool.

However there are many more places where unit tests aren't sufficient. You can't express properties like, "this program can share memory and never allows information to escape to other threads." Or, with <= 10 writers all transactions will always complete. A unit test can demonstrate an example of one such case at a time... but you will never prove things one example at a time.

Re: The day I started believing in unit tests

#146
post #106

It is a little sad to see so many be so dismissive of unit tests. They aren't a universal solution, which seems to be why they are written off in many cases, but they make your life so much easier in so many cases. If you need to mock out 80% of a system to make your unit test work, then yes, it's potentially pointless. In that case I'd argue that you should consider rewriting the code so that it's more testable in i…

I have tried to evangelize unit testing at each company I've worked at and most engineers struggle with two things. The first is getting over the hurdle of trusting that a unit test is good enough, a lot of them only trust an end-to-end test which are usually very brittle. The second reason is, I think, a lot of them don't know how to systematically breakdown test into pieces to validate e.g. I'll do a test for null,…

I've evangelized against unit testing at most companies I work at, except in one specific circumstance. That circumstance is complex logic in stateless code behind a stable API where unit testing is fine. I find this usually represents between 5-30% of most code bases.

The idea that unit testing should be the default go to test I find to be horrifying.

I find that unit test believers struggle with the following:

1) The idea that test realism might actually matter more than test speed.

2) The idea that if the code is "hard to unit test" that it is not necessarily better for the code to adapt to the unit test. In general it's less risky to adapt the test to the code than it is the code to the test (i.e. by introducing DI). It seems to be tied up with some sort of idea that unit testability/DI just makes code inherently better.

3) The idea that integration tests are naturally flaky. They're not. Flakiness is caused by inadequate control over the environment and/or non-deterministic code. Both are fixable if you have the engineering chops.

4) The idea that test distributions should conform to arbitrary shapes for reasons that are more about "because google considered integration tests to be naturally flaky".

5) Dogma (e.g. uncle bob or rainsberger's advice) vs. the idea that tests are investment that should pay dividends and to design them according to the projected investment payoff rather than to fit some kind of "ideal".

Re: The day I started believing in unit tests

#147

Earlier quoted context omitted.

A sort of non-judgmental question, in your mind are you writing them to cover lines or exercise required behavior with an intent of proving the module is broken? I ask because it seems like requiring line coverage as a metric would have the effect you are describing.

I've seen the same thing with comments. My boss required us to add comments to our code, to make it easier to read. That was all he asked, please add comments. My co-worker added comments like "Increase variable i by 1", while completely ignoring the 8 lines of spaghetti business logic above. Similarly I've seen people add tests that will ensure that code coverage doesn't go down, but it doesn't actually do anything…

Ah, Goodheart's law ruins everything.

Re: The day I started believing in unit tests

#148

Earlier quoted context omitted.

> In actual practice, about 99% of unit tests I see amount to "verifying that our code does what our code does" and are a useless waste of time and effort. If you rephrase this as, "verifying that our code does what it did yesterday" these types of tests are useful. When I'm trying to add tests to previously untested code, this is usually how I start. 1. Method outputs a big blob of JSON 2. Write test to ensure that…

The problem with this for me is that most of the time "verifying that our ccode does what it did yesterday" is not a useful condition : if you make no change to code, its going to do what it did yesterday. If you do make a change to the code, then you are probably intending for it to do something different, so now you have to change the test accordingly. It usually just means you have to make the same change in 2 dif…

Then think of the unit test as the safety interlock.

Re: The day I started believing in unit tests

#149

>It is a little sad to see so many be so dismissive of unit tests. You're preaching to the choir. The overwhelming majority of people worship unit tests like dogma. There's almost no point in saying the above. It's like saying it's a little sad to see some people who are so dismissive about eating and breathing to stay alive. Your next part is the one that's interesting. Mocking 80 percent of a system to get unit tes…

Sitting on a call right now where a guy is going on about how excited he is to mock out the entirety of a large e-commerce vendor's platform. It's maddening.

Re: The day I started believing in unit tests

#150

I have an unrelated (and most likely dumb) question about the article. When they talk about the inheritance relationship between 'Thread' and 'MyThread' in the example code in reference to the destructor methods, particularly here: > Now, what happens when MyThread::singlepassThreadWork() uses a member variable of MyThread like foobar and we delete the MyThread object while the thread is still running? The destructio…

You're right, the wording is confusing. It should be "parent class". There is only one object, a MyThread object. In C++ when an object is destroyed, all the destructors in the hiearchy run, from bottom to top. So first ~MyThread and then ~Thread. Anyway I think it is odd design to stop the thread in the destructor. You'd normally stop the thread first and then destroy the object, not the other way around?

They might be trying to encapsulate things so that they are sure threads get stopped when the objects go out of scope.

But, I would probably do that by having a class that contains both the thread and the data that the thread needs to access. Then its destructor could first join the thread and then clean up the data. For example, instead of a WorkerThread that contains a vector of WorkItem, have a BackgroundWorker that contains a Thread and a vector of WorkItem.

Post reply on HN