MStest Nunit Xunit
MStest Nunit Xunit
PS C:\>dotnet --info
https://dotnet.microsoft.com/en-us/
Target frameworks and .NET X
In the VS project file:
https://docs.microsoft.com/en-us/dotnet/standard/frameworks
ProjName.csproj
TFM:s <Project
Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetF
ramework>
MAUI <ImplicitUsings>enable</ImplicitU
sings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
Xamarin vNext: https://github.com/dotnet/maui
Big Performance Improvements
in .NET 5/6/7/8/9/...
• Performance Testing
BenchmarkTesting project in lectures/code_csharp folder
https://github.com/dotnet/benchmarkdotnet
https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-8/
Performance Improvements in .NET 8/9/... and AOT
• Publishing your app as Native AOT produces an app that's self-contained and that has
been ahead-of-time (AOT) compiled to native code
• Native AOT apps have faster startup time and smaller memory footprints. These apps
can run on machines that don't have the .NET runtime installed
• https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/
Add the following to the PropertyGroup setting in the .csproj file
<PropertyGroup>
<PublishAot>true</PublishAot> BenchmarkTesting_net8
</PropertyGroup>
dotnet publish -r win-x64 -c Release
dotnet publish -r linux-arm64 -c Release
https://www.codemag.com/Article/2010122/Windows-Desktop-Apps-and-.NET-5
• Modernizing Desktop Apps on Windows with .NET 7 (book)
https://docs.microsoft.com/en-us/dotnet/architecture/modernize-desktop/
• Overview of porting from .NET Framework to .NET
try-convert (dotnet tool install / update -g try-convert)
upgrade-assistant (dotnet tool install / update -g upgrade-assistant)
.NET API Portability Analyzer
.NET API Analyzer
https://docs.microsoft.com/en-us/dotnet/core/porting/
• Porting Projects to .NET 5 (links in YouTube comments)
https://www.youtube.com/watch?v=bvmd2F11jpA
• More info and resources at:
OneDrive > gmi2j3 > docs.and.code > Porting_Projects_to_.NET5+/
VS – Extensions > Manage Extensions...
https://
marketplace.vis
ualstudio.com/
https://visualstudio.microsoft.com/vs/features/extend/
Unit testing in C#
• Test methods recommended naming convention
(Test)_MethodToTest_ScenarioWeTest_ExpectedBehaviour
In the test method // In C# with MSTest, Nunit, xUnit
[TestClass], [TestFixture],
the pattern we use is public
{
class ReservationsTests
+17 overloads
Run the test from VS and cmd
• Run (Ctrl+R, A) and/or debug tests via the
main menu or the Test Explorer (right-click)
• Test Settings > Processor Architecture for ...
may need some review (x86/x64)
• If the test is not discoverable you may
have to rebuild or reload the solution
• By default the tests are grouped by output
result, so you can quickly find failed tests
• Run the tests from the command line
Using .NET X, you can run tests from
the console using the following command
dotnet test
dotnet test docs: https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-test
Visual Studio CodeLens
• CodeLens which is extremely useful may not be enabled – it can however be
activated and configured via the Tools > Options menu
• Above every important line of code like methods, classes or properties, Visual
Studio inserts a line with helpful links (code must be version controlled – git etc.)
Latest Tests and their Status
References used and Code Maps
Last author
• Read more about CodeLens here:
https://docs.microsoft.com/en-us/visualstudio/ide/
find-code-changes-and-other-history-with-codelens
Exploring Asserts
• Assertions are central to unit testing regardless of test
framework
• An assertion method is a method that takes parameters and
ensure a condition is met
• If an assertion fails, an exception is thrown, the method call
does not return and an error is reported
The tested method should throw an exception with a formatted message
about the error in question
[TestMethod]
If a test contains multiple public void TestMultipleAsserts()
assertions, any assert that {
follow the one that failed Assert.AreEqual(10, 5 + 5); // Success
Assert.AreEqual(10, 1 + 2); // 10 ≠ 3 => Error
will not be executed! Assert.AreEqual(20, 1 + 2); // Not executed
}
Standard C# Asserts
• Trace.Assert and Debug.Assert checks for a specific condition
If the condition is false, they display a message box that shows the call stack
If the condition is true, a failure message is not sent and the message box is not displayed
.NET X may not show a message box until .NET X WinForms is referenced?
• By default, the Debug.Assert method works only in debug builds
• Use the Trace.Assert method if you want to do assertions in both debug and release builds
/// <summary> TryCatchFinallyYield
/// https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.trace.assert
/// https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.debug.assert
/// </summary>
/// <param name="type"></param>
/// <param name="baseType"></param>
public static void MyMethod(Type type, Type baseType)
{
// Create an index for an array.
int index;
// Perform some action that sets the index.
index = -40;
// Test that the index value is valid.
Debug.Assert(index > -1);
// Perform some processing.
Trace.Assert(type != null, "Type parameter is null");
}
MSTest - Assertion on a single value 1
• The following Assert and StringAssert methods validate that a value meet a specific condition
• Assert.AreEqual(object expected, object actual): Tests whether the specified values are equal. Different numeric types are
treated as unequal even if the logical values are equal. 42L is not equal to 42.
• Assert.AreNotEqual(object expected, object actual): Tests whether the specified values are unequal. Same as AreEqual for
numeric values.
• Assert.AreSame(object expected, object actual): Tests whether the specified objects both refer to the same object
• Assert.AreNotSame(object expected, object actual): Tests whether the specified objects refer to different objects
• Assert.IsTrue(bool condition): Tests whether the specified condition is true
• Assert.IsFalse(bool condition): Tests whether the specified condition is false
• Assert.IsNull(object value): Tests whether the specified object is null
• Assert.IsNotNull(object value): Tests whether the specified object is non-null
• Assert.IsInstanceOfType(object value, Type type): Tests whether the specified object is an instance of the expected type
• Assert.IsNotInstanceOfType(object value, Type type): Tests whether the specified object is not an instance of the wrong
type
• StringAssert.Contains(string value, string substring): Tests whether the specified string contains the specified substring
• StringAssert.StartsWith(string value, string substring): Tests whether the specified string begins with the specified substring
• StringAssert.Matches(string value, Regex regex): Tests whether the specified string matches a regular expression
• StringAssert.DoesNotMatch(string value, Regex regex): Tests whether the specified string does not match a regular
expression
MSTest - Assertion on a single value 2
• The usage is pretty clear, but the difference for AreEqual and AreSame is more subtile
[TestMethod] MathOpsMsTests
[Timeout(TestTimeout.Infinite)] // Test timeout attribute in Milliseconds
public void TestSingleValAsserts()
{
var expected = "a";
var actual = "a";
xUnit is written
by the original
inventor of
NUnit
Comparison
from:
https://
xunit.net/
docs/
comparisons
Comparing xUnit.net to other frameworks 3
ThrowsException<T>
[Theory]
[Theory]
public static class MathHelper [InlineData(1, 1, 2)]
{ [InlineData(12, 30, 42)]
public static int Add(int a, int b) => a + b; [InlineData(14, 1, 15)]
public static bool IsOdd(int value) public void Test_Add_DataDriven(int a, int b, int expected)
{ {
return value % 2 == 1; Assert.Equal(expected, MathHelper.Add(a, b));
} }
}
[DataTestMethod] [Theory]
[DataRow(3)] MSTest [TestCase(3)]
[DataRow(5)]
[DataRow(6)]
[TestCase(5)]
[TestCase(6)]
NUnit
public void MyFirstTheory(int value) public void MyFirstTheory(int value)
{ {
Assert.IsTrue(MathHelper.IsOdd(value)); Assert.That(MathHelper.IsOdd(value), Is.True);
} }
[DataTestMethod] [Theory]
[DataRow(1, 1, 2)] MathOps and MathOps***Test [TestCase(1, 1, 2)]
[DataRow(12, 30, 42)] [TestCase(12, 30, 42)]
[DataRow(14, 1, 15)] [TestCase(14, 1, 15)]
public void Test_Add_DataDriven(int a, int b, int expected) public void Test_Add_DataDriven(int a, int b, int expected)
{ {
Assert.AreEqual(expected, MathHelper.Add(a, b)); Assert.That(expected, Is.EqualTo(MathHelper.Add(a, b)));
} }
MSTest/xUnit – DynamicData
• If your data cannot be set into an attribute parameters (non constant values or complex
objects), you can use the [DynamicData] attribute
• This attribute allows to get the values of the parameters from a method or a property
• The method or the property must return an IEnumerable<object[]>
Each row corresponds to the values of a test
• https://www.infoworld.com/article/3122592/my-two-cents-on-the-yield-keyword-in-c.html
[DataTestMethod]
[DataTestMethod] [DynamicData(nameof(Data), DynamicDataSourceType.Property)]
[DynamicData(nameof(GetData), DynamicDataSourceType.Method)] public void Test_Add_DynamicData_Property(int a, int b, int expected)
public void Test_Add_DynamicData_Method(int a, int b, int expected) {
{ var actual = MathHelper.Add(a, b);
var actual = MathHelper.Add(a, b); Assert.AreEqual(expected, actual);
Assert.AreEqual(expected, actual); }
}
yield return public static IEnumerable<object[]> Data
DynamicDataMsTests
public static IEnumerable<object[]> GetData() {
{ get DynamiDataXUnitTests
yield return new object[] { 1, 1, 2 }; {
yield return new object[] { 12, 30, 42 }; yield return new object[] { 1, 1, 2 };
yield return new object[] { 14, 1, 15 }; yield return new object[] { 12, 30, 42 };
} yield return new object[] { 14, 1, 15 };
}
}
MSTest – lifecycle attributes and test log 1
• Initialize/Cleanup by Assembly,
Class and Test
• Some tests may have pre-
conditions and may also need
some cleanup
• For instance, if you need to set a
global configuration, or to delete
some files after a test ran. This
may be more useful for
integration tests than for unit
tests
Also an execution log may be
needed to find more complicated
errors that are hard to debug
Open test log – Ctrl+L
Output can be outputted via
Standard Output or via
TestContext Messages
LifecycleAttributes
MSTest – lifecycle [ClassInitialize]
public static void ClassInitialize(TestContext testContext)
{
• Workers property: The number of threads to run the tests. Set it to 0 to use the number of cores on
your computer
• Scope property: Determine if the runner must parallelize tests on method or class level. MethodLevel
will run all tests in parallel. ClassLevel will run all test classes in parallel, but tests in a class are run
sequentially. You should use ClassLevel if the tests within classes have mutual dependencies
• If you have multiple test assemblies that you want to <?xml version="1.0" encoding="utf-8"?>
<RunSettings>
parallelize in your solution you can create a file named <MSTest>
<Parallelize>
<runsettings>.runsettings <Workers>8</Workers>
<Scope>ClassLevel</Scope>
To set the run settings file, select: Test > Configure Run </Parallelize>
</MSTest>
Settings > Select Solution Wide runsetting File </RunSettings>
MSTest – Execute tests in parallel 2
• If you want to get the maximum performance, you should use
Workers = 0 and Scope = ExecutionScope.MethodLevel
• A method can contains multiple tests if you use data driven tests.
Each row of a data test is executed sequentially
• To opt-out parallelize for a specific test or class use [DoNotParallelize]
• Test Manager may not report total time correct! It should be 15 + 5 = 20 sec.
[assembly: Parallelize(Workers = 0, Scope = ExecutionScope.MethodLevel)]
namespace MyparallelExecutionTest [TestClass]
{ public class TestClass2
[TestClass] {
public class TestClass1 [TestMethod]
{ [DoNotParallelize]
[TestMethod] // will add extra 5 sec. if ExecutionScope.MethodLevel
public void Test1() => Thread.Sleep(5000); public void Test1() => Thread.Sleep(5000);
[TestMethod]
public void Test2() => Thread.Sleep(5000);
[TestMethod]
public void Test2() => Thread.Sleep(5000);
[DataTestMethod]
[DataRow(5000)] [TestMethod]
[DataRow(5000)] public void Test3() => Thread.Sleep(5000);
[DataRow(5000)] }
public void Test3(int i) => Thread.Sleep(i); }
} MyparallelExecutionTest
Unit test coverage
• Test > Analyze Code Coverage
Red = Not covered, Blue = Covered
https://docs.microsoft.com/en-us/visualstudio/test/using-code-coverage-to-determine-how-much-code-is-being-tested
Code Coverage Results window
• Coverage results and code coloring are not automatically updated after you
modify your code or when you run tests
• Code coverage is counted in blocks. A block is a piece of code with exactly
one entry and exit point
• Display results in terms of lines by choosing Add/Remove Columns when right
clicking the table
• [ExcludeFromCodeCoverage] results decorator
• Export and import buttons
Recommended viewing and reading
• Blog post about MSTest v2
https://www.meziantou.net/2018/01/22/mstest-v2-setup-a-test-project-and-run-tests
• Unit Testing Tutorial for C# Developers
https://www.youtube.com/watch?v=HYrXogLj7vg
• Intro to Unit Testing in C# using XUnit
https://www.youtube.com/watch?v=ub3P8c87cwk
• Rövarspråket in CSharp
OneDrive > gmij23 > lectures > code_rovarsprak
• NUnit documentation
https://github.com/nunit/docs
• MSTest documentation
https://github.com/Microsoft/testfx-docs
• xUnit documentation
https://xunit.net/#documentation