Testing Visual Basic Code
Prerequisites: Namespaces, projects, and NuGet
Automated tests check that your code does what you think — and keeps doing it after every change. The .NET SDK includes templates for three test frameworks, all usable from VB: MSTest, xUnit, and NUnit.
An MSTest project
dotnet new classlib -lang VB -o Shop.Core
dotnet new mstest -lang VB -o Shop.Tests
dotnet add Shop.Tests reference Shop.Core
The code under test, in Shop.Core:
' illustrative: Shop.Core/PriceCalculator.vb
Public Class PriceCalculator
Public Function Discount(total As Decimal) As Decimal
If total < 0 Then Throw New ArgumentOutOfRangeException(NameOf(total))
Return If(total >= 100D, total * 0.9D, total)
End Function
End Class
The tests, in Shop.Tests:
' illustrative: Shop.Tests/PriceCalculatorTests.vb
Imports Microsoft.VisualStudio.TestTools.UnitTesting
Imports Shop.Core
<TestClass>
Public Class PriceCalculatorTests
Private ReadOnly calc As New PriceCalculator()
<TestMethod>
Public Sub LargeOrdersGetTenPercentOff()
Assert.AreEqual(90D, calc.Discount(100D))
End Sub
<TestMethod>
Public Sub SmallOrdersPayFullPrice()
Assert.AreEqual(50D, calc.Discount(50D))
End Sub
<TestMethod>
<DataRow(0)>
<DataRow(99)>
Public Sub TotalsBelowOneHundredAreUnchanged(total As Integer)
Assert.AreEqual(CDec(total), calc.Discount(total))
End Sub
<TestMethod>
Public Sub NegativeTotalsAreRejected()
Assert.ThrowsExactly(Of ArgumentOutOfRangeException)(Function() calc.Discount(-1D))
End Sub
End Class
$ dotnet test
...
Passed! - Failed: 0, Passed: 5, Skipped: 0, Total: 5, Duration: 32 ms - Shop.Tests.dll (net10.0)
<TestClass>and<TestMethod>are attributes that mark tests.<DataRow(...)>runs the same test with different inputs.Assert.ThrowsExactlychecks that an exception is thrown (it's the MSTest 4 name; older versions usedAssert.ThrowsException).- Name tests after the behavior they check.
- Visual Studio's Test Explorer runs and debugs tests with a click.
xUnit uses <Fact> and <Theory>/<InlineData> instead; NUnit uses
<Test> and <TestCase>. The ideas are identical.
Making code testable
- Put logic in class libraries, not in forms or
Main. - Pass dependencies (clocks, file access, databases) in through interfaces, as in the Interfaces lesson, so tests can supply fakes.
- Test the edges: empty input, zero, negative numbers,
Nothing, very large values.
Characterization tests for legacy code
Before changing old code you don't fully understand, write tests that record what it does today — even if that behavior looks wrong. Then refactor with confidence: if a test fails, you changed behavior, and you can decide whether that was intended.