Create a new C# class library solution called MsTestRoot.  The default class1 has been replaced with this code to calculate a square root:

namespace MsTestRoot
{
    public class Rooter
    {
        public double SquareRoot(double input)
        {
            if (input <= 0.0)
            {
                throw new ArgumentOutOfRangeException();
            }
            double result = input;
            double previousResult = -input;
            while (Math.Abs(previousResult - result) > result / 1000)
            {
                previousResult = result;
                result = (result + input / result) / 2;
            }
            return result;
        }
    }
}

The next step is to test this code which can be done in several ways.

  • add testing code directly to the class library project,
  • add a new class to the class library which contains the test code. 

Both methods mean that we are mixing test code with live running code so the latter choice is better. Add a new project to the solution of type C# MSTest Test Project, with default name of TestProject1:

2-6-1.png

The above code is autogenerated. A class with one method has been added to the project.  The class has been decorated with the [TestClass] attribute telling MsTest that it contains unit test code.  Every unit test method in the test class must be decorated with the [TestMethod] attribute.

For simplicity the class is not renamed.  but the test methods are given more meaningful names.

using MsTestRoot;
namespace TestProject1
{
    [TestClass]
    public class UnitTest1
    {
        [TestMethod]
        public void BasicRooterTest()
        {
            // Create an instance to test
            Rooter rooter = new Rooter();
            // Define test input & expected output
            double expectedResult = 2.0;
            double input = expectedResult * expectedResult;
            // Run the method under test
            double actualResult = rooter.SquareRoot(input);
            // Verify the result
            Assert.AreEqual(expectedResult, actualResult,
                        delta: expectedResult / 100);
        }
        [TestMethod]
        public void RooterValueRange()
        {
            // Create an instance to test
            Rooter rooter = new Rooter();
            // Try a range of values
            for (double expectedResult = 1.0;
                expectedResult < 1e+6;
                expectedResult++)
            {
                RooterOneValue(rooter, expectedResult);
            }
        }
        private void RooterOneValue(Rooter rooter, double expectedResult)
        {
            double input = expectedResult * expectedResult;
            // Run the method under test
            double actualResult = rooter.SquareRoot(input);
            // Verify the result
            Assert.AreEqual(expectedResult, actualResult,
                        delta: expectedResult / 1000);
        }
        [TestMethod]
        public void RooterTestNegativeInput()
        {
            Rooter rooter = new Rooter();
            try
            {
                double actualResult = rooter.SquareRoot(-10);
            }
            catch (ArgumentOutOfRangeException e)
            {
                return;
            }
            Assert.Fail();
        }
    }
}

This shows the basic method of writing any automated test.  Set up the test system, set up an input and expected output value, perform the operation & finally check that the result is as expected.  Line 21 is the test on the result.  Tests are generally done using assertions.  The Assert class is defined in the UnitTesting namespace.  It contains a number of boolean test methods some of which we will be using shortly. The one used here allows us to check for a result with a given variance.  We need to do this because our iterative square root calculation is not guaranteed to be 100% accurate so we are testing the result with a variance of 1%.

The above code will not compile. This is because our test code is in a separate project to the square root code, and the test project need to reference the Rooter project in our test project.  To link, right click the Dependencies (grey highlight) for the TestProject1 project in Solution Explorer:

2-6-2.png

Select Add Project Reference. This displays the Reference Manager dialog.  Select the Solution entry & the MsTestRoot entry as shown:

2-6-3.png

The errors are resolved.

Simply testing with a single value is rarely sufficient.  For that reason a second test method has been added to the test class.

Two methods have been added to the test class, but only one is a TestMethod.  VS will identify the RooterValueRange method as a test method & ignore the RooterOneValue method.  This method is called by the RooterValueRange method for all values between 1 & 1,000,000.  These are arbitrary values used purely for demonstration purposes. 

Assuming that the previous tests pass OK, we should also test for values that do not work. You cannot take the square root of a negative number using real numbers.

Methods 
AreEqual              Tests whether the specified objects are equal and throws an exception if the two objects are not equal. Different numeric types are treated as unequal even if the logical values are equal. 42L is not equal to 42.
AreNotEqual Tests whether the specified strings are unequal and throws an exception if they are equal. The invariant culture is used for the comparison.
AreNotSame Tests whether the specified objects refer to different objects and throws an exception if the two inputs refer to the same object.
AreSame Tests whether the specified objects both refer to the same object and throws an exception if the two inputs do not refer to the same object.
Equals Static equals overloads are used for comparing instances of two types for reference equality. This method should not be used for comparison of two instances for equality. This object will always throw with

Assert.Fail. Please use Assert.AreEqual and associated overloads in your unit tests.

Fail Throws an AssertFailedException.
Inconclusive Throws an AssertInconclusiveException.
IsFalse Tests whether the specified condition is false and throws an exception if the condition is true.
Methods 
IsInstanceOfType Tests whether the specified object is an instance of the expected type and throws an exception if the expected type is not in the inheritance hierarchy of the object.
IsNotInstanceOfType Tests whether the specified object is not an instance of the wrong type and throws an exception if the specified type is in the inheritance hierarchy of the object.
IsNotNull Tests whether the specified object is non-null and throws an exception if it is null.
IsNull Tests whether the specified object is null and throws an exception if it is not.
IsTrue Tests whether the specified condition is true and throws an exception if the condition is false.
ReplaceNullChars Replaces null characters ('\0') with "\0".
ThrowsException<T>(Action)   Tests whether the code specified by delegate action throws exact given exception of type T (and not of derived type) and throws

AssertFailedException if code does not throw exception or throws exception of type other than T.

The above table does not include the many overloads of some of the Assert methods which are described in the MSDN documentation [1]

The tests have been created but are not yet run. MsTest is fully integrated into Visual Studio.  Use the Test -> Test Explorer menu to make the Test Explorer window visible.  The window currently looks like the one below:

2-6-4.png

MsTest system has detected 3 unit test methods.  It has done this through a process called reflection which has searched for all entities marked with one of the test attributes.

The ! icon indicates that these tests have not yet been run.  You may need to expand the branches in the explorer window.

The run menu at the top of the window shows various options for running tests which can be individually or all together or as part of a playlist.  For example, you may have playlists for normal tests such as the BasicRooterTest and RooterValueRange which test normal operation and another for exceptional conditions such as the RooterTestNegativeInput test.  The example tests are unallocated to a playlist.

Run all the tests using the Run All button.  Assuming all tests run OK the tests will all have green ticks beside them and a time will be displayed against each test:

2-6-5.png

Timings can be ignored as they are only an indication & not useful for benchmarking.  

However, if any tests fail this will be indicated by a red cross next to the failing test and a summary of the test run is provided. If a test fails the details are found by selecting the test in the Test Explorer window & inspecting the lower part of the window:

2-6-6.png

In this case I simulated a failure by testing for a FormatException instead of the expected ArgumentOutOfRange exception.

I can't resist pointing out that the square root of a positive real number has two roots. Dave (talk)