Unit Test (1) --- Basic

Note: this article is published on 07/11/2024.

This series of articles will discuss Unit Test

This article will discuss the basic of Unit Test.

A - Introduction

Unit tests give developers and testers a quick way to look for logic errors in the methods of classes. The content of this article:

Unit testing is a software testing method by which individual units of source code—sets of one or more computer program modules together with associated control data, usage procedures, and operating procedures—are tested to determine whether they are fit for use.

Image, when we have a developed software, QA spent months to test it throughly, while in the last stage, we find a new bug, it is a very simple fix, however, to make sure the software working well in the whole, we need to test it piece by piece, so called regression testing. If let QA to retake the job, that will take one testing cycle, months work. However, if we have s unit test for all individual piece of software, especially, if the tests can be run automatically, then we can reduce the time for the regression testing. This is what the unit test for.

  • A - Introduction
  • B - AAA in Unit Test
  • C - Testing tools
  • D - A Sample of MS Unit Test
  • E - Why Unit Test
  • F - Characteristics of a good unit test

B - AAA in Unit Test

The AAA (Arrange, Act, Assert) pattern is a common way of writing unit tests for a method under test.

  • The Arrange section of a unit test method initializes objects and sets the value of the data that is passed to the method under test.

  • The Act section invokes the method under test with the arranged parameters.

  • The Assert section verifies that the action of the method under test behaves as expected. For .NET, methods in the Assert class are often used for verification.Unit test tools and tasks --- MS

The unit tests are created

C - Testing tools

.NET is a multi-language development platform, for each language, one can choose between several test frameworks.

C -1: NUnit

NUnit is a unit-testing framework for all .NET languages. Initially, NUnit was ported from JUnit (born on 1997 on a flight by Kent Beck, Erich Gamma), and the current production release has been rewritten with many new features and support for a wide range of .NET platforms. It's a project of the .NET Foundation.

Major atributes:

  • [TestFixture] --- a class that contains unit tests.
  • [Test] --- a method is a test method.

C -2: xUnit

xUnit is a free, open-source, community-focused unit testing tool for .NET. The original inventor of NUnit v2 wrote xUnit.net. xUnit.net is the latest technology for unit testing .NET apps. xUnit.net is a project of the .NET Foundation and operates under its code of conduct.

Major atributes:

  • none --- a class that contains unit tests.
  • [Fact] --- a method is a test method.

C - 3: MSTest

MSTest is the default test framework that is shipped along with Visual Studio. It is the Microsoft test framework for all .NET languages. It's extensible and works with both .NET CLI and Visual Studio.

Major atributes:

  • [Testclass] --- a class that contains unit tests.
  • [TestMethod] --- a method is a test method.

D - A Sample of MS Unit Test

We have a BankAccount class:

using System;

namespace BankAccountNS
{
    /// <summary>
    /// Bank account demo class.
    /// </summary>
    public class BankAccount
    {
        private readonly string m_customerName;
        private double m_balance;
        private BankAccount() { }
        public BankAccount(string customerName, double balance)
        {
            m_customerName = customerName;
            m_balance = balance;
        }
        public string CustomerName
        {
            get { return m_customerName; }
        }
        public double Balance
        {
            get { return m_balance; }
        }
        public void Debit(double amount)
        {
            if (amount > m_balance)
            {
                throw new ArgumentOutOfRangeException("amount");
            }
            if (amount < 0)
            {
                throw new ArgumentOutOfRangeException("amount");
            }
            //m_balance += amount; // intentionally incorrect code
            m_balance -= amount;
        }
        public void Credit(double amount)
        {
            if (amount < 0)
            {
                throw new ArgumentOutOfRangeException("amount");
            }
            m_balance += amount;
        }
        public static void Main()
        {
            BankAccount ba = new BankAccount("Mr. Bryan Walton", 11.99);
            ba.Credit(5.77);
            ba.Debit(11.22);
            Console.WriteLine("Current balance is ${0}", ba.Balance);
        }
    }
}

Create a unit test project (we use Visual Studio 19)

  1. On the File menu, select Add > New Project.

  2. Type test in the search box, select C# as the language, and then select the C# MSTest Unit Test Project (.NET Core) for .NET Core template, and then click Next.

  3. Name the project BankTests and click Next.

  4. Choose a recommended target framework (I used .Net Core 3.1), and then choose Create.

    The BankTests project is added to the Bank solution. Change the program.cs file name to BankAccountTests.cs.

  5. In the BankTests project, add a reference to the Bank project.

    In Solution Explorer, select Dependencies under the BankTests project and then choose Add Reference (or Add Project Reference) from the right-click menu.

  6. In the Reference Manager dialog box, expand Projects, select Solution, and then check the Bank item.

  7. Choose OK.

We have the solution like this

Copying the code below into the BankAccountTests.cs:

using Microsoft.VisualStudio.TestTools.UnitTesting;
using BankAccountNS;

namespace BankTest
{
    [TestClass]
    public class BankAccountTests
    {
        [TestMethod]
        public void Debit_WithValidAmount_UpdatesBalance()
        {
            // Arrange
            double beginningBalance = 11.99;
            double debitAmount = 4.55;
            double expected = 7.44;
            BankAccount account = new BankAccount("Mr. Bryan Walton", beginningBalance);
            // Act
            account.Debit(debitAmount);
            // Assert
            double actual = account.Balance;
            Assert.AreEqual(expected, actual, 0.001, "Account not debited correctly");
        }
    }
}

Test class requirements

The minimum requirements for a test class are:

  • The  attribute is required on any class that contains unit test methods that you want to run in Test Explorer.

  • Each test method that you want Test Explorer to recognize must have the   attribute.

You can have other classes in a unit test project that do not have the [TestClass] attribute, and you can have other methods in test classes that do not have the [TestMethod] attribute. You can call these other classes and methods from your test methods.

Test method requirements

A test method must meet the following requirements:

  • It's decorated with the [TestMethod] attribute.

  • It returns void.

  • It cannot have parameters.

Build and run the test:

  • Build Solution.

  • Open the Test Explorer by choosing Test > Test Explorer from the top menu bar (or press Ctrl + ET).

We have Test Explorer: 

  • Choose Run All to run the test (or press Ctrl + RV).

When something is wrong, the Test Explorer could show more messages like this:

The Test Explorer toolbar helps you discover, organize, and run the tests that you are interested in.

E - Why Unit Test

  • Less time performing functional tests

    • Functional tests are expensive. They typically involve opening up the application and performing a series of steps that you (or someone else) must follow in order to validate the expected behavior. These steps might not always be known to the tester. They'll have to reach out to someone more knowledgeable in the area in order to carry out the test. Testing itself could take seconds for trivial changes, or minutes for larger changes. Lastly, this process must be repeated for every change that you make in the system.
    • Unit tests, on the other hand, take milliseconds, can be run at the press of a button, and don't necessarily require any knowledge of the system at large. Whether or not the test passes or fails is up to the test runner, not the individual.
  • Protection against regression

    • Regression defects are defects that are introduced when a change is made to the application. It's common for testers to not only test their new feature but also test features that existed beforehand in order to verify that previously implemented features still function as expected.
    • With unit testing, it's possible to rerun your entire suite of tests after every build or even after you change a line of code. Giving you confidence that your new code doesn't break existing functionality.
  • Executable documentation

    • It might not always be obvious what a particular method does or how it behaves given a certain input. You might ask yourself: How does this method behave if I pass it a blank string? Null?
    • When you have a suite of well-named unit tests, each test should be able to clearly explain the expected output for a given input. In addition, it should be able to verify that it actually works.
  • Less coupled code

    • When code is tightly coupled, it can be difficult to unit test. Without creating unit tests for the code that you're writing, coupling might be less apparent.
    • Writing tests for your code will naturally decouple your code, because it would be more difficult to test otherwise.

F - Characteristics of a good unit test

  • Fast: It isn't uncommon for mature projects to have thousands of unit tests. Unit tests should take little time to run. Milliseconds.
  • Isolated: Unit tests are standalone, can be run in isolation, and have no dependencies on any outside factors such as a file system or database.
  • Repeatable: Running a unit test should be consistent with its results, that is, it always returns the same result if you don't change anything in between runs.
  • Self-Checking: The test should be able to automatically detect if it passed or failed without any human interaction.
  • Timely: A unit test shouldn't take a disproportionately long time to write compared to the code being tested. If you find testing the code taking a large amount of time compared to writing the code, consider a design that is more testable.

 

References


Similar Articles