问题
I'm trying to find the best way to uncouple messageboxes from my logic so I can properly unittest it. Now I was wondering if it would be enough if I just made a seperate helper class (C#) which I can stub later for my messagebox. For instance:
static class messageBoxHelper
{
public static void msgBoxAlg(string message, string title, MessageBoxButtons buttons, MessageBoxIcon icons, bool show)
{
if (show)
{
MessageBox.Show(message, title, buttons, icons);
}
}
Then everytime I'd need to use a messagebox i'd just use messageboxHelper/msgBoxAlg(...) instead of messagebox.show(...). Using the bool show I could enable or disable it during testing.
I'm just wondering if this is the "right way". By which I mean, is there an easier or better way to do this properly? I can't just ditch the messageboxes, they relay "vital" info to the user ("Do you want to close this windows?" YES/NO etc.). It could also just be I'm not using proper software engineering, and I should decouple my messageboxes from my bussinesslogic more?
回答1:
Yes, it is right way. But instead of static class, you should implement IDialogService
and inject it into classes that should display dialogs:
public interface IDialogService
{
void ShowMessageBox(...);
...
}
public class SomeClass
{
private IDialogService dialogService;
public SomeClass(IDialogService dialogService)
{
this.dialogService = dialogService;
}
public void SomeLogic()
{
...
if (ok)
{
this.dialogService.ShowMessageBox("SUCCESS", ...);
}
else
{
this.dialogService.ShowMessageBox("SHIT HAPPENS...", ...);
}
}
}
During testing the SomeClass
you should inject mock object of the IDialogService
instead of real one.
If you need to test more UI logic, consider to use MVVM pattern.
回答2:
Look into Inversion of Control (IoC), the basic principal is that things that perform actions ect should be passed in as an interface then you use a IoC container to bind interfaces to specific implementations for your app. To easily achieve this in your case pass the thing that does message boxes in as an interface and in your unit test creat a mock (fake) version of that message box service which does not show a message box
look at http://martinfowler.com/articles/injection.html for details on IoC, my favorite container is Ninject (http://ninject.org)
回答3:
Ideally, you want the code your testing with Unit Tests to be logic and not UI. Therefore, the logic your testing shouldn't really be displaying a message box. If you are wanting to test the UI, then I would suggest Coded UI Tests.
Judging by your question, I would imagine your code shouldn't really be using a MessageBox
. Perhaps instead consider using a callback or arbitrary Action
, or the approaches mentioned by Luke McGregor and Sergey V.
回答4:
"Unit test", in its exact meaning, is a test of atomic behavior. This is not the only kind of code-driven tests you can make for your code. Especially for testing longer scenarios with "Yes/No" dialogs you mention, larger-scale code-driven tests are often more effective than unit tests.
However to be able to write them easier, it would be good not only to create a special service as it was mentioned by Sergii, but also to make its calls asynchronous:
public interface IDialogService
{
Task<bool> ShowYesNoMessageBox(...);
...
}
By wrapping messageboxes in non-asynchronous service calls and mocking them, for longer scenarios you'll start to contradict "Arrange-Act-Assert" pattern by predicting user action before it actually happens (doing "Arrange" instead of "Act"), which can cause numerous problems in testing, especially if your tests are done using BDD/SpecFlow. Making these calls asynchronous allows to avoid such problems. See my blog article for details and samples of larger-scale tests with messageboxes.
来源:https://stackoverflow.com/questions/8560865/messagebox-and-unit-testing