If you disagree, maybe an illustrative example would help. I couldn't think of one where I want to test a private method in detail that is not worth exposing.
One issue that leads to private methods in Java is the lack of any way to extend the set of operations on built-in classes: a problem that Kotlin and C# solve with extension methods.
Is the "only way" not property-based testing (and maybe fuzz testing)? If that doesn't get you there, it is likely that the API is poorly designed.
Some function behavior is intentionally and intrinsically tied to temporal access patterns in the API usage. Unless you can simulate a broad cross-section of real-world runtime API access timing patterns with your testing framework, you won't test all of the code paths. You often see this test problem with scheduler-like functions, where the correct function behavior varies based on internal resource pressures that are an interaction between temporal access patterns of the API and the runtime environment. It is a single function, self-contained, and quite simple, maybe not more than 100 LoC, but test environments are so sterile that usually only a single code path is actually used no matter what you throw at the API.
Some functions have critical code paths that dynamically switch strategies to mitigate when certain types of contention or resource starvation conditions are detected internally. These can be extremely rare cases across the set of possible inputs, such that fuzzing is unlikely to trigger them, or the set of inputs that can trigger them is dependent on exogenous environmental details e.g. the machine where the test is run.
As a fun side-effect, sometimes these functions do not have deterministic result. Getting the same result out of the function all the time does not imply correctness, you also have to know why you got the result you did.
All of the above does not apply to writing business logic in Java or similar. But for high-performance and/or high-reliability systems software, these cases come up often enough that it is a well-understood testing problem. Even if you expose all of the internal implementation detail to the unit tests it is not always possible to reliably trigger all the code paths from an arbitrary test absent purpose-built test tooling.
i am currently marking all functions as public for simplicity, but i feel like it's a failing of elixir and/or its test framework that i cannot say "private, but an associated test module should be able to see them", and once the code is done i will be exploring some of the third-party solutions people have come up with to hack around the issue. (the other option, of course, is to have a new module that just contains a couple of public methods, and regard the entire implementation module as a private module with public functions)
I'm not sure why all testing frameworks don't have this.
These days, I'm of the opinion that you should test at the highest level possible (if testing a web service, for example, at the level of the actual public API to the service), thoroughly define the behaviours expected for those APIs, and leave the insides of the service entirely untested, unless you have _very_ good reason to. (Critical code, payment code, hard-to-test code, etc.) This ensures you have the important behaviours tested, but leaves you free to modify implementation details as necessary without needing to alter or rewrite entire swathes of test code that suddenly starts failing because you decided to break up or consolidate an inner method.
perhaps relatedly, i like to build my code from the bottom up, and make sure each layer is solid so that i can use it to construct the next layer. there is often not a public api at all until i am well into the project, so for me that would involve writing fair bit of code with no tests at all simply because it was all private code.
i do use the "public methods on private helper objects" pattern a lot, but there is a fair mix of times where it's the best way to write the code and times where it's just to keep the test framework happy.
I go even further - for the browser level tests, avoid using “testid” or css classes or anything that is “implementation”, but rely in your test on solely things that the user can read / interact with.
So don’t “Press button with id “generate”, but the button that says “save” inside the content element titled “generation”.
This way any refactoring work would not require test changes (as it should) and any change of the visible ui / workflow to the user would require an adjustment to the test.
This is a style that I learned from ruby’s integration testing framework “capybara”, and have been replicating it wherever I can since.
A nice bonus is that if you switch rendering technologies, you can reuse the tests (like react native for example).
> This way any refactoring work would not require test changes
This is the purpose of a testid, your letting people know removing it can/will break tests.
(Especially e2e tests that might not only be ran by you/ might not live alongside the code)
> and any change of the visible ui / workflow to the user would require an adjustment to the test.
I really don't follow this reasoning; why would I want my tests to fail when the Accept button is renamed to Agree?
Eg: I’ve done some deep algorithmic work on CRDTs lately and my code has a lot of internal parts which are all quite fiddly to implement correctly. For example, I’m using a custom btree with some unusual features as an internal data structure. Btrees are famously difficult to implement correctly, so my btree has both unit tests and fuzz tests to make sure it does what I expect. Having that test suite makes integration tests easier to triage, since I know any failures in my integration tests probably don’t come from the btree. And some btree code paths are probably rarely or never executed by my integration tests. But I still want that code to be correct. Testing it in isolation is the best way.
They’re a lot of other small pieces that I test like this - like saving & loading code, my graph traversal utility code (for comparing versions semantically) and so on. Low level unit testing can find bugs in utility modules while you write the module itself, not later (when you start using the module in some other code).
https://learn.microsoft.com/en-us/dotnet/api/system.runtime....
<InternalsVisibleTo Include="assemblyname" />
Which means you can do stuff with msbuild props, like auto including a `$(assemblyname).Tests` into your projects.One of the lessons I've learned is that very little needs to be private, and there's a lot of advantage of not having things private.
Putting that in a clearly defined namespace allows access when needed, for testing or workarounds, while conveying the needed "if you use this you're on your own".
There is “public” within a module, and “public” for other modules. This is not on a per method basis, though.
And you can make a method “package-private”, and you can then test it right a way.
Class Access Modifiers were for controlling property and method inheritance, which has been bastardized into "CAMs tell you the API", because it's convenient. An object's API can be defined by an interface (following traditional OOP conventions), allowing CAMs to be used for their original orthogonal purpose. In practice, developers don't want to make an interface when they can slap a CAM on it and say "good enough". Some language don't even support interfaces, pushing the practice. So here we are with the wrong abstraction.
What exactly is being referenced here?
> Python has this (_ access)
Bypassing CAMs is useful for testing. Python has a mechanism, which is used for that^...regardless of the intent of the feature.
^Underscores are a partially enforced convention for private methods. If your import stripped your access to an attribute, you can still access via __getattribute__() which reaches into the hierarchy. There are other tricks as well. You might call this a special method lookup.
> If your import stripped your access to an attribute
Python doesn't have class access modifiers, and in particular import does not strip access to attributes in Python. There are two ways Python modifies behaviour in the presence of leading underscores:
1. If there is a module attribute (i.e. a variable in a module) starting with an underscore, it will not be imported when doing `import * from some_module` -- which is not recommended anyway. Importing `some_module` directly will give normal access to the attribute, as `some_module._foo`.
2. If a class or instance attribute's name begins with a double underscore, it will not be accessible directly; however its name is simply mungled at compile time:
> class Foo:
> __foobar = "__foobar"
>
> def __init__(self):
> self.__bar = "__bar"
> self.__baz__ = "__baz__"
>
> Foo.__foobar
AttributeError: 'Foo' object has no attribute '__foobar'
> foo = Foo()
> foo.__bar
AttributeError: 'Foo' object has no attribute '__bar'
> Foo._Foo__foobar
'__foobar'
> foo._Foo__bar
'__bar'
> # Note that does not hold for attributes both starting and ending in double underscores
> # (so-called 'dunder' attributes):
foo.__baz__
'__baz__'
You are correct that underscores are a convention, but in Python one simply cannot count on the kind of access control that exists in other languages, especially when writing code that is supposed to be executed in unknown contexts (i.e. a library). When writing tests, one presumably has full control over both the code and the execution context, so this kinds of tricks can be used relatively safely; that being said, test runners like pytest tend to provide their own abstractions and tools around those tricks anyway.Ironically, there's a story on the frontpage of HN right now, regarding attribute handling in python, which may be of interest: https://lwn.net/SubscriberLink/943619/eaa8a4496fcba1fd/
I.e., assuming my_module has function foo, instead of:
from my_module import *
foo()
Use this: import my_module
my_module.foo()
Or this: import my_module as mm
mm.foo()You also can simulate it in JavaScript by using a closure and a separate package for your "private" functions. By mixing the "private" code into the closure you can make it private in usage but still available to unit tests.