WPF C#/VB
-
Unit & End-to-End Testing
Sections:
// AZUL CODING ---------------------------------------
// WPF C#/VB - Unit & End-to-End Testing
// https://youtu.be/pZQBH8gg8iE
using AzulCalculator;
namespace TestSuite
{
public class UnitTests
{
[Theory]
[InlineData(2, 3, 5)]
[InlineData(-1, 1, 0)]
[InlineData(0, 0, 0)]
[InlineData(10.5, 2.3, 12.8)]
[InlineData(-5.5, -3.2, -8.7)]
public void Add_ShouldReturnCorrectSum(double a, double b, double expected)
{
double result = Calculator.Add(a, b);
Assert.Equal(expected, result, 1);
}
[Fact]
public void Add_WithLargeNumbers_ShouldNotOverflow()
{
double a = double.MaxValue / 2;
double b = double.MaxValue / 4;
double result = Calculator.Add(a, b);
Assert.True(result > 0);
Assert.False(double.IsInfinity(result));
}
[Theory]
[InlineData(10, 0)]
[InlineData(-5, 0)]
[InlineData(0, 0)]
[InlineData(double.MaxValue, 0)]
public void Divide_ByZero_ShouldThrowArgumentException(double a, double b)
{
ArgumentException exception = Assert.Throws<ArgumentException>(() =>
Calculator.Divide(a, b));
Assert.Equal("Cannot divide by zero", exception.Message);
}
}
}
Help support the channel
// AZUL CODING ---------------------------------------
// WPF C#/VB - Unit & End-to-End Testing
// https://youtu.be/pZQBH8gg8iE
using System.Diagnostics;
using System.Windows.Automation;
namespace TestSuite
{
public class UITests : IDisposable
{
private Process? _appProcess;
private AutomationElement? _mainWindow;
private readonly string _appPath;
// change these values as appropriate
private const string _appName = "AzulCalculator";
private const string _appWindowTitle = "Azul Coding - Simple Calculator";
public UITests()
{
_appPath = Path.Combine(
Directory.GetCurrentDirectory(),
"..", "..", "..", "..", _appName, "bin", "Debug", $"net{Environment.Version.Major}.0-windows", $"{_appName}.exe"
);
}
#region Helper Functions
private async Task<AutomationElement> StartApplicationAndGetMainWindow()
{
if (!File.Exists(_appPath))
throw new FileNotFoundException($"Application not found at: {_appPath}");
_appProcess = Process.Start(_appPath);
await Task.Delay(2000);
int retryCount = 0;
int maxRetries = 10;
while (retryCount < maxRetries)
{
AutomationElement desktop = AutomationElement.RootElement;
PropertyCondition condition = new(AutomationElement.NameProperty, _appWindowTitle);
_mainWindow = desktop.FindFirst(TreeScope.Children, condition);
if (_mainWindow != null)
return _mainWindow;
await Task.Delay(500);
retryCount++;
}
throw new TimeoutException("Could not find the main window within the timeout period");
}
private AutomationElement FindElementByAutomationId(string automationId)
{
if (_mainWindow == null)
throw new InvalidOperationException("Window is not initialised");
PropertyCondition condition = new(AutomationElement.AutomationIdProperty, automationId);
AutomationElement element = _mainWindow.FindFirst(TreeScope.Descendants, condition);
return element ?? throw new InvalidOperationException($"Could not find element with AutomationId: {automationId}");
}
private static void ClickButton(AutomationElement button)
{
if (button.TryGetCurrentPattern(InvokePattern.Pattern, out var pattern))
{
((InvokePattern)pattern).Invoke();
}
else
{
throw new InvalidOperationException("Button does not support InvokePattern");
}
}
private static void SetTextBoxValue(AutomationElement textBox, string value)
{
if (textBox.TryGetCurrentPattern(ValuePattern.Pattern, out var pattern))
{
((ValuePattern)pattern).SetValue(value);
}
else
{
throw new InvalidOperationException("TextBox does not support ValuePattern");
}
}
private static string GetTextValue(AutomationElement element)
{
return element.Current.Name ?? string.Empty;
}
public void Dispose()
{
try
{
_appProcess?.CloseMainWindow();
_appProcess?.WaitForExit(3000);
if (!_appProcess?.HasExited == true)
_appProcess?.Kill();
}
catch (Exception ex)
{
Debug.WriteLine($"Error disposing application: {ex.Message}");
}
finally
{
_appProcess?.Dispose();
GC.SuppressFinalize(this);
}
}
#endregion
#region Tests
[Fact]
public async Task Addition_ShouldDisplayCorrectResult()
{
await StartApplicationAndGetMainWindow();
AutomationElement firstNumberTextBox = FindElementByAutomationId("FirstNumber");
AutomationElement secondNumberTextBox = FindElementByAutomationId("SecondNumber");
AutomationElement addButton = FindElementByAutomationId("AddButton");
AutomationElement resultTextBlock = FindElementByAutomationId("Result");
SetTextBoxValue(firstNumberTextBox, "10");
SetTextBoxValue(secondNumberTextBox, "5");
ClickButton(addButton);
await Task.Delay(500);
string result = GetTextValue(resultTextBlock);
Assert.Equal("15.00", result);
}
[Fact]
public async Task DivisionByZero_ShouldDisplayErrorMessage()
{
await StartApplicationAndGetMainWindow();
AutomationElement firstNumberTextBox = FindElementByAutomationId("FirstNumber");
AutomationElement secondNumberTextBox = FindElementByAutomationId("SecondNumber");
AutomationElement divideButton = FindElementByAutomationId("DivideButton");
AutomationElement errorTextBlock = FindElementByAutomationId("ErrorMessage");
SetTextBoxValue(firstNumberTextBox, "10");
SetTextBoxValue(secondNumberTextBox, "0");
ClickButton(divideButton);
await Task.Delay(500);
string errorMessage = GetTextValue(errorTextBlock);
Assert.Equal("Cannot divide by zero", errorMessage);
}
#endregion
}
}
// AZUL CODING ---------------------------------------
// WPF C#/VB - Unit & End-to-End Testing
// https://youtu.be/pZQBH8gg8iE
namespace AzulCalculator
{
public class Calculator
{
public static double Add(double a, double b)
{
return a + b;
}
public static double Subtract(double a, double b)
{
return a - b;
}
public static double Multiply(double a, double b)
{
return a * b;
}
public static double Divide(double a, double b)
{
if (b == 0)
throw new ArgumentException("Cannot divide by zero");
return a / b;
}
}
}
// AZUL CODING ---------------------------------------
// WPF C#/VB - Unit & End-to-End Testing
// https://youtu.be/pZQBH8gg8iE
using System.Windows;
namespace AzulCalculator
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void AddButton_Click(object sender, RoutedEventArgs e)
{
PerformOperation(Calculator.Add);
}
private void SubtractButton_Click(object sender, RoutedEventArgs e)
{
PerformOperation(Calculator.Subtract);
}
private void MultiplyButton_Click(object sender, RoutedEventArgs e)
{
PerformOperation(Calculator.Multiply);
}
private void DivideButton_Click(object sender, RoutedEventArgs e)
{
PerformOperation(Calculator.Divide);
}
private void ClearButton_Click(object sender, RoutedEventArgs e)
{
FirstNumberTextBox.Text = "";
SecondNumberTextBox.Text = "";
ResultTextBlock.Text = "";
ErrorTextBlock.Text = "";
}
private void PerformOperation(Func<double, double, double> operation)
{
try
{
ErrorTextBlock.Text = "";
if (!double.TryParse(FirstNumberTextBox.Text, out double first))
{
ErrorTextBlock.Text = "Please enter a valid first number.";
return;
}
if (!double.TryParse(SecondNumberTextBox.Text, out double second))
{
ErrorTextBlock.Text = "Please enter a valid second number.";
return;
}
double result = operation(first, second);
ResultTextBlock.Text = result.ToString("F2");
}
catch (ArgumentException ex)
{
ErrorTextBlock.Text = ex.Message;
ResultTextBlock.Text = "";
}
catch (Exception ex)
{
ErrorTextBlock.Text = $"An error occurred: {ex.Message}";
ResultTextBlock.Text = "";
}
}
}
}
' AZUL CODING ---------------------------------------
' WPF C#/VB - Unit & End-to-End Testing
' https://youtu.be/pZQBH8gg8iE
Imports AzulCalculator
Imports Xunit
Public Class UnitTests
<Theory>
<InlineData(2, 3, 5)>
<InlineData(-1, 1, 0)>
<InlineData(0, 0, 0)>
<InlineData(10.5, 2.3, 12.8)>
<InlineData(-5.5, -3.2, -8.7)>
Public Sub Add_ShouldReturnCorrectSum(a As Double, b As Double, expected As Double)
Dim result As Double = Calculator.Add(a, b)
Assert.Equal(expected, result, 1)
End Sub
<Fact>
Public Sub Add_WithLargeNumbers_ShouldNotOverflow()
Dim a As Double = Double.MaxValue / 2
Dim b As Double = Double.MaxValue / 4
Dim result As Double = Calculator.Add(a, b)
Assert.True(result > 0)
Assert.False(Double.IsInfinity(result))
End Sub
<Theory>
<InlineData(10, 0)>
<InlineData(-5, 0)>
<InlineData(0, 0)>
<InlineData(Double.MaxValue, 0)>
Public Sub Divide_ByZero_ShouldThrowArgumentException(a As Double, b As Double)
Dim exception As ArgumentException = Assert.Throws(Of ArgumentException)(
Function() Calculator.Divide(a, b))
Assert.Equal("Cannot divide by zero", exception.Message)
End Sub
End Class
' AZUL CODING ---------------------------------------
' WPF C#/VB - Unit & End-to-End Testing
' https://youtu.be/pZQBH8gg8iE
Imports System.IO
Imports System.Windows.Automation
Imports Xunit
Public Class UITests
Implements IDisposable
Private _appProcess As Process
Private _mainWindow As AutomationElement
Private ReadOnly _appPath As String
' change these values as appropriate
Private Const _appName As String = "AzulCalculator"
Private Const _appWindowTitle As String = "Azul Coding - Simple Calculator"
Public Sub New()
_appPath = Path.Combine(
Directory.GetCurrentDirectory(),
"..", "..", "..", "..", _appName, "bin", "Debug", $"net{Environment.Version.Major}.0-windows", $"{_appName}.exe"
)
End Sub
#Region "Helper Functions"
Private Async Function StartApplicationAndGetMainWindow() As Task(Of AutomationElement)
If Not File.Exists(_appPath) Then
Throw New FileNotFoundException($"Application not found at: {_appPath}")
End If
_appProcess = Process.Start(_appPath)
Await Task.Delay(2000)
Dim retryCount As Integer = 0
Dim maxRetries As Integer = 10
While retryCount < maxRetries
Dim desktop As AutomationElement = AutomationElement.RootElement
Dim condition As New PropertyCondition(AutomationElement.NameProperty, _appWindowTitle)
_mainWindow = desktop.FindFirst(TreeScope.Children, condition)
If _mainWindow IsNot Nothing Then
Return _mainWindow
End If
Await Task.Delay(500)
retryCount += 1
End While
Throw New TimeoutException("Could not find the main window within the timeout period")
End Function
Private Function FindElementByAutomationId(automationId As String) As AutomationElement
If _mainWindow Is Nothing Then
Throw New InvalidOperationException("Window is not initialised")
End If
Dim condition As New PropertyCondition(AutomationElement.AutomationIdProperty, automationId)
Dim element As AutomationElement = _mainWindow.FindFirst(TreeScope.Descendants, condition)
If element Is Nothing Then
Throw New InvalidOperationException($"Could not find element with AutomationId: {automationId}")
End If
Return element
End Function
Private Shared Sub ClickButton(button As AutomationElement)
Dim pattern As Object = Nothing
If button.TryGetCurrentPattern(InvokePattern.Pattern, pattern) Then
DirectCast(pattern, InvokePattern).Invoke()
Else
Throw New InvalidOperationException("Button does not support InvokePattern")
End If
End Sub
Private Shared Sub SetTextBoxValue(textBox As AutomationElement, value As String)
Dim pattern As Object = Nothing
If textBox.TryGetCurrentPattern(ValuePattern.Pattern, pattern) Then
DirectCast(pattern, ValuePattern).SetValue(value)
Else
Throw New InvalidOperationException("TextBox does not support ValuePattern")
End If
End Sub
Private Shared Function GetTextValue(element As AutomationElement) As String
Return If(element.Current.Name, String.Empty)
End Function
Public Sub Dispose() Implements IDisposable.Dispose
Try
_appProcess?.CloseMainWindow()
_appProcess?.WaitForExit(3000)
If _appProcess?.HasExited = False Then
_appProcess?.Kill()
End If
Catch ex As Exception
Debug.WriteLine($"Error disposing application: {ex.Message}")
Finally
_appProcess?.Dispose()
GC.SuppressFinalize(Me)
End Try
End Sub
#End Region
#Region "Tests"
<Fact>
Public Async Function Addition_ShouldDisplayCorrectResult() As Task
Await StartApplicationAndGetMainWindow()
Dim firstNumberTextBox As AutomationElement = FindElementByAutomationId("FirstNumber")
Dim secondNumberTextBox As AutomationElement = FindElementByAutomationId("SecondNumber")
Dim addButton As AutomationElement = FindElementByAutomationId("AddButton")
Dim resultTextBlock As AutomationElement = FindElementByAutomationId("Result")
SetTextBoxValue(firstNumberTextBox, "10")
SetTextBoxValue(secondNumberTextBox, "5")
ClickButton(addButton)
Await Task.Delay(500)
Dim result As String = GetTextValue(resultTextBlock)
Assert.Equal("15.00", result)
End Function
<Fact>
Public Async Function DivisionByZero_ShouldDisplayErrorMessage() As Task
Await StartApplicationAndGetMainWindow()
Dim firstNumberTextBox As AutomationElement = FindElementByAutomationId("FirstNumber")
Dim secondNumberTextBox As AutomationElement = FindElementByAutomationId("SecondNumber")
Dim divideButton As AutomationElement = FindElementByAutomationId("DivideButton")
Dim errorTextBlock As AutomationElement = FindElementByAutomationId("ErrorMessage")
SetTextBoxValue(firstNumberTextBox, "10")
SetTextBoxValue(secondNumberTextBox, "0")
ClickButton(divideButton)
Await Task.Delay(500)
Dim errorMessage As String = GetTextValue(errorTextBlock)
Assert.Equal("Cannot divide by zero", errorMessage)
End Function
#End Region
End Class
' AZUL CODING ---------------------------------------
' WPF C#/VB - Unit & End-to-End Testing
' https://youtu.be/pZQBH8gg8iE
Public Class Calculator
Public Shared Function Add(a As Double, b As Double) As Double
Return a + b
End Function
Public Shared Function Subtract(a As Double, b As Double) As Double
Return a - b
End Function
Public Shared Function Multiply(a As Double, b As Double) As Double
Return a * b
End Function
Public Shared Function Divide(a As Double, b As Double) As Double
If b = 0 Then
Throw New ArgumentException("Cannot divide by zero")
End If
Return a / b
End Function
End Class
' AZUL CODING ---------------------------------------
' WPF C#/VB - Unit & End-to-End Testing
' https://youtu.be/pZQBH8gg8iE
Class MainWindow
Public Sub New()
InitializeComponent()
End Sub
Private Sub AddButton_Click(sender As Object, e As RoutedEventArgs)
PerformOperation(AddressOf Calculator.Add)
End Sub
Private Sub SubtractButton_Click(sender As Object, e As RoutedEventArgs)
PerformOperation(AddressOf Calculator.Subtract)
End Sub
Private Sub MultiplyButton_Click(sender As Object, e As RoutedEventArgs)
PerformOperation(AddressOf Calculator.Multiply)
End Sub
Private Sub DivideButton_Click(sender As Object, e As RoutedEventArgs)
PerformOperation(AddressOf Calculator.Divide)
End Sub
Private Sub ClearButton_Click(sender As Object, e As RoutedEventArgs)
FirstNumberTextBox.Text = ""
SecondNumberTextBox.Text = ""
ResultTextBlock.Text = ""
ErrorTextBlock.Text = ""
End Sub
Private Sub PerformOperation(operation As Func(Of Double, Double, Double))
Try
ErrorTextBlock.Text = ""
Dim first As Double
If Not Double.TryParse(FirstNumberTextBox.Text, first) Then
ErrorTextBlock.Text = "Please enter a valid first number."
Return
End If
Dim second As Double
If Not Double.TryParse(SecondNumberTextBox.Text, second) Then
ErrorTextBlock.Text = "Please enter a valid second number."
Return
End If
Dim result As Double = operation(first, second)
ResultTextBlock.Text = result.ToString("F2")
Catch ex As ArgumentException
ErrorTextBlock.Text = ex.Message
ResultTextBlock.Text = ""
Catch ex As Exception
ErrorTextBlock.Text = $"An error occurred: {ex.Message}"
ResultTextBlock.Text = ""
End Try
End Sub
End Class
<!-- AZUL CODING --------------------------------------- -->
<!-- WPF C#/VB - Unit & End-to-End Testing -->
<!-- https://youtu.be/pZQBH8gg8iE -->
<Window x:Class="AzulCalculator.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="Azul Coding - Simple Calculator" Background="#03506E" SizeToContent="WidthAndHeight" Foreground="White" ResizeMode="CanMinimize">
<StackPanel Margin="20">
<TextBlock Text="Simple calculator" FontSize="20" FontWeight="DemiBold" Margin="0,0,0,20"/>
<DockPanel Margin="0,5">
<TextBlock Text="First number:" MinWidth="115" VerticalAlignment="Center"/>
<TextBox x:Name="FirstNumberTextBox" Padding="5" AutomationProperties.AutomationId="FirstNumber"/>
</DockPanel>
<DockPanel Margin="0,5">
<TextBlock Text="Second number:" MinWidth="115" VerticalAlignment="Center"/>
<TextBox x:Name="SecondNumberTextBox" Padding="5" AutomationProperties.AutomationId="SecondNumber"/>
</DockPanel>
<StackPanel Orientation="Horizontal" Margin="0,20,0,10">
<Button x:Name="AddButton" Content="+" Width="40" Height="30" Margin="0,0,10,0" FontSize="20" Click="AddButton_Click" AutomationProperties.AutomationId="AddButton"/>
<Button x:Name="SubtractButton" Content="−" Width="40" Height="30" Margin="0,0,10,0" FontSize="20" Click="SubtractButton_Click" AutomationProperties.AutomationId="SubtractButton"/>
<Button x:Name="MultiplyButton" Content="×" Width="40" Height="30" Margin="0,0,10,0" FontSize="20" Click="MultiplyButton_Click" AutomationProperties.AutomationId="MultiplyButton"/>
<Button x:Name="DivideButton" Content="÷" Width="40" Height="30" Margin="0,0,10,0" FontSize="20" Click="DivideButton_Click" AutomationProperties.AutomationId="DivideButton"/>
<Button x:Name="ClearButton" Content="Clear" Width="60" Height="30" Click="ClearButton_Click" AutomationProperties.AutomationId="ClearButton"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,15,0,10">
<TextBlock Text="Result:" VerticalAlignment="Center" FontWeight="Bold"/>
<TextBlock x:Name="ResultTextBlock" Width="150" FontSize="20" Margin="15,0,0,0" FontWeight="Bold" AutomationProperties.AutomationId="Result"/>
</StackPanel>
<TextBlock x:Name="ErrorTextBlock" Foreground="#FF9696" TextWrapping="Wrap" VerticalAlignment="Top" AutomationProperties.AutomationId="ErrorMessage"/>
</StackPanel>
</Window>


