📜 ⬆️ ⬇️

Using the MVC approach in WinForms for the smallest

The article describes the general principles of building applications, using the MVC approach, on the example of implementation in an application that uses the Code Behind approach for many years.

Will not be:
• Classic Unit Test;
• Reducing the influence of Code Behind;
• Unexpected discoveries in MVC.

Will be:
• Unit Test and Stub;
• MVC;
• Implementing an approach based on an existing product.

')

Introduction


Before I started using the data approach, I went a long way as a regular developer of enterprise systems. On the way, I met absolutely stereotypical events and things, both simple and more complex. For example, the development is complete, the functionality has been sent for testing, it is clicked, then a lot of errors fall, from simple flaws to gross violations. A more complicated case: the functionality is accepted, users actively participate in feedback and lead to the need to change functionality. The functionality was changed, we went the same way and the old functionality stopped working, and the new one leaves much to be desired. Something like clicking a fast-growing functionality begins the constant editing of bugs. First it was hours, then weeks, now I know that months are not the limit. When it took hours or days, errors closed, everything flowed more or less. But once he began to rediscover the mistakes, hell began. The testing department constantly swears, the developers constantly correct errors and make new ones in the developed functionality. Increasingly, feedback comes from real users, full of negativity. And the functional grew and grew, I was looking for methods and approaches, how to simplify life for myself. At the same time, I was visited by the idea a long time ago; it was to use developer-level testing when developing the user interface. The desire to lay custom scenarios led me to the realization of the existence of the problem: the absence of at least some objective criteria for product readiness from the developer. And I began to think a little more widely, but what if you try to lay all the positive usage scenarios in some rules, constantly check the rule sets, because this is at least some guarantee that the user will not be stunned by the falling off functionality at the next release, and the testers will be involved to destination. It was precisely these rules that were foretold to become the criterion for the completion of work on the functional for themselves, for the developer.

Formalization


Began the search for tools to formalize the rules. All code of user and data interaction logic was in forms. Of course, the layers of work with the database, services, remouting and much, much more were separated. But the user interface remained oversaturated with logic. Logic, not related to the display. The first, and quite logical step, it seemed to me to use tools for visual testing of forms. We collect the rule of their clicks - inputs and convert it into a test. For example, this is perfectly able to do Visual Studio 2013 Ultimate, or rather one of its templates. I cheerfully created several tests and realized that creating tests in such a way, and then running, is a huge disaster. It took a lot of time to fully load the entire system, get to the necessary dialogs and trigger the appropriate events. This required the work of all links of the chain, which in some places was impossible. I took all the actions to completely separate the Unit Testing for myself, while constantly using it for services and logic that rotated on the server, because it had no forms, accordingly Code Behind. While working in parallel with ASP.NET MVC, I increasingly liked the approach of separating logic from views. Simply, by calling the ASP.NET MVC controller, we simply pull its public method, which returns the view with some changes. I started to think about how to shift this model to WinForms and came to the conclusion that using events on views I will call the methods of the controllers. I accepted the rule that I always have a controller class that is tied to the events of the class that implements the view interface. Thus, I removed all the logic code from Code Behind into the controller code, and the controller was tested using the usual Unit Tests, implementing Stubs for the data manager classes and the views themselves. Later I learned to test the performances themselves, exclusively with the help of Unit Tests. Next, I want to give an example of how I came to this.

Example


Task Description


All of the above, I will consider the example of the implementation of the form of changing exceptions when importing data. Exceptions are simply an entity with three fields, necessary for reading text files in the system. From the user's point of view, this form should allow adding, modifying, deleting exceptions. After adding a new exception, it should be added to the end of the list. When removing an exception, the focus should go to the first item in the list. In this case, the form is called from the context does not implement MVC.

Presentation formalization


Brought up on a huge number of forms, in corporate applications, I came to the conclusion that I would also use the standard approach to the interface: the control panel with buttons - actions on exceptions, the list of exceptions, in the form of a table, and the basic button to close. Accordingly, for the display in the interface I need:
• The entire list of exceptions to build a table;
• The exception is in focus;
• The exception change event in focus;
• Add event;
• Delete event;
• The event of pressing the button to close;
• discovery method;
• Closing method;
• View update method.
Later I realized that to implement a more user-friendly interface, I would need a few more things:
• availability of creation;
• availability of removal;
• A list of all selected exceptions, for mass deletion.

Converting all this into code, I got the presentation interface:
public interface IReplaceView { bool EnableCreate { get; set; } bool EnableDelete { get; set; } List<ReplaceSymbol> Replaces { set; } ReplaceSymbol FocusedReplace { get; set; } List<ReplaceSymbol> SelectedReplaces { get; set; } event EventHandler Closing; event EventHandler Creating; event EventHandler Deleting; event EventHandler FocusedChanged; void Open(); void Close(); void RefreshView(); } 


Controller formalization


The entire formalization of the controller is that it must accept the data manager and the actual representation itself, which supports the interface presented above. Converting to code, received:
 public class ReplaceController : IReplaceController { private IReplaceView _view = null; private IReplaceStorage _replaceStorage = null; public ReplaceController(IReplaceView view, IReplaceStorage replaceStorage) { Contract.Requires(view != null, "View can't be null"); Contract.Requires(replaceStorage != null, "ReplaceStorage can't be null”); _view = view; _replaceStorage = replaceStorage; } } 

In this controller, I use contracts to check the parameters, but for the article it does not matter.

Testing


Creating and formalizing, you can forget about why all this was started. All for the sake of one, to improve the quality of work by obtaining an adequate criterion for assessing the completion of work. Therefore, immediately began to formalize the rules in the tests. To implement the interfaces received by the controller in the constructor, we simply implement stubs.

Download empty exclusion list


In this case, the button for creating exceptions must be available, the button for removing exceptions is not available, the element in focus must be null, and the list of selected elements is empty but not null. Converting to code received:
 [TestMethod] public void LoadEmptyReplaces() { var emptyReplaces = new List<ReplaceSymbol>(); var storage = new StubIReplaceStorage() { ReplaceSymbolsGet = () => { return emptyReplaces; }, }; ReplaceSymbol focusedReplace = null; var loadedReplaces = new List<ReplaceSymbol>(); var selectedReplaces = new List<ReplaceSymbol>(); var enableCreate = false; var enableDelete = false; var view = new StubIReplaceView() { FocusedReplaceGet = () => { return focusedReplace; }, FocusedReplaceSetReplaceSymbol = x => { focusedReplace = x; if (x != null) { selectedReplaces.Add(x); } }, ReplacesSetListOfReplaceSymbol = x => { loadedReplaces = x; }, EnableCreateSetBoolean = x => { enableCreate = x; }, EnableDeleteSetBoolean = x => { enableDelete = x; }, SelectedReplacesGet = () => { return selectedReplaces; }, }; var controller = new ReplaceController(view, storage); controller.Open(); view.FocusedChangedEvent(null, null); Assert.IsNotNull(loadedReplaces, "           null"); Assert.IsTrue(loadedReplaces.Count == 0, "           ,       "); Assert.IsNull(focusedReplace, "          "); Assert.IsNotNull(selectedReplaces, "        null"); Assert.IsTrue(selectedReplaces.Count == 0, "            "); Assert.IsTrue(enableCreate, "        "); Assert.IsFalse(enableDelete, "       ,     "); } 


Other rules


After that, formalizing the other rules, I received a list of tests, which immediately made it possible to assess the readiness of the controller's functionality for operation. Never once launched the client itself.


View implementation


After that, I created the view itself that implements the above interface. For him, I also created small tests using the fun PrivateObject class, which allows you to call private methods implemented in an object. For example, I received a removal test like this:
 [TestMethod] public void DeleteOneReplaceView() { var replaces = GetTestReplaces(); var storage = new StubIReplaceStorage() { ReplaceSymbolsGet = () => { return replaces; }, }; var controller = new ReplaceController(_view, storage); Task.Run(() => { controller.Open(); }).Wait(WaitTaskComplited); var privateObject = new PrivateObject(_view); privateObject.Invoke("RiseDeleting"); Assert.IsTrue(replaces.Count == 2, "       "); Assert.IsNotNull(_view.FocusedReplace, "        "); Assert.AreEqual(replaces.First().Source, _view.FocusedReplace.Source, "           "); Assert.IsTrue(_view.SelectedReplaces.Count == 1, "         "); Assert.AreEqual(_view.FocusedReplace.Source, _view.SelectedReplaces.First().Source, "           "); Assert.IsTrue(_view.EnableCreate, "        "); Assert.IsTrue(_view.EnableDelete, "          "); } 

After that, formalizing the other rules, I received a list of tests, which immediately made it possible to assess the readiness of the presentation functional to work. Again, never running the client itself. Gradually implementing each of the pieces of functionality, I achieved the picture:

After that, I made a call in the controller code with the already real manager and view.
 _replaceView = new ReplaceView(); _replaceController = new ReplaceController(_replaceView, _importer); 

I started the whole application and saw the final result of the work. Everything worked exactly as I pawned. Testing department is satisfied, customers are satisfied, positive feedback has been received. What else is needed?


I will be very happy for any comments or my stories about how you deal with Windows Forms. The first reviews correctly notice that I came to MVP with my chug, this is absolutely true.
Thanks for attention! I hope you do not waste time on me.

Source: https://habr.com/ru/post/242091/


All Articles