31
votes

I have written a Windows Forms application and now I want to write some unit tests for it (not exactly test driven development seeing as I am writing the tests after I have developed but better late then never!) My question is that with such an application how do you go about writing the unit tests, given that nearly all of the methods and events are private? I have heard of NUnit Forms but I hear good and bad things about it, also there has been no real development on that project for a while so it looks abandoned. Also is it generally accepted that the project have have adequate unit testing in place if I wrote unit test cases for all of the events that a user would trigger by clicking/ pressing buttons, or would I have to go and write unit test cases for all methods and figure out a way to test my private methods?

EDIT: My business logic is seperated from my presentation logic, there is 1 or 2 public methods my business logic exposes so the form can access them, but what about all the private methods that are in the business logic?

6

6 Answers

37
votes

The key to Unit Testing graphical applications is to make sure that all most all of the business logic is in a separate class and not in the code behind.

Design patterns like Model View Presenter and Model View Controller can help when designing such a system.

To give an example:

public partial class Form1 : Form, IMyView
{
    MyPresenter Presenter;
    public Form1()
    {
        InitializeComponent();
        Presenter = new MyPresenter(this);
    }

    public string SomeData
    {
        get
        {
            throw new NotImplementedException();
        }
        set
        {
            MyTextBox.Text = value;
        }
    }

    private void button1_Click(object sender, EventArgs e)
    {
        Presenter.ChangeData();
    }
}

public interface IMyView
{
    string SomeData { get; set; }
}

public class MyPresenter
{
    private IMyView View { get; set; }
    public MyPresenter(IMyView view)
    {
        View = view;
        View.SomeData = "test string";
    }

    public void ChangeData()
    {
        View.SomeData = "Some changed data";
    }
}

As you can see, the Form only has some infrastructure code to thy everything together. All your logic is inside your Presenter class which only knows about a View Interface.

If you want to unit test this you can use a Mocking tool like Rhino Mocks to mock the View interface and pass that to your presenter.

[TestMethod]
public void TestChangeData()
{
    IMyView view = MockRepository.DynamickMock<IMyView>();
    view.Stub(v => v.SomeData).PropertyBehavior();

    MyPresenter presenter = new MyPresenter(view);

    presenter.ChangeData();

    Assert.AreEqual("Some changed data", view.SomeData);
}
21
votes

The first thing I would do is to ensure that you have proper separation of your business logic from your form. Basically, using an MVC pattern. Then, you can easily test everything outside the form, as if the form didn't even exist.

Now, this could still leave some untested form-specific functionality. I.E., is the form wired-up to the service correctly? For this, then you could still consider something like NUnit Forms or another alternative.

6
votes

Break out all business logic into a separate project and unit test that. Or at least move all logic from the forms into separate classes.

6
votes

You have a few options.

  1. Use a tool like Coded UI to test via your user interface. This isn't a great option, because it's slower than unit testing and the tests tend to be more brittle.

  2. Separate your business logic from your presentation logic. If you have a lot of private methods performing business logic in your UI, you've tightly coupled your business logic to your presentation. Start identifying these and moving them out to separate classes with public interfaces that you can test. Read up on SOLID principles, which can help you keep your code loosely coupled and testable.

1
votes

Unit testing the View is simple enough using approvaltests ( www.approvaltests.com or nuget). there is a video here: http://www.youtube.com/watch?v=hKeKBjoSfJ8

However, it also seems like you are worried about making a function default or public for the purposes of being able to test functionality.

These are usually referred to as seams; ways to get into your code for testing. and they are good. Sometime people confuse private/public with security, and are afraid to turn a private function public, but reflection will call either, so it's not really secure. Other times people are worried about the API interface to a class. But this only matters if you have a public API, and if you have a winform app, it is probably meant to be the top level (no other consumers are calling it.)

You are the programmer, and as such can design your code to be easy to test. This usually means little more than changing a few methods public and creating a few connivence methods that allow dependences to be passed in.

For example:

buttonclick += (o,e)=> {/*somecode*/};

is very hard to test.

private void button1_Click(object sender, EventArgs e) {/*somecode*/}

still hard to test

public void button1_Click(object sender, EventArgs e) {/*somecode*/}

easier to test

private void button1_Click(object sender, EventArgs e) { DoSave();}
public void DoSave(){/*somecode*/}

Really easy to Test!

This goes double if you need some information from the event. ie.

public void ZoomInto(int x, int y)

is much easier to test that the corresponding mouse click event, and the passthrough call can still be a single ignorable line.

1
votes

One may employ the MVVM (Model–View–ViewModel) pattern with Reactive.UI, to author testable WinForms code. To get the separation of concerns really need. See: Reactive.UI https://reactiveui.net/ The main downside of using Winforms / MVVM / Reactive.UI is that there are not a lot of examples of its use (for WinForms). The upside is that it is applicable to just about all desktop frameworks and languages. You learn it for one, but the principles apply for all. When you have lots of private methods, that's OK. IMHO: try to use public methods to begin a business process you want to test. You can use tell-don't-ask: https://martinfowler.com/bliki/TellDontAsk.html and still keep all those methods private.

One may also test the code by driving the UI but this is not so highly recommended, because the resultant tests are (1) very fragile, (2) harder to get working, and IMHO, (3) can't be written at the same level of fine granuality as pure code tests; (4) Finally: if you use a database, you will need to consider populating it with test data, and, because your database must be in a clean, well-defined state before each test, (5) your tests may run even slower than your thought as you reinitialize the data for each test.

Summary: Author your code with good SoC (e.g. by applying MVVM), then your code will have far better testability.