Skip to content
AgileLogics
6 min read

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.ThrowsExactly checks that an exception is thrown (it's the MSTest 4 name; older versions used Assert.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.