Fermer

mars 25, 2021

JustMock Telerik WinForms RadGridView


Ce didacticiel vous guidera tout au long du processus de création de tests unitaires avec JustMock pour un projet qui utilise le contrôle le plus couramment utilisé de la suite Telerik UI pour WinForms — RadGridView.

Très souvent, les logiciels dépendent de bibliothèques tierces , par exemple la suite Telerik UI pour WinForms . Avoir un ensemble complet de tests unitaires vous permet de mettre à niveau n'importe quelle bibliothèque tierce sans craindre qu'elle ne se brise en raison d'un défaut hors de votre contrôle. La mise à jour de la technologie tierce est particulièrement importante lorsque de nouvelles fonctionnalités, des améliorations importantes et des corrections de bogues sont publiées qui profiteront à votre projet. Plus vous le faites souvent, plus il sera facile de gérer les mises à jour.

De plus, si un test unitaire doit lire une base de données à chaque exécution et s’attend à ce qu’une valeur spécifique soit présente dans la base de données , nous dépendons directement de la base de données. Que se passe-t-il si un autre développeur attend une valeur différente dans cette base de données? Que se passe-t-il si un autre développeur exécute un test qui modifie cette valeur? Soudain, ce test devient imprévisible, passant un moment puis échouant soudainement le suivant, malgré le fait que le code n'a pas changé. Une exigence courante est non seulement de garder un test isolé du code testé pour aider à localiser le défaut, mais de l'isoler pour que les tests restent cohérents afin que vous ne soyez pas directement dépendant des données réelles.

Code qui doit interagir avec une ressource externe comme une base de données ou un service Web fonctionnera plus lentement en raison de la latence dans la communication avec ces ressources externes. Les simulations de ces ressources externes répondent beaucoup plus rapidement que les ressources externes réelles, ce qui signifie que toute la suite de tests unitaires peut s'exécuter en quelques secondes et non en quelques minutes. JustMock pour un projet qui utilise le contrôle le plus couramment utilisé de l'interface utilisateur Telerik pour la suite WinForms, alias RadGridView .

L'exemple de projet complet de ce didacticiel est disponible dans notre référentiel SDK ici .

Commençons par les détails de la configuration du projet et de ce que nous avons implémenté:

public partial class JMRadForm: Telerik.WinControls.UI.RadForm

{

public RadGridView Grid { get { return [19659009] ceci .radGridView1; }}

public JMRadForm () [19659003] {

InitializeComponent ();

ceci .radGridView1.DataSource = GetData ();

this .radGridView1.MultiSelect = true ;

SelectOrdersByProduct ( "Apple" );

}

public void [19659011] SelectOrdersByProduct ( string productName)

{

foreach (GridViewRowInfo row in this .radGridView1.Rows)

{

Order order = row.DataBoundItem as Order;

if (order.ContainsProduct ( productName))

{

row.IsSelected = vrai ;

}

[19659028]}

}

public List GetData ()

{

List < string [19659020]> productNames = nouveau Liste < string > () { "Apple" "Banana" "Avocado "" Concombre "" Tomato "" Orange " };

List < float > productPrices = new List < float > () {1.45f, 3.15f, 2.30f, 2.80f , 1.20f, 4.30f};

List orders = new List ();

Random rand = new Random ();

[19659003] int ordersCount = rand.Next (1, 10);

int [19659011] productsCount = 0;

int productIndex = -1;

pour ( int i = 0; i <ordersCount; i ++)

{

List orderProducts = nouvelle Liste ();

productsCount = rand.Next (1, 5);

for ( int j = 0; j <productsCount; j ++) [19659003] {

productIndex = rand.Next (0, productsCount + 1);

orderProducts.Add ( new Product (productNames [productIndex]productPrices [productIndex]rand.Next (1, 5))) ;

}

orders.Add ( new Order (i, DateTime.Now, orderProducts));

}

commandes de retour ;

}

public class Ordonnance

{

public [19659009] int Id { get ; set ; }

public DateTime CreatedOn { get ]; set ; }

[TypeConverter( typeof (MyConverter))]

public Liste Produits { get ; set ; }

public Order ( int id, DateHeure createdOn, List produits)

{

this [19659020] .Id = id;

this .CreatedOn = createdOn;

this .Products = products;

} [19659003]

private float GetTotalAmount ()

[19659028] {

float sum = 0;

foreach (Product p in this .Produits)

{

sum + = ( p.Prix * p.Quantity);

}

[19659065] retour somme;

}

[19659003] public bool ContainsProduct ( string name)

{

[19659065] foreach (Produit p in this .Products)

{

if (p.Name == name)

{

return true ;

[19659070]}

}

return false ;

}

}

classe publique MyConverter: TypeConverter

{

public override bool CanConvertTo (contexte ITypeDescriptorContext, Type destinationType)

{

if (destinationType == typeof ( string ))

{

return true [19659020];

}

return base . CanConvertTo (context, destinationType);

}

public override object ConvertTo (ITypeDescriptorContext context,

System.Globalization .CultureInfo culture, objet valeur, Type destinationType)

{

List products = value as List ;

if (destinationType == typeof ( string ) && products! = null )

{

StringBuilder sb = nouveau StringBuilder (); [19659003] foreach (Produit p dans produits)

{

sb.Append (p.Quantity + "x" + p.Name + "; ");

}

return sb.ToString ();

}

[19659065] return base .ConvertTo (contexte, culture, valeur, destinationType);

}

}

public class Produit

{

public [19659009] string Nom { get ; set ]; }

public float Price { get ; set ; }

public int Quantity { get ; set ; }

public Product ( string name, float prix, int quantité)

{

this .Name = name;

this .Prix = price;

this .Quantity = quantité;

}

}

}

Si vous exécutez le projet, vous êtes devrait obtenir le résultat suivant:

 Liste des ID de commande avec date de création a nd liste des produits pour chaque commande.

Le code ci-dessus comprend l'implémentation des classes Order et Product et deux méthodes publiques du formulaire, GetData et SelectOrdersByProduct . Le but principal de la méthode GetData est de produire toutes les données de commandes aléatoires qui seront utilisées pour RadGridView. DataSource . La mise en œuvre réelle de la méthode elle-même et la manière dont les données sont obtenues ne sont pas importantes. Il peut se connecter à une base de données, sélectionner une requête, remplir un DataTable ou lire un fichier pour remplir une liste. D'autre part, la méthode SelectOrdersByProduct accepte un paramètre de chaîne représentant un nom de produit et sélectionne toutes les lignes de la grille où la commande contient le produit spécifique.

Pour les besoins des tests unitaires, vous ne Je ne veux pas vous soucier des données et perdre du temps à préparer des données factices appropriées. Vous avez juste besoin d'une grille remplie de données et de vous concentrer sur la logique que vous devez tester, par ex. Vérifiez si les lignes correctes seront sélectionnées après avoir appelé la méthode SelectOrdersByProduct . Voici l'avantage de JustMock .

Intégration JustMock dans votre projet WinForms

Il est possible soit de créer un tout nouveau projet de test JustMock, soit d'ajouter une référence de Telerik.JustMock.Dll à un projet de test unitaire existant: Ajoutez Telerik JustMock à votre projet de test

Ajoutez un projet de test JustMock:

 Dans la fenêtre Explorateur de solutions, ajoutez un nouveau projet [19659003]  Sur l'écran «ajouter un nouveau projet», nous ajoutons un nouveau projet de test C # JustMock (.NET Framework).

Ajoutez une référence de Telerik.JustMock.Dll au projet de test unitaire existant:

 Dans l'Explorateur de solutions, sous UnitTestProjectChallenge> Références> Telerik.JustMock. "title =" add-justmock-to-unit-test "/></p data-recalc-dims=

Ensuite, assurez-vous que le profileur est activé:

 Extensions> JustMock> Enable Profiler. " title = "enable-profiler" /></p data-recalc-dims=

Arrange / Act / Assert (AAA) est un modèle pour organiser et formater le code dans les méthodes de test unitaire. Nous le suivrons également dans ce tutoriel.

Mocking DataSource Collection

La ​​méthode GetData renvoie des données aléatoires représentant la collection Orders où chaque commande contient un ou plusieurs produits. Nous voulons tester si la méthode SelectOrdersByProduct sélectionnera le nombre correct de lignes dans RadGridView en passant un nom de produit, par exemple sélectionnez toutes les commandes contenant un produit «Apple». Comme nous ne savons pas quelles données seront renvoyées par la méthode GetData, nous ne savons pas combien de commandes contiendront des pommes. Nous dépendons donc de la collection. Cette dépendance peut être éliminée en forçant la méthode GetData à renvoyer une collection prédéfinie où vous saurez combien de pommes exactement nous avons.

  

[TestMethod]

public void TestMethodSelectRows ()

{

// Organiser

List myCollection = new List ();

myCollection.Add ( new Order (1, DateTime.Now, new List ()

{

nouveau Produit ( "Apple" 1.45f, 3),

nouveau Produit ( "Banana" 3.15f, 2), [19659003] nouveau Produit ( "Avocado" 2.30f, 1),

nouveau Produit ( "Concombre" 2.80f, 2)

}));

myCollection.Add ( new Order (2, DateHime.Now, [19659016] nouveau Liste ()

{

nouveau Produit ( "Tomato" 1.20f, 4),

new [19659011] Produit ( "Concombre" 2.80f, 2),

nouveau Produit ( "Oran ge "4.30f, 3)

}));

myCollection.Add ( new Order (3, DateTime.Now, new List ()

{

nouveau Produit ( "Apple" 1.45f, 4),

nouveau Produit ( "Avocado" 2.30f, 2)

}));

[19659003] myCollection.Add ( new Order (4, DateTime.Now, new List ()

{

nouveau Produit ( "Banana" 3.15f, 4),

nouveau Produit ( "Mango" 6.80f, 2)

}));

using (JMRadForm form = nouveau JMRadForm ())

{

Mock.Arrange (() => form.GetData ()). ReturnsCollection (myCollection);

// Loi

List orders = form.GetData (); [1 9459016]

formulaire.Grid.DataSource = commandes;

formulaire .Grid.ClearSelection ();

form.Grid.MultiSelect = true ;

chaîne productName = "Apple" ;

form.SelectOrdersByProduct (productName);

// Assert [19659003] Assert.IsTrue (form.Grid.SelectedRows.Count == 2);

}

}

Payez à Appel de méthode Arrange qui forcera les appels de méthode GetData plus tard dans le test unitaire (section Act) à retourner une collection prédéfinie où deux commandes contiennent des pommes. Il est réalisé à l'aide de la méthode ReturnsCollection . Ensuite, nous avons un contrôle total sur les données et nous pouvons nous attendre à ce que deux lignes soient sélectionnées dans la grille après avoir appelé la méthode SelectOrdersByProduct .

Future Mocking of Methods that Depend on Other Methods

In the exemple ci-dessus, la méthode SelectOrdersByProduct appelle en interne la méthode ContainsProduct de la classe Order qui renvoie un résultat booléen indiquant si un nom de produit est contenu dans une commande ou non . En d'autres termes, nous dépendons du résultat renvoyé par une autre méthode. Afin de contrôler notre test unitaire pour suivre un chemin strict dans son exécution, nous pouvons simuler le résultat pour qu'il soit toujours faux afin qu'aucune ligne ne soit sélectionnée dans la grille. Ainsi, nous pouvons à nouveau tester la qualité de notre méthode SelectOrdersByProduct mais éliminer la dépendance au résultat de la méthode ContainsProduct .

  

[TestMethod]

public void TestMethodNoSelection ()

{

// Organiser

en utilisant (JMRadForm form = new JMRadForm ())

{

var order = Mock.Create ();

Mock.Arrange (() => order.ContainsProduct (Arg.AnyString)). IgnoreInstance (). Renvoie ( false );

// Loi [19659003] Liste commandes = form.GetData ();

form.Grid.DataSource = commandes ;

form.Grid.MultiSelect = true ;

chaîne productName = "Apple" ;

form.SelectOrdersByProduct (productName) ;

// Assert

Assert.IsTrue (form.Grid.SelectedRows.Count == 0);

}

}

Dans le Arrange de notre test unitaire, nous bénéficions de la puissance de Future Mocking garantissant que les prochains appels de méthode ContainsProduct renverront toujours false, quelles que soient les données contenues dans RadGridView .

Lancement d'événements simulés

Très souvent, un test unitaire peut s'attendre à ce qu'un certain événement soit déclenché afin de valider tout morceau de code en cours d'exécution dans le gestionnaire d'événements. Nous n'avons pas à nous soucier de la configuration de cette partie du test et de préparer les étapes précises qui conduisent au déclenchement de cet événement. JustMock vous permet de déclencher des événements avec des arguments d'événement prédéfinis afin que vous puissiez simuler le déclenchement d'événement sans vous soucier de savoir comment conduire mon code dans le test unitaire à cela.

Prenons l'exemple dans lequel nous devons tester qu'une certaine variable a une valeur assignée uniquement lorsque la ligne du milieu dans RadGridView devient courante. Si la ligne du milieu n'est pas à jour, cette variable reste nulle.

  

[TestMethod]

public void TestMethodRaiseEvent ()

{

// Organiser

en utilisant (JMRadForm form = new JMRadForm ())

{

        List orders = form.GetData();

         form.Grid.DataSource = orders;

        form.Grid.ClearSelection();

        form.Grid.MultiSelect = false;

        form.Grid.CurrentRow = null;

        form.Grid.LoadElementTree();

 

        string actual = null;

        string expected = form.Grid.Rows[orders.Count / 2].Cells["Id"].Value + "";

 

 

        var executor = Mock.Create();

        executor.CurrentRowChanged += delegate (object sender, CurrentRowChangedEventArgs args)

        {

            if (args.CurrentRow != null && args.CurrentRow.DataBoundItem == orders[orders.Count / 2])

            {

                actual = args.CurrentRow.Cells["Id"].Value + "";

            }

        };

 

        // Act

 

        CurrentRowChangedEventArgs eventArguments = new CurrentRowChangedEventArgs(nullform.Grid.Rows[orders.Count / 2]);[1 9459016]

        Mock.Raise(() => executor.CurrentRowChanged += nulleventArguments);

 

        // Assert

        Assert.AreEqual(expected, actual);

 

    }

}

Mock Mouse Position

Let's finish this tutorial with a more advanced mocking example. Imagine that you want to simulate that a user clicks a specific row in RadGridView and selects it. This can be easily achieved in the unit test by simply calling the public BaseGridBehavior.OnMouseDown(MouseEventArgs e) method. However, this action directly depends on the mouse position and the passed X,Y coordinates. Depending on the current monitor's resolution, columns' sizes, rows' heights, you don't know what row exactly will be selected. You are not even sure that any row will be selected at all because it is unpredictable what element will be under the mouse coordinates, e.g. it can be the header row, filtering row, the new row, etc.

Since RadGridView manages user mouse and keyboard input over its rows by a row behaviorit introduces different behaviors depending on the row type. Hence, RadGridView internally detects what type of visual row element is located under the mouse and the mouse handling of the respective row behavior is being performant. This makes the unit test setup more complex because you have to ensure that a specific data row is clicked and selected and then your unit of code is executed. Thus, you won't be mainly focused on the unit of code to test but also on the setup that a specific data row is clicked, not the header or filtering row.

The following schema shows the different paths that may occur in the unit test execution depending on the coordinates that are passed in the MouseEventArgs. However, we want to strictly control that the marked path will be followed no matter what X,Y coordinates are passed:

A red rectangle encircles part of a diagram: the ‘BaseGridBehavior: OnMouseDown (MouseEventArgs e) – dependency on mouse coordinates’ with a line down to GridDataRowBehavior.

Here we will combine private methods mocking, future mocking, mocking of returned results:

[TestMethod]

public void TestMethodSelectionChanged()

{

    //Arrange

    using (JMRadForm form = new JMRadForm())

    {

        List orders = form.GetData();

        form.Grid.DataSource = orders;

        form.Grid.ClearSelection();

        form.Grid.CurrentRow = null;

        form.Grid.MultiSelect = false;

        form.Grid.LoadElementTree();

 

        //get the middle visual row element

        GridRowElement visualRowElement = form.Grid.TableElement.GetRowElement(form.Grid.Rows[orders.Count / 2]);

        var rowBehaviorMock = Mock.Create(Behavior.CallOriginal);

 

        Mock.NonPublic.Arrange((BaseGridBehavior)form.Grid.GridBehavior, "GetRowBehaviorAtPoint",

            Arg.Expr.IsAny()).Returns(((BaseGridBehavior)form.Grid.GridBehavior).GetBehavior(visualRowElement.RowInfo.GetType()));

        Mock.NonPublic.Arrange(rowBehaviorMock, "GetCellAtPoint"Arg.Expr.IsAny()).IgnoreInstance().Returns(visualRowElement.VisualCells[1]);

        Mock.NonPublic.Arrange(rowBehaviorMock, "GetRowAtPoint"Arg.Expr.IsAny()).IgnoreInstance().Returns(visualRowElement);

 

        string middleRowText = visualRowElement.VisualCells[1].Text;

        string actual = string.Empty;

        form.Grid.SelectionChanged += (o, e) =>

        {

            actual = form.Grid.SelectedRows[0].Cells["Id"].Value.ToString();

        };

 

        //Act

        MouseEventArgs emptyMouseEventArgs = new MouseEventArgs(MouseButtons.Left, 1, 0, 0, 0);

        ((BaseGridBehavior)form.Grid.GridBehavior).OnMouseDown(emptyMouseEventArgs);

 

        //Assert           

        Assert.AreEqual(middleRowText, actual);

    }

}

The visualRowElement variable represents the visual element for the middle row in RadGridView. We don't want to bother at what coordinates it is located exactly according to the current resolution, row's height, records count, etc. We just want to ensure that when the grid is clicked, the middle row gets selected and the actual variable is assigned with a value. If you click the header row or the new row, the actual variable will remain empty. Hence, we should eliminate this path of execution in the test.

The BaseGridBehavior.OnMouseDown method may fork its execution path according to the coordinates as it was illustrated in the above schema. That is why we will future mock the GridRowBehavior.GetBehavior method to always return the data GridRowBehavior. Thus, we make the first direction in the test path. We will control the path as if a data row is clicked no matter the passed X,Y. Then, the mouse input will be handled by the GridRowBehavior and its internal methods GetCellAtPoint and GetRowAtPoint will detect what visual elements are placed under the mouse location. This is also a dependency on the coordinates. That is why we will mock them to always return the middle row element.

As a result, when in the Act section the BaseGridBehavior.OnMouseDown method is called, you will ensure that the middle row get selected and the actual value has a value. Thus, with a few lines of code, you have controlled the path of your unit test without bothering about coordinates, resolution and other settings that may vary on different machines.

With all this demonstrated in this tutorial we only hint at the possibilities that JustMock offers. It definitely can add value to any testing project.

Try It Out 

If you are intrigued (hopefully you are 🤞), I'll be more than happy to hear your honest feedback in the comments. 

Whether you: 

  • Are new to Telerik JustMock—learn more about it via the product page. It comes with a 30-day free trialgiving you some time to explore the capabilities of JustMock.
  • Are already familiar with Telerik JustMock—the R1 2021 release is already available for download in your account.
  • Want to take advantage of Mihail Vladov's advice on writing user tests—download the Unit Testing eBook now. 

Regardless of the above "cases," don't be shy to:

 Try The Latest JustMock Now

You won't regret it.




Source link