Unit Testing

Rubberduck itself started as a C# port of a VBA project that brought unit testing capabilities to VBA. Over time, support for various VBA hosts was added, and it was ultimately through contributions by Wayne Philips (vbWatchDog, twinBASIC) that Rubberduck's test runner was made to support _any_ classic-VB host, including good old Visual Studio 6. #### [What is unit testing](#what-is-unit-testing)? Simply put, a unit test is a small procedure that calls into your code and asserts an outcome. The test passes if the assertion is verified, or fails otherwise. Each test should be testing one specific aspect of the code, be fully independent from any other test (and/or their respective outcome and/or side-effects), and reliably produce the same result regardless of what order the tests are executed in, when they are executed, whether or not your computer is connected to the company's network. Tests should never involve any user interaction (no form pops up, no `MsgBox` or `InputBox` statement interferes), should never alter any global state, and should run quickly, so that they can run often. The basic structure of a test is as follows: - **Arrange**, where you set up dependencies and expectations. - **Act**, where you invoke the method under test. - **Assert**, where you validate the output and/or side-effects against expectations. #### [Implications for your code](#implications-for-your-code) Not all code is even _testable_. In fact, most Classic-VB code isn't. Writing _testable_ code involves _decoupling_ any dependencies from the code under test, _injecting_ these dependencies as run-time parameters, ...and it's quite a bit more work than _just getting it done_: it requires solving the problem at hand not only with the desired outcome in mind, but also with a thought about _how_ a test could invoke this logic and validate its outputs and/or side-effects. ##### Outputs Prefer `Function` over `Sub` procedures: a `Function` takes inputs, processes them, and returns a value that can be validated. If a function needs to return multiple values, encapsulate them into a class, and return an instance of that class. "Pure" functions, such as Excel UDFs, are always fully testable because they always produce an output that is solely dependent on its inputs: strive to write such _pure functions_, and unit testing them will be fairly simple. Not all user-defined functions (UDF) are actually unit-testable: if a function is accessing a particular specific `Range` of cells without taking it as an input parameter, then it is _hiding a dependency_ and a unit test needs to start making assumptions about the implementation of the function in order to work... and that isn't desirable, because such tests are making the code _harder to change_ later (if the UDF depends on the value of cell A1 and then that changes to B12, the test needs to be updated - and it shouldn't), which is exactly the opposite of what unit tests are meant to achieve! By taking _all_ dependencies in as input parameters, we allow a unit test (and all other callers) to control these dependencies, and the function can focus on doing its job. ##### Side-Effects In order to test the side-effects of a `Sub` procedure, a test must control all the dependencies, and the procedure should be working against _abstractions_ rather than concrete implementations of these dependencies. For example if a macro procedure removes all duplicates from a given `Range` of cells and then displays a `MsgBox`, then it should be taking in a `Range` parameter, as well as some `IMsgBox` abstraction that the real code can implement as calls to the actual `MsgBox` function but that the test code can substitute for its own implementation which _doesn't_ pop any actual `MsgBox` but tracks its invocations so that a test can assert that `IMsgBox.ShowInfo` was called exactly once in the process. This makes use of more advanced OOP principles such as _polymorphism_ and _dependency inversion_, and the test-code `IMsgBox` implementation is known as a _stub_, or a _fake_. Rubberduck's unit testing library comes with a _Fakes API_ that limits the need to manually implement a test stub/fake for a `MsgBox`. Instead, the API hooks into the VB runtime and hijacks/suppresses calls to VB's internal `MsgBox` function, making it return exactly what the test set it up for. This is in a way quite similar to _mocking_, a more advanced technique that leverages a library that literally spawns a configurable implementation of the mocked interface at run-time.

4 items

AssertApi

loading...

FakesApi

loading...

TestExplorer

loading...

TestSettings

loading...

4 items