weixin_39777242
weixin_39777242
2020-12-31 13:44

Rubberduck needs to FAKE ALL THE THINGS!

Rather than having separate issues for each VBA function that needs to have an IFake implementation, this meta feature-request should be used to track and prioritize functionality that needs to be faked. The PR with the framework shouldn't be much longer, so it's time to start tracking these. In general, we should probably prioritize functions that disrupt unit tests (like MsgBox and InputBox) so let's get those covered before adding more to the list. Note that the vbe7.dll doesn't have all of the type hinted variations, so just list the Variant version.

If you implement one of these, check it off. If you want to add a new one to the list (and have the permissions to do so), edit this comment.

TODO:

Interaction - [x] MsgBox - [x] InputBox - [x] Timer - [x] Beep - [x] DoEvents

File system - [x] Kill - [ ] Move - [ ] Name - [ ] FileCopy - [x] MkDir - [x] RmDir - [ ] Dir - [x] ChDir - [x] ChDrive - [x] CurDir - [ ] EOF - [ ] FreeFile - [ ] FileAttr - [ ] FileCopy - [ ] FileDateTime - [ ] FileLen - [ ] Get - [ ] GetAttr - [ ] SetAttr - [ ] Loc - [ ] LOF - [ ] Open - [ ] Reset - [ ] Seek - [ ] Unlock - [ ] Width

Registry \ System - [ ] GetAllSettings - [ ] DeleteSetting - [ ] GetSetting - [ ] SaveSetting - [x] Environ - [ ] IMEStatus - [ ] Now - [ ] Date - [ ] Time - [x] SendKeys - [x] Shell

Misc - [ ] Rnd - [ ] Randomize - [ ] VarPtr - [ ] ObjPtr - [ ] StrPtr

该提问来源于开源项目:rubberduck-vba/Rubberduck

  • 点赞
  • 写回答
  • 关注问题
  • 收藏
  • 复制链接分享
  • 邀请回答

15条回答

  • weixin_39629093 weixin_39629093 4月前

    Should CurDir be implemented with ChDir changing its return value? Or that's for the user's setup code to do? How does .Returns(CurDir) behave?

    点赞 评论 复制链接分享
  • weixin_39777242 weixin_39777242 4月前
    • I was thinking of building a complete mock filesystem to attach when any of the file handling functions were faked. Maybe in its own namespace? Something like:

    Fakes.FileSystem.CurDir.Returns("C;\Foo")

    Maybe with it's own configuration. Could probably do both too - allow the above and the option to do:

    Fakes.CurDir.Returns("C:\Foo")

    点赞 评论 复制链接分享
  • weixin_39629093 weixin_39629093 4月前

    that. we should mimick the standard library's structure, so MsgBox would be under Fakes.Interaction.MsgBox for example.

    How would mocking, say, a Worksheet or a Range object work?

    点赞 评论 复制链接分享
  • weixin_39777242 weixin_39777242 4月前
    • It depends. For COM objects, EasyHook needs an instance to examine the interface for. IIR, the object itself is always going to be a parameter on the interface call so we could either tie them to specific user created objects or try to emit a managed class that implements the passed interface. It basically needs some more research.
    点赞 评论 复制链接分享
  • weixin_39827034 weixin_39827034 4月前

    Could we just do a CreateObject of the application, dump stuff in the temp folder and then delete once done? That way, Fake is dealing with a dedicated instance of Excel/Word/other Office application, and thus would model its behavior much more accurately than an actual Fake implementation.

    This isn't a general solution, though because that makes assumption that there's an Application object that we can instantiate and configure to use the Temp folder for the rest of testing. Not all COM objects have that design model but for a complex object model like Access or Excel, I'm more liable to want just to use a new instance of that application and let it do its own thing than finding runtime bugs that wasn't caught by the fake implementation.

    点赞 评论 复制链接分享
  • weixin_39777242 weixin_39777242 4月前
    • Maybe, but most likely not. I'm guessing the rtcCreateObject is just a thin wrapper around the VBA CreateObject function. If RD was hooking that and just newing up an object to mock the interfaces on, we might as well just use the existing objects. The other downside would be that anything we do inside a unit test that potentially creates new document objects probably isn't handled very well currently (IIR we're currently not disabling the parser if code is under test). Hooking the specific interface would allow mocking individual members (for example Range.Value), but leaving the native functionality in place for everything else. I think ultimately it should come down to a design decision as to whether to replace a specific interface member of a native object or set up an entirely managed dummy interface.
    点赞 评论 复制链接分享
  • weixin_39827034 weixin_39827034 4月前

    , well, I wasn't thinking of just a straight CreateObject anyway. As I alluded to, additional configuration would be necessary so that it works in the temporary filesystem and maybe even use RD's eventual fake filesystem. As part of the CreateObject build-up, you can configure RD to ignore that dedicated instance of Office application for testing, among other things.

    The point is that I think it's easier and less buggy to extend the existing native objects than to supersede it with a entirely new fake implementation, at least for those within the Office applications.

    Two specific cases to think about:

    ActiveWorksheet or Screen (bleh) are good examples. After discounting the abuse of thereof, thanks to Macro Recorder, suppose we have an add-in that should operate on the current worksheet. Still, I know there has been many buggy VBA procedures that makes wrong assumption about what is the active worksheet. but there was other code that changed the active worksheet, and thus the code fails to operate on the expected worksheet. That would be very difficult to consistently replicate in a fake implementation. There are several other Activeobjects in various Office application. Selection, too. I sure don't want to try and fake those.

    SaveAs or Close are other good examples - those have several side effects that should be replicated -- firing events, changing the state of the application, etc., etc.. We certainly do want to override those methods and ensure the paths for saving the document are routed to our temporary or RD fake's filesystem, but we should then let those run using the native object's model so that we can observe all the side-effects and thus determine accurately if our test should pass or fail.

    Finally, because those stuff are quite tightly coupled, I don't think it's all that practical to set up dummy interface on a case-by-case basis. You'd end up needing to build interfaces for all exposed objects within that application's object model, I'd think.

    点赞 评论 复制链接分享
  • weixin_39777242 weixin_39777242 4月前
    • That's basically what I meant by "I think ultimately it should come down to a design decision as to whether to replace a specific interface member of a native object or set up an entirely managed dummy interface". RD shouldn't be in the position of determining the best methodology for test implementation - that is the domain of the test writer. What RD can do is offer a tool-set for the test writer to choose from. If the system under test only calls one method on an interface, you might not even need a native object at all. At the end of the day, if test setup is difficult, refactor in a way that your code is testable. If the test setup is impossible (like this feature :grin:), that's just an eventuality that you deal with as well as you can.

    Ultimately, this will probably come down to "functionality that is easier to code will be offered first, followed by stuff that is difficult to code". :wink:

    点赞 评论 复制链接分享
  • weixin_39538877 weixin_39538877 4月前

    I think the Global enumerable collections would be good candidates for Fakes.

    vb
    Set v = Fakes.Excel.Workbooks("Foo.xlsm")
    Set x = Fakes.Excel.Worksheets("Settings")
    Set y = Fakes.Excel.Sheets("Report")
    Set z = Fakes.Excel.Addins("Foo")
    
    点赞 评论 复制链接分享
  • weixin_39777242 weixin_39777242 4月前
    • What about a separate Proxies namespace? The use would be something like:

    Proxies.Excel.Workbooks("Foo.xlsx").Returns("C:\Test\Bar.xlsx")

    That would allow using "non-production" files to run integration tests on.

    点赞 评论 复制链接分享
  • weixin_39727005 weixin_39727005 4月前

    I'm confused by the usage of the term "Proxies" in this context which theoretically sounds good to enumerate what is at.

    In business, another party can act for another person in a nutshell, are we generalising here that it may imply represented from another host (within Office, of course) / application (whatever it is) / network (another PC/Server/Mobile?) on our behalf for inner VBA usage?

    Thus, "C:\Test\Bar.xlsx" exists across an (assumed) approved shared network with UNC notation like \?\UNC\Server01\user\docs\Letter.txt (Source: Wikipedia) https://en.wikipedia.org/wiki/Path_(computing) under Windows.

    点赞 评论 复制链接分享
  • weixin_39777242 weixin_39777242 4月前
    • Might not be the best identifier to use, but "another party can act for another person" is a great description. What a Proxy would do is allow substitution of one object for another one. So the line of sample code above...

    Proxies.Excel.Workbooks("Foo.xlsx").Returns("C:\Test\Bar.xlsx")

    ...would basically mean "If you get a request to open and return "Foo.xlsx", open and return "C:\Test\Bar.xlsx" instead. It would still be using the Excel object model to open the file, manage it, etc., but would allow integration testing without messing around with production files.

    点赞 评论 复制链接分享
  • weixin_39758032 weixin_39758032 4月前

    Would the scope of Rubberduck's Fakes be limited to only VBA's own functions, or would it be able to fake any COM class?

    点赞 评论 复制链接分享
  • weixin_39777242 weixin_39777242 4月前
    • It doesn't even need to be a COM class - it can literally be any dll. Would a framework for faking arbitrary Declare Functions be useful? That would be a ton of shoot yourself in the foot (or higher) potential. Or is giving the user a rope and saying "hang" yourself useful?
    点赞 评论 复制链接分享
  • weixin_40007016 weixin_40007016 4月前

    Is the task list in this up-to-date?

    点赞 评论 复制链接分享

相关推荐