JustMock Telerik 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:
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:
[19659003]
Ajoutez une référence de Telerik.JustMock.Dll au projet de test unitaire existant:
Ensuite, assurez-vous que le profileur est activé:
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(
null
form.Grid.Rows[orders.Count / 2]);[1 9459016]
Mock.Raise(() => executor.CurrentRowChanged +=
null
eventArguments);
// 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:
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:
You won't regret it.
Source link