When Software Meets Wetware, Things Can Get Squishy
Arguably (no pun intended), the biggest challenge in software development is communication; not just communication between machines and methods and such, but communication with and among the wetware (human "beans") for whom the software is being created: the stakeholders (the client, the end users, management, and, of course, the stars of the show, the coders).
We developers often misunderstand, for various reasons, what we are supposed to be building. For that reason, we first try this and then, when it is found to be not what the client and/or management had in mind, we try that (or maybe the first iteration was what they wanted, but they have since changed their mind). This can recur ad nauseum, ad infinitum, seemingly in an infinite loop, until the code base looks like a rat's nest, and it's hard to tell at a glance which code is dead, which is alive, and which is just not feeling all that chipper.
It may be better to start off with a "mockup" of the app's functionality, but one that is embedded within the primary project itself, not a separate test project within the solution; one that can be "turned on and off" with the flick of a switch, so to speak, once everyone agrees on what the app should really do, be, and look like.
One way to accomplish this is by creating interfaces that represent the major categories of functionality of the app (that much should be known at least before beginning the project). Then, you can create 2..N implementations of that interface - the "real" (production) implementation (left unimplemented at first) and the test implementation.
Which implementation is in use (real or test) can be set globally.
Show Me the Code[s]!
For example, you may have an interface like this:
public interface IFetcher
{
ArrayList FetchSlippers(string voiceTone);
ArrayList FetchThePaper(string voiceTone);
ArrayList FetchTheBall(string voiceTone);
ArrayList FetchThatPeskyVarmint(string voiceTone);
}
You can implement that interface in a production class (but leave the methods unimplemented for now):
public class ProductionFetcher : IFetcher
{
public ArrayList FetchSlippers(string voiceTone)
{
throw new NotImplementedException();
}
. . .
...and in a test class (this assumes you have a class "Slipper" with string members "Material" and "Color"):
public class TestFetcher : IFetcher
{
public ArrayList FetchSlippers(string voiceTone)
{
Slipper slip = new Slipper();
slip.Material = "suede";
slip.Color = "blue";
. . .
List<string> slips = new List<string>();
slips.Add(slip.Material);
slips.Add(slip.Color);
. . .
slip.Material = "lead";
slip.Color = "burnt sienna and hospital green polka dots";
slips.Add(slip.Material);
slips.Add(slip.Color);
ArrayList slipList = new ArrayList();
slipList.AddRange(slips);
return slipList;
}
Set it up so that you can toggle your app to use any of the implementations like so:
Create an enum that holds the implementations, perhaps in a file named "MyConsts":
public enum FetchTypes
{
Production,
Test
}
In the same MyConsts.cs file, put code like this to set the implementation being used:
private const FetchTypes implementation = FetchTypes.Test;
Add a method like this:
public static IFetcher GetFetcherImplementation()
{
IFetcher impl;
switch (implementation)
{
case FetchTypes.Production:
impl = ProductionFetcher.Instance;
break;
case FetchTypes.Test:
impl = TestFetcher.Instance;
break;
default:
impl = TestFetcher.Instance;
break;
}
return impl;
}
This assumes that your concreate classes implement the singleton pattern, and their "Instance" method returns an instance of the class. Otherwise, you can use "new" and omit the ".Instance" bit.
Now you need to add code to the forms that will call the implementations of the interface. First, this:
public partial class FrmDelivery : Form
{
private IFetcher fetcher { get; set; }
. . .
...and then, where the methods are called:
fetcher = MyConsts.GetFetcherImplementation();
That will return whichever implementation of the IFetcher interface you assigned to the "implementation" variable. Now, you can call any of the methods:
ArrayList arrlst = fetcher.FetchSlippers("suave");
Note: There's a beature, or a fug (combination bug and feature) in how this switch code compiles. Since the compiler knows which implementation you have globally selected, it grays out the case statements that are currently moot. IOW, if you have set the test instance of the implementation like so:
private const DBTypes implementation = DBTypes.Test;
...the switch code only "lights up" that particular case:
This makes it obvious which implementation is live (a feature!), but asphyxiates, or at least anesthetizes, all but one of the case candidates (a bug, of sorts).
If the "buggy" portion of this beature irks you too much, you can refactor the code to circumvent the grayification of the switch statement. Me, I don't mind making the compiler pull its weight and work another one one trillionth of a second to gray up the currently impossible cases. YOMV (Your Obsessiveness May Vary).
Another Usage: Poor Man's Branching
A related benefit of this approach is that you can effectively have multiple "branches" of your code base without having to literally create branches in some source control. IOW, it's "poor man's branching."
For example, you can use your test implementation to explore alternate approaches; if these work, you can update the production code to reflect these changes. This keeps the code cleaner as you're investigating various possibilities (rather than commenting out existing code, adding comments to explain what you're trying, etc., you simply keep the tried-and-true-blue code in production (for now, anyway) and the I've-got-an-idea-that-might-work code in the test implementation.
So, using the test implementation has more value than simply as a mockup prior to rolling up your sleeves and getting busy with the production code - you can continue to use your test implementation even after diving into production code to try out new ideas before "taking the plunge" and using that code in the production implementation.
Put the Pedal to the Metal (or is it Petal to the Medal?)!
After you provide fake data or functionality for all the Test methods, you can set the app to use that test implementation, and let the stakeholders play around with it. Once it has gone through the requisite 47,000 iterations and changes and flip flops, and everybody has agreed what type of data they want, etc. etc. you can implement the Production version of it in a much smoother fashion, knowing precisely (or as precise as can be expected when various styles and flavors of wetware collide) what should be implemented.
This should be enough to get you started having fun, making money, winning friends, influencing people, and making all your wildest dreams come true in the wild and wonderful world of test implementations.
Where, Oh Where, Can My Platypup Be?
Speaking of soft and wet, where in tarnation is my earnestly yearned-for Choctypus (Chocolate-covered Duckbilled Platypus)?!?
Note: A platypup is a baby platypus - really! No foolin'! Ask any Mammalogist, falls Sie duda mis palabras.