Sorry for another lengthy explanation and an alternate approach via the Model - View - Controller pattern. This is another pattern that isolates the user interface (UI) from the business logic (e.g. the calculator class in your example).
Let's start with the model, or the calculator class. As in your example, that is where the business logic lives.
interface ICalculatorModel
{
long Multiply(int mValue1, int mValue2);
}
class CalculatorModel : ICalculatorModel
{
public long Multiply(int mValue1, int mValue2)
{
return Math.BigMul(mValue1, mValue2);
}
}
This calculator model contains the business logic for multiplication of 2 integer values. (It returns a long value instead of an int to prevent an overflow error in case the product of 2 values would exceed 2,147,483,647.) The model can be extended to include methods for division, addition, subtraction, square root, etc., completely independent of the user interface. (Notice that the calculator class does not instantiate the controller or the view interfaces.)
The unit test for the multiply method in your calculator class is almost identical to the unit test you outlined, but again we can decouple unit testing the business functionality from the user interface completely.
[TestMethod()]
public void MultiplyTest()
{
CalculatorModel calcModel = new CalculatorModel();
int value1 = 5; // Initialize to an appropriate value
int value2 = 5; // Initialize to an appropriate value
long expected = 25; // Initialize to an appropriate value
long actual;
actual = calcModel.Multiply(value1, value2);
Assert.AreEqual(expected, actual);
Assert.Inconclusive("Verify the correctness of this test method.");
}
Also, with more extensive data driven unit tests, and/or API testing we could easily develop robust automated tests to test the functionality (business logic) of the method(s) in the calculator class.
Ideally, the business logic in the calculator class knows nothing about the user interface (view). Communication between the view (user interface form code) and the model (calculator class in this example) we can use a controller. Since the controller has to know about both the view and the model we instantiate both the view and the model interfaces in the controller class.
public interface ICalculatorController
{
void OnMultiplyClick(int cValue1, int cValue2);
}
class CalculatorController : ICalculatorController
{
ICalculatorModel calcModel;
ICalculatorView calcView;
public CalculatorController(ICalculatorModel calcModel, ICalculatorView calcView)
{
this.calcModel = calcModel;
this.calcView = calcView;
this.calcView.Listener(this);
}
public void OnMultiplyClick(int cValue1, int cValue2)
{
calcView.result = calcModel.Multiply(cValue1, cValue2).ToString();
}
}
Finally, we have the form code (or view) similar to your design of 3 textboxes and a button for multiplying the values in the textboxes (assuming they are integer values).
interface ICalculatorView
{
void Listener(ICalculatorController calcController);
int value1 { get; }
int value2 { get; }
string result { set; }
}
public partial class formCalculatorView : Form, ICalculatorView
{
ICalculatorController calcController;
public formCalculatorView()
{
InitializeComponent();
}
public void Listener(ICalculatorController calcController)
{
this.calcController = calcController;
}
private void buttonMultiply_Click(object sender, EventArgs e)
{
calcController.OnMultiplyClick(value1, value2);
}
public int value1
{
get { return int.Parse(textBoxValue1.Text); }
}
public int value2
{
get { return int.Parse(textBoxValue2.Text); }
}
public string result
{
set { textBoxResult.Text = value; }
}
}
Notice that the buttonMultiply_Click() method calls the method in the controller, and the controller sends the result back to the view (the result textbox on the form).
Now, that we have our view (or user interface code), there are additional tests that we would want to run from the user interface in order to test behavioral aspects of how a customer might interact with the UI. For example, we might include a small subset of valid entries to verify that the event handler is calling the right method in the controller. If we did a thorough job testing the calculator class we shouldn't necessarily need to do extensive 'functional' re-testing of the calculation methods. What we should focus on are behavioral testing such how does the program handle non-integer input values into the textboxes, or numbers larger than 2,147,483,647 in either textbox. (Notice that in this example there is no exception handling and in either of these cases the int.parse() methods would throw unhandled exceptions.) Other things to look for might include visual layout (look and feel) and accessibility issues.
Certainly we can test for business logic functionality through the user interface, and I suspect in many cases that how testing is commonly approached. But, I think this and Stuart's example of separating the business logic from the user interface provides an alternate viewpoint of how we can also think of different testing approaches, and help us understand benefits and limitations of testing at different layers.