Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Unit testing private methods via public interfaces is accidental testing and leads to overly complex and brittle test code. The author’s first instincts were correct here.


This perspective is really interesting to me. I’d be interested in knowing what people find problematic with it.

I used to unit test all the components individually. But these days I test with most of the componentry wired up, only mocking the i/o layers. I end up writing fewer tests, they have been less brittle, and refactoring is easier.


If you want to expose issues in a code base’s architecture, don’t allow mocking frameworks and see how complex and lengthy unit tests become.


Yes if you eliminate an entire class of testing infrastructure, tests become harder to write because you end up poorly implementing that infrastructure in every class.

How does this show you "issues in the code base's architecture?"


When code is written to interfaces those interfaces can be mocked without mocking frameworks. Mocking frameworks mask poorly structured code.


Then you have to reimplement method call count when you need to assert something was called in your interface-based mock; and reimplement differing returns based on input or call count; and maybe reimplement throwing exceptions when those are necessary to test not-so-happy paths.

In the end you'll have an implementation of the interface with quite complex logic in the methods' implementation to handle differing returns, which will couple your mock implementation to the test cases setup, or you'll reimplement a mocking framework.

I prefer to use a mocking framework to deliver stuff instead of stabbing myself to keep purity of interface-based APIs.


You just rephrased "issues in a code base's architecture" as "mask poorly structured code" without saying how.


How is it accidental? The code can only do what its public interfaces expose. This indirectly (not accidentally) exercises the private methods.

If all the public interface test cases pass, isn't that enough?


I don't necessarily agree, I think it's situational but I think that the dev community as a group has swung too far to the "only test public interfaces" side lately. It ignores some important realities in favour of ideological purity.

Sometimes there are well-defined processes for performing a task, and that task is performed in only one place in the system. Therefore the details of the process can be kept class private inside of the only consumer. That doesn't mean that the processes should never be tested. If the task is cumbersome to set up or runs slowly then there is good reason to test the internal parts of the process which can be tested with dozens, hundreds or even thousands of permutations of input data cheaply and efficiently. Always relying on the large-scale tests to hit every combination of inputs for a well understood subroutine can be inefficient.

You could make the argument that this well-understood process could be broken out into its own class/package/module and tested with its own public interface, but if there really is only one consumer then that's kind of a strange trade-off to make in many cases.


> You could make the argument that this well-understood process could be broken out into its own class/package/module and tested with its own public interface, but if there really is only one consumer then that's kind of a strange trade-off to make in many cases.

That's how I develop in general: a "component" does not exist because it has multiple-clients, but because it is a conceptual piece of logic that makes sense to document and test in isolation. It allows to define what is the public API of this component and what isn't. This is how software scales and stays maintainable over time IMO.


I don't disagree, I'm just saying it's situational. The trade-off doesn't always make sense. But I typically develop in much the same way as you do.


In the vast majority of cases, The Enterprise, private methods are little to no use anyway. Whenever I’m faced with updating a code base that has Draconian code coverage metrics in place I’m more apt to make a private method public than futz around trying to figure out mock magic to test the change


Whether or not it is enough depends on the context. Some cases require 100% condition and decision coverage. In these cases, it gets to be very difficult to achieve that unless you are directly testing the functions you write, not through a caller.

But even deeper, it is "accidental" because you are not testing contracts on the public APIs, you're testing the private functions through a layer higher. When the implementation changes, you have coupled your tests so tightly to the implementation that they are useless for regression testing. If not, then you actually aren't testing the private function at all, you're just hitting some parts by coincidence.


There are a number of issues. First, the fundamental unit under test is the method/function, not the entire call stack. Second, complexity starts to grow when there are code coverage gates in place.

When I see tests that are orders of magnitude larger than the units they’re testing and contain mocked references that are used 7 stack frames deep I know the author had a fundamental misunderstanding of how to write helpful and maintainable tests.


Tests freeze code and design. Often this is exactly what you want but sometimes it's not what you want at all. If your internal interfaces are unit tested then you can't refactor easily because you'll break all your tests. However, if only your external interface is unit tested then those tests actively benefit refactoring the internals.


I agree. It's also not always possible to sufficiently stimulate a private function using public methods.

He was right to change his mind about the other things.

I was also skeptical about remote working, and while I think it is worse in most ways than office working, it's not a lot worse, and the lack of commute is sooo big a benefit that overall it's totally viable.


if your private methods are complicated enough that you're not able to achieve sufficient coverage through public interfaces it's time to refactor. make the private code a library that you can test in isolation.


This.

When I did C++ programming, I would test via public interfaces. If it didn't seem sufficient, or got messy, the reason would always be "This is a big class that is doing a lot of stuff." I would then identify all the things the class was doing, make classes for each one, and have instances of them in the original class as private members.

This way I could test the individual classes easily, and still test the (formerly) big class using only the public interface (with appropriate mocking as needed).

The main thing I learned from unit testing is to pay little attention to the word unit. Insisting on testing the private methods after the exercise above is usually a symptom of aiming for a level of intellectual purity that does not benefit the code.

(Of course, a better solution may be not to use OO to begin with, but that's a whole other discussion).


Exactly.

I worked in a place that disallowed friend functions and classes generally, but explicitly for unit testing. I never wrote another private function. Instead, there was an explosion at the tiny-free-function-that-could-have-been-a-private-function factory, and the parts ended up everywhere.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: