Unit Testing
Telerik Academy Alpha

 

UT

 Table of contents

What is Unit Testing

 MInd map

  • You should make a mind map
    • What you know about it
    • How it is different from other types of tests
      • What kind of tests do you know
  • You have about 5 minutes to think and make it

 

 What?

  •  Unit testing is a very wide topic
    • It can get really complicated
    • But don't be afraid to dive into it
      • Basic concepts are what you would learn during this course
      • This will help you to build a strong foundation
        • And be confident in yourself

 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.


"Program testing can be used to show the presence of bugs, but never to show their absence!"
                                                              Edsger Dijkstra, [1972]

 Definition

A unit test is a piece of code that invokes a unit of work and checks one specific end result of that unit of work. If the assumptions on the end result turn out to be wrong, the unit test has failed. A unit test’s scope can span as little as a method or as much as multiple classes.”


A unit of work is the sum of actions that take place between the invocation of a public method in the system and a single noticeable end result by a test of that system. …”

                                     The Art of Unit Testing, Second Edition

 Unit testing "examples" 

 Lifecycle 

Software Development Lifecycle 

 Lifecycle 

QUESTION:
Who’s responsible for the quality of a software system?

 

ANSWERS:

  - QA Engineer

  - Software Engineer

  - Everyone

  - No one

Right answer – everybody in the team, not just QAs. Developers are responsible for the code to run correctly always BEFORE committing.

Live demo

 What unit testing is not?

Unit Testing is NOT Test Driven Development (TDD)

and

Test Driven Development is NOT only Unit Testing

TDD, Test-first development and Test-driven-design are different concepts that rely on Unit Testing.

But they are not the same and should not be confused! We will not cover TDD here.

TDD is a very advanced practice and skill 

Even if looks relatively simple process to you

 Why not to do it?

Reasons not to write unit tests

"Writing tests is too hard."

"Testing is not my job."

"My code is too simple for tests."

"I don't have enough time to write tests."

"I don't know how to write tests."

Most companies that have lengthy software development lifecycle adopt some version of unit testing process.

 Testing Pyramid

 Benefits

  • For readability / more than just documentation – shows the intended practical usage of the method/class
  • To reduce cost of change and maintenance
  • To find bugs easier - does your code work correctly
  • To improve Code Design
     
  • But it should be done the right way – we’ll later give you an idea about it and we’ll learn more and more about unit testing throughout the whole rest of the course, not just in today’s session

 

 Diagrams

Unit Testing Overview

 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 - Example

int Sum(int[] array)
{
    int sum = 0;
    for (int i = 0; i < array.Length; i++)
        sum += array[i];
    return sum;
}

void Sum_Should()
{
 if (Sum(new int[]{ 1, 2 }) != 3)
    throw new TestFailedException("1 + 2 != 3");
 if (Sum(new int[]{ -2 }) != -2)
    throw new TestFailedException("-2 != -2");
 if (Sum(new int[]{ }) != 0)
    throw new TestFailedException("0 != 0");
}

 Unit Testing

  • 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
    • (TFS / SVN / Git) along with the code they test
  • Unit testing framework is needed
    • Visual Studio Team Test (VSTT)
    • NUnit, MbUnit, Gallio, etc.

 Unit Testing

  • All classes should be tested
  • All methods should be tested
    • Trivial code may be omitted
      • E.g. property getters and setters
    • Private methods can be omitted
      • Some gurus recommend to never test private methods → this can be debatable
  • Ideally, all unit tests should pass before check-in into the source control repository

 Unit Testing - Why bother do it?

  • Dramatically decreased number of defects in the code*
     
  • Improved code design
    • Helps introduce SRP
    • Helps introduce loose coupling
       
  • Good documentation
    • Code result expectations
    • Self-documenting code
       
  • Reduced cost of change
    • Easier, more efficient refactoring

 Unit Testing - What makes one good?

  • A good unit test should:
    • runs quickly
    • be automated and repeatable
    • be easy to implement
    • be relevant tomorrow
    • be consistent in its results
    • have full control of the unit under test
    • be fully isolated

 Unit Testing - What makes one good?

  • When it fails, it should be easy to detect what was expected and determine how to pinpoint the problem.
     
  • Anyone should be able to run it with the push of a button.

Unit Testing Basics

(examples are with MSTest syntax)

 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

 Assertions

  • Predicate is a true / false statement
  • Assertion is a predicate placed in a program code (check for some condition)
    • Developers expect the predicate is always true at that place
    • If an assertion fails, the method call does not return and an error is reported
    • Example of assertion:
Assert.AreEqual(expectedValue, actualValue, "Error message.");

 Assertions examples

  • Assertions check a condition
    • Throw exception if the condition is not satisfied
  • Comparing values for equality
Assert.AreEqual(expected_value, actual_value [,message])
  • Comparing objects (by reference)
Assert.AreSame(expected_object, actual_object [,message])

 Assertions examples

  • Checking conditions
Assert.IsTrue(condition)
Assert.IsFalse(condition)
  • Forced test failure
Assert.Fail(message)
  • Checking for null value
Assert.IsNull(object [,message])
Assert.IsNotNull(object [,message])
  • And many more

 Code Coverage

  • Code coverage
    • Shows what percent of the code we’ve covered
    • High code coverage means less untested code
    • We may have pointless unit tests that are calculated in the code coverage
  • 70-80% coverage is a good start
  • You should try to reach over 90%

 Example

public class Account
{
  private decimal balance;

  public void Deposit(decimal amount)
  {
    this.balance += amount;
  }

  public void Withdraw(decimal amount)
  {
    this.balance -= amount;
  }

  public void TransferFunds_Should_Condition(
    Account destination, decimal amount) { … }

  public decimal Balance { … }
}

 The 3A Pattern

  • Arrange all necessary preconditions and inputs
  • Act on the object or method under test
  • Assert that the expected results have occurred
[TestMethod]
public void TestDeposit() // TODO: Improve test name
{
  // Arrange
  BankAccount account = new BankAccount();

  // Act
  account.Deposit(125.0);

  // Assert
  Assert.AreEqual(125.0, account.Balance, "Balance is wrong.");
}

 Isolation

  • In order to make a true unit test you must run it in isolated environment
    • Ex: If you want to test if the fuel pump of an engine works, you don't need the engine itself, neither the car
    • ​You should make a fake object of the engine and the car and run the test in order to verify that the fuel pump works
  • There are techniques we will discuss later
    • ​You can do it manually
    • Or with a framework

 Naming Conventions

  • MethodName_StateUnderTest_ExpectedBehavior
    • IsAdult_AgeLessThan18_False
       
  • MethodName_ExpectedBehavior_StateUnderTest
    • IsAdult_False_AgeLessThan18
       
  • Should_ExpectedBehavior_When_StateUnderTest
    • Should_ThrowException_When_AgeLessThan18
       
  • When_StateUnderTest_Expect_ExpectedBehavior
    • When_AgeLessThan18_Expect_isAdultAsFalse

Testing Frameworks

MSTest

 MSTest

  • MSTest 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
  • Had some limitations in the past but it is very well structured and the new version is fully featured

 MSTest - Example

using Microsoft.VisualStudio.TestTools.UnitTesting;

[TestClass]
public class AccountShould
{
  [TestMethod]
  public void TransferFunds_ShouldUpdateBothAccountBalances_WhenInvoked()
  {
    // Arrange
    Account source = new Account(200.00M);
    Account dest = new Account(150.00M);

    // Act
    source.TransferFunds(dest, 100.00M);

    // Assert
    Assert.AreEqual(100.00M, source.Balance);
    Assert.AreEqual(250.00M, dest.Balance);
  }
}

NUnit

 NUnit

  • Test code is annotated using custom attributes
  • Test code contains assertions
  • Tests organized as multiple assemblies
  • NUnit provides:
    • Creating and running tests as NUnit Test Projects
    • Visual Studio support

 NUnit

  • Two standalone execution interfaces
    • GUI - nunit-gui.exe
    • Console - nunit-console.exe
  • ​Integrates with Visual Studio with test adapter (Nuget packages)

 NUnit - Example

using NUnit.Framework;

[TestFixture]
public class AccountTest
{
  [Test]
  public void TransferFunds_ShouldUpdateBothAccountBalances_WhenInvoked()
  {
    // Arrange
    Account source = new Account(200.00M);
    Account dest = new Account(150.00M);

    // Act
    source.TransferFunds(dest, 100.00M);

    // Assert
    Assert.AreEqual(100.00M, source.Balance);
    Assert.AreEqual(250.00M, dest.Balance);
  }
}

Audience Choice

 Choose a naming convention

  • MethodName_StateUnderTest_ExpectedBehavior
  • MethodName_ExpectedBehavior_StateUnderTest
  • Should_ExpectedBehavior_When_StateUnderTest
  • When_StateUnderTest_Expect_ExpectedBehavior

 

  • Do we really need to choose only one?

 

  • Answer: YES. Conventions are hard to adopt but once achieved it brings us a lot of benefits.

 Choose a testing framework

  • MSTest
    • or
  • NUnit
  • Later we will choose isolation framework too

Best Practices

 Volunteer?

  • We need a volunteer to write a simple unit test
public string ReverseString(string text)
{
    StringBuilder sBuilder = new StringBuilder();
    for (int i = text.Length - 1; i >= 0; i--)
    {
        sBuilder.Append(text[i]);
    }

    return sBuilder.ToString();
}
  • What case we do not cover here

 When should a test be removed or changed?

  • 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
[Test]
void Sum_ShouldThrow_WhenFirstPassedNumberIsNegative()
{
  Assert.ShouldThrow(() => Sum(-1, 1, 2));
}

 When should a test be removed or changed?

  • New features allow negative numbers
  • New developer writes the following test:
  • Earlier test fails due to a requirement change
  • Two course of actions:
    • Delete the failing test after verifying it is invalid
    • Change the old test:
      • Either testing the new requirement
      • Or test the older requirement under new settings

 Test should reflect reality

  • Consider the following method:
int Sum(int a, int b) -> return sum of a and b
  • What’s wrong with the following test?
public void Sum_AddsOneAndTwo()
{
    int result = Sum(1, 2);
    Assert.AreEqual(4, result, "Bad sum");
}
  • A failing test should prove that there is something wrong with the production code, not with the unit test code

 Assert messages

  • Assert message in a test could be 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."

 Assert messages

  • 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

 Assert messages

  • 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
void Sum_Test()
{
    Assert.AreEqual(3, Sum(1001, 1, 2));
    Assert.AreEqual(3, Sum(1, 1001, 2));
    Assert.AreEqual(3, Sum(1, 2, 1001));
}

Additional Resources

 Resources

Questions?

[C# UT] Unit Testing

By telerikacademy

[C# UT] Unit Testing

  • 1,145