-
-
Notifications
You must be signed in to change notification settings - Fork 224
We should have every API backed up by an interface, in a non-static way #55
Comments
While mockability has some value, I have several concerns with this proposal:
I also wonder just how valuable mocking at the P/Invoke level would really be. How likely is outside caller code into p/invoke methods to be testable just by mocking up these interfaces, and is that really what is going to be the easiest way to test it? Personally, I'd lean toward wrapping P/Invoke method calls in a higher level API which is what I would mock out, if I mocked at all. I think I've mentioned in the guidance doc that this project shouldn't become a high level API for any individual library. It's strictly for P/Invokes, and a few helper methods when the P/Invoke method is so difficult to call that it requires special memory handling, marshaling, etc. would where a strong case can be made that virtually every caller would greatly benefit from a higher level API for the method. In other words, if a library or collection of functions could benefit from just a more managed friendly API with specific use cases in mind, that's a great idea for another library -- not PInvoke. And that other library may certainly bring in PInvoke as a dependency as it adds a higher level API to it. Everyone who needs to P/Invoke will need the P/Invoke signatures. As we add additional features on top of it, we actually filter out potential customers as they look at it and decide this library is too specialized for them and they'll look elsewhere. I feel that the rise in cost for development, the increase in assembly size, and the (IMO) limited audience that would want to leverage the mockability of the library suggests the cost is higher than the benefit. So my inclination is to Won't Fix this. But I'm open to discussion for a short time to see if you or anyone else wants to chime in. |
If we used #56 for this, it would be very easy to make this extra level. Furthermore, even for a megabyte NuGet, it isn't bad. 1 megabyte of memory is nothing, and neither is 1 megabyte of diskspace. To see an example where this really shines, I'll refer to some of the code I wrote recently (I'll find it and get back with a concrete example). I only think it makes sense if automatic generation is possible. |
Most of my concerns would be resolved if that were the case. I look forward to what you get back to us with. |
I agree with @AArnott on that one, it doesn't feel like the correct level to mock and losing the I'm not concerned so much about library size and having a second "interface + non-static class" version of the API could be a way to have such a "mockable" version (Kinda like Rothko does for IO in the framework) |
Here's an example from a project I am working on. Is entirely untestable because of one single call to This repeats itself in a lot of scenarios, and I believe we live in a world where testing is a must, and isolation is important. This library has the potential to transform the way we work with PInvoke, not just making it more accessible. And yes, automatic generation is a must. Here's a proposal in regards to the lack of being able to use |
Yes, automatic generation and still exposing the static API would make me feel better. |
True. The naming is details though. |
The first example of this generator is here: #70 |
Another consideration with interfaces is that they are not immutable, by nature, in this project. Because every new API we add coverage for will add a member to this interface, it is technically a breaking change. |
That is why we auto generate the members. Also, are you really concerned that somebody will implement the interface themselves? Why would anyone do that? And no, static methods can't be faked. |
My point was that they wouldn't and shouldn't. If we define these interfaces, it's under the goal that folks will only find them useful when mocking using a tool that generates code. But nevertheless it is a public API that incurs breaking changes with most releases and thereby breaks semver rules to simply add an interface with a minor version update -- so I feel better if we make it very clearly documented that these interfaces are not meant to be implemented except by such tools.
Yes they can. I know of at least two test frameworks that can do this, including shims as supported by Microsoft Fakes. |
Ah yes. I forgot about shims. But they alter the assembly they fake and that's how they get away with it. Not sure how other frameworks would do it. Most people I know use Moq or NSubstitute. |
I've never used Fakes, but from what I've seen partner teams' experience is, I'd rather avoid it, myself. The best library that supports mocking up statics is a MS internal one (done by a lone dev). Most folks I know are like yours: Moq. |
Hmmm actually I just thought of something. You want to use static classes because then you can use the "using static" operation. Why, actually? Isn't IOC more common than using static methods? We are evolving into an IOC era. |
Yes, I've noticed the trend. But I don't think that P/Invoke methods are the best place to 'inject'. They're so low level, it seems more helpful in the scenarios I'm thinking of that components that P/Invoke are themselves the injected and mocked components. |
We haven't had anyone else request this. So closing. |
Incidentally, a means of doing p/invoke via an interface came to my attention this morning, so I thought I'd share. Unfortunately its license is more restrictive than our own so it's not even really an option, but we could leverage our build time code generation to do the same thing that they're doing with RefEmit at runtime and thus claim the "modern" benefits |
Now that's worth considering! Nice find. Re open the issue then? |
I'm considering it. If we do, I think it would only be to modify our code gen to produce interfaces and implement them (or maybe generate classes with virtual methods?). I don't think we could pick up ADL itself though, for these reasons:
|
Since partial trust is deprecated but supported on the desktop .NET framework, would the plan be to continue supporting a mode that keeps working in partial trust scenarios on .NET 3.5 and .NET 4.x? |
Partial trust is not a concern at all here. We're P/Invoking after all, which requires full trust anyway. |
(The ADL intro blog I linked to above mentions that |
Hi there.
Often when working with PInvoke code, you have a function like this:
public void DoSomething() { MyApi.SomePInvokeCall(); SomeOtherCodeThatIsRelevant(); }
But then you can't unit test
DoSomething
because it uses a PInvoke API. In other words, it would be an integration test, and if the API in question does something nasty, you would never want it to run.What if we (from every static class of APIs) backed them up by an interface, which was then injectable through IOC?
Yes, it would require an instance of the class (maybe we could add a Singleton pattern if some people wouldn't ever want to instantiate each API). But it would make huge amounts of code easier to test, and take PInvoke a step further.
So instead of:
We would have:
And then the
ISomeExampleApi
would look like:That way, I could inject an
ISomeExampleApi
whenever I wanted to use it, and then when testing, fake it out with my favorite mocking framework, and still test other functionality.The text was updated successfully, but these errors were encountered: