В тази лекция се разглеждат основните елементи в unit testing-а (component testing-а) с примери от платформата .NET Framework. Показват се писането на тестове на Visual Studio Team Test и писането на тестове с популярната библиотека за unit testing - NUnit. В края се разглеждат някои добри практики при писането на unit тестове. Вярвам, че лекцията ще е полезна за всеки начинаещ програмист, който иска да се научи не само да пише код, но и да го прави качествено.

Видео лекция за Unit Testing

Съдържание на лекцията

Table of Contents

    •    What is Unit Testing?
    •    Code and Test vs. Test Driven Development
    •    Unit testing Frameworks
    •    Visual Studio Team Test
    •    Nunit
    •    Gallio
    •    Unit Testing Best Practices

What is Unit Testing?

Unit Test – Definition

A unit test is a piece of code written by a developer that exercises a very small, specific area of functionality of the code being tested.

Manual Testing

    •    You have already done unit testing
    •    Manually, by hand
    •    Manual tests are less efficient
    •    Not structured
    •    Not repeatable
    •    Not on all your code
    •    Not easy to do as it should be

Unit Testing – Some Facts

    •    Tests are specific pieces of code
    •    In most cases unit tests are written by developers, not by QA engineers
    •    Unit tests are released into the code repository along with the code they test
    •    Unit testing framework is needed
    •    Visual Studio Team Test (VSTT)
    •    NUnit, MbUnit, Gallio, etc.

Unit Testing – More Facts

    •    All classes should be tested
    •    All methods should be tested
    •    Trivial code may be omitted 
    •    E.g. property getters and setters
    •    Ideally all unit tests should pass before check-in into the source control repository

Why Unit Tests?

    •    Unit tests dramatically decrease the number of defects in the code 
    •    Unit tests improve design 
    •    Unit tests are good documentation
    •    Unit tests reduce the cost of change
    •    Unit tests allow refactoring
    •    Unit tests decrease the defect-injection rate due to refactoring / changes

Code and Test vs. Test Driven Development

Unit Testing Approaches

    •    "Code and Test" approach
    •    Classical approach
    •    "Test First" approach
    •    Test-driven development (TDD)

Code and Test Approach

Test Driven Development (TDD)

Why Test Driven Development?

    •    Helps find design issues early and avoids rework
    •    Writing code to satisfy a test is a focused activity – less chance of error
    •    Tests will be more comprehensive than when written after code

Unit Testing Frameworks

    •    JUnit
    •    The first popular unit testing framework
    •    Based on Java, written by Kent Beck & Co.
    •    Similar frameworks have been developed for a broad range of computer languages
    •    NUnit – for C# and all .NET languages
    •    cppUnit, jsUnit, PhpUnit, PerlUnit, ...
    •    Visual Studio Team Test (VSTT)
    •    Developed by Microsoft, integrated in VS

Visual Studio Team Test


Visual Studio Team Test – Features

    •    Team Test (VSTT) is very well integrated with Visual Studio
    •    Create test projects and unit tests
    •    Execute unit tests
    •    View execution results
    •    View code coverage
    •    Located in the assembly Microsoft.VisualStudio.QualityTools.UnitTestFramework.dll

Visual Studio Team Test –  Attributes

    •    Test code is annotated using custom attributes:
    •    [TestClass] – denotes a class holding unit tests
    •    [TestMethod] – denotes a unit test method
    •    [ExpectedException] – test causes an exception
    •    [Timeout] – sets a timeout for test execution
    •    [Ignore] – temporary ignored test case
    •    [ClassInitialize], [ClassCleanup] – setup / cleanup logic for the testing class
    •    [TestInitialize], [TestCleanup] – setup / cleanup logic for each test case


    •    Predicate is a true / false statement 
    •    Assertion is a predicate placed in a program code 
    •    Indicates that the developer thinks that the predicate is always true at that place
    •    If an assertion fails, the method call does not return and an error is reported
    •    Example:

VSTT – Assertions

    •    Assertions check condition and throw exception if condition is not satisfied
    •    Comparing values
    •    AreEqual(expected value, calculated value [,message]) – compare two values for equality
    •    Comparing objects
    •    AreSame(expected object, current object [,message]) – compares object references

VSTT – Assertions (2)

    •    Checking for null value
    •    IsNull(object [,message])
    •    IsNotNull(object [,message])
    •    Conditions
    •    IsTrue(condition)
    •    IsFalse(condition)
    •    Forced test fail
    •    Fail(message)

The 3A Pattern

    •    Arrange all necessary preconditions and inputs
    •    Act on the object or method under test
    •    Assert that the expected results have occurred

Code Coverage

    •    Shows what percent of the code we’ve covered
    •    We may have pointless unit tests that are calculated in the code coverage
    •    70-80% of coverage is excellent


NUnit – Features

    •    Test code is annotated using custom attributes
    •    Test code contains assertions
    •    Tests organized as multiple assemblies
    •    Two execution interfaces
    •    GUI: nunit-gui.exe
    •    Console: nunit-console.exe

NUnit – Features (2)

    •    NUnit provides:
    •    Creating and running tests as NUnit Test Projects
    •    Visual Studio support

NUnit – Attributes

    •    [TestFixture] – Marks a class that contains tests 
    •    Should be a public class
    •    [Test] – Marks a method as an unit test
    •    [TestFixtureSetUp] – Indicate a setup method that will be ran once before all other tests
    •    Called before the tests are started

NUnit – Attributes (2)

    •    [TestFixtureTearDown] – Indicate a tear down method that will be ran once after all other tests have run
    •    Called after all the tests have finished
    •    [SetUp] – indicates a setup method that should be ran before each of the tests
    •    [TearDown] – used to indicate a tear down that method should be ran after each of the tests

NUnit – Attributes (3)

    •    [ExpectedException(typeof(Exception))]
    •    When you expect an exception to be thrown
    •    Will only pass if exception type was thrown
    •    [Ignore("Not ready for primetime")]
    •    Indicates test that is not ready, or should not be run

NUnit – Assertions

    •    Using NUnit.Framework.Assert class
    •    Assertions check condition and throw exception if condition is not satisfied
    •    Comparing values
    •    AreEqual(expected value, calculated value [, message]) - compare values

NUnit – Assertions (2)

    •    Comparing objects
    •    IsNull(object [, message])
    •    IsNotNull(object [, message])
    •    AreSame(expected object, current object [,message]) – compare object references
    •    Conditions
    •    IsTrue(condition)
    •    IsFalse(condition)

NUnit – Assertions (3)

    •    Forced test fail
    •    Fail()
    •    Explicit tests (ignored unless run explicitly)
    •    [TestFixture, Explicit]
    •    [Test, Explicit]
    •    Categories
    •    [TestFixture, Category("LongRunning")]
    •    [Test, Category("Long")]

NUnit – Example: Class

public class Account
    private float balance;
    public void Deposit(float amount)
    public void Withdraw(float amount)
    public void TransferFunds(
      Account destination, float amount)
    { … }
    public float Balance { get { return balance; } }

NUnit – Example: Test

using NUnit.Framework;

public class AccountTest
    public void TransferFunds()
        Account source = new Account();
        Account dest = new Account();
        source.TransferFunds(dest, 100.00F);
        Assert.AreEqual(250.00F, dest.Balance);
        Assert.AreEqual(100.00F, source.Balance);

SetUp & TearDown Timeline

    •    Begin Tests
    •    Call TestFixtureSetUp
    •    Call SetUp
    •    Call ExampleTestOne
    •    Call TearDown
    •    Call SetUp
    •    Call ExampleTestTwo
    •    Call TearDown
    •    Call TestFixtureTearDown
    •    End Tests

Unit Testing Best Practices

Naming Standards for Unit Tests

    •    The test name should express a specific requirement that is tested
    •    Usually prefixed with [Test]
    •    E.g. TestAccountDepositNegativeSum()
    •    The test name should include
    •    Expected input or state 
    •    Expected result output or state
    •    Name of the tested method or class

Naming Standards for Unit Tests – Example

    •    Given the method:
public int Sum(params int[] values)

with requirement to ignore numbers greater than 100 in the summing process
    •    The test name should be:

When Should a Test be Changed or Removed?

    •    Generally, a passing test should never be removed
    •    These tests make sure that code changes don’t break working code
    •    A passing test should only be changed to make it more readable
    •    When failing tests don’t pass, it usually means there are conflicting requirements
    •    New features allows negative numbers
    •    Earlier test fails due to a requirement change
    •    Two course of actions:
    •    Delete the failing test after verifying if it’s valid
    •    Change the old test:
    •    Either testing the new requirement
    •    Or test the older requirement under new settings

Tests Should Reflect Required Reality

    •    A failing test should prove that there is something wrong with the production code
    •    Not with the unit test code

What Should Assert Messages Say?

    •    Assert message in a test is one of the most important things
    •    Tells us what we expected to happen but didn’t, and what happened instead
    •    Good assert message helps us track bugs and understand unit tests more easily
    •    Example:
    •    "Withdrawal failed: accounts are not supposed to have negative balance."

What Should Assert Messages Say? (2)

    •    Express what should have happened and what did not happen
    •    “Verify() did not throw any exception”
    •    “Connect() did not open the connection before returning it”
    •    Do not:
    •    Provide empty or meaningless messages 
    •    Provide messages that repeat the name of the test case

Avoid Multiple Asserts in a Single Unit Test

    •    Avoid multiple asserts in a single test case
    •    If the first assert fails, the test execution stops for this test case
    •    Affect future coders to add assertions to test rather than introducing a new one

Unit Testing – The Challenge

    •    The concept of Unit Testing has been around for many years
    •    New methodologies in particular XP, have turned unit testing into a cardinal foundation of software development
    •    Writing good & effective Unit Tests is hard!
    •    This is where supporting integrated tools and suggested guidelines enter the picture
    •    The ultimate goal is tools that generate unit tests automatically


    •    Write three classes: Student, Course and School. Students should have name and unique number (inside the entire School). Name can not be empty and the unique number is between 10000 and 99999. Each course contains a set of students. Students in a course should be less than 30 and can join and leave courses.
    •    Write VSTT tests for these two classes
    •    Use 2 class library projects in Visual Studio: School.csproj and TestSchool.csproj
    •    Execute the tests using Visual Studio and check the code coverage. Ensure it is at least 90%.

Useful Links

    •    A Unit Testing Walkthrough with Visual Studio Team Test – http://msdn.microsoft.com/en-us/library/ms379625(VS.80).aspx
    •    NUnit – www.nunit.org
    •    Extreme Programming  – www.extremeprogramming.org
    •    XP Programming – www.xprogramming.com
    •    Advanced Unit Testing – www.codeproject.com/csharp/autp1.asp