Testing: Exposing internals and utilities #681
Replies: 2 comments 2 replies
-
I see two embedded questions: How should we name functions that assumes an argument is checked?One example of this is the tiered sparse merkle tree. When we are computing the hash of a node of a ordered list, after we deserialize the list from the disk, we can assume it was serialized as ordered, so we don't need to re-order it every time. However, if the serialization was faulty, then we will end up computing invalid nodes. We can have two methods The pros of this approach (of having two methods; one checked, the other unchecked) is that we allow the user to skip unnecessary redundant operations when he is certain the conditions are met for an argument (i.e. the list is serialized in its sorted state). The cons is that we increase the likeliness of bugs as there might be an error or divergence between the expected sort strategy and the one implemented for serialization. We can mitigate this con by requiring the API to always provide a method with its "unchecked" counterpart that will take the argument in arbitrary form and transform it into the expected form (in that example, sort the list). Another con is that we increase API complexity. Having a single Functions that might transition the component into an invalid state.This is often desirable for stress test as we might want to check if the component will panic under certain conditions. However, these should never be exposed by the API because, regardless of the user input, the component shouldn't ever be invalid. There is a rather clear differentiation from the previous item, and it is the fact that the previous item handles the output of a function. Meaning: the output will be invalid if the input is invalid. This case covers the internal state of the component, that should always be valid. We can benefit of such risky API in some cases if we assume certain behavior of a dependency, and couple the internals of the component to these assumptions. But, before we do that, I think we should have clear answers to the following questions: Why can't we just create a new API on the dependency?One possible answer to that question is "this is an external library that we don't have control of". One case that might happen is when something is defined in winterfell, and we can't really change that code as it might impact somewhere else. Another possible answer is the dependency itself won't benefit from exposing such API as it might just forward the problem of the possible inconsistent state to itself. In that case, it is desirable to move these unchecked functions as downstream as possible, covered by robust tests. They are often ad-hoc routines, and should be treated as such. The biggest con of this approach is that we create a hard couple between the versions, making it much harder to upgrade to latest versions as the internals of the dependency might change, breaking the previous assumption. Why can't we create an intermediate component that will handle these states?It might be overkill sometimes to create components for everything, but this is ideal. Is the dependency API insufficient and should be refactored?If we face such needs, it is very likely the dependency API is poor. It should, ideally, provide safe and checked options to manipulate its state, covering all use-cases. Is this meant to only test a critical edge case of the current API?This case would be a solid yes to use an |
Beta Was this translation helpful? Give feedback.
-
2023/02/14: We had a discussion and decided to start by exposing the |
Beta Was this translation helpful? Give feedback.
-
This is a discussion to gather the pros and cons of exposing testing utilities and data structures / algorithms internals for testing purposes.
Some of the drivers behind this are re-use of testing code among our different projects, and easier testing.
Beta Was this translation helpful? Give feedback.
All reactions