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. …”
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
- Trivial code may be omitted
- 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
-
IsAdult_AgeLessThan18_False
- MethodName_ExpectedBehavior_StateUnderTest
-
IsAdult_False_AgeLessThan18
-
IsAdult_False_AgeLessThan18
- Should_ExpectedBehavior_When_StateUnderTest
-
Should_ThrowException_When_AgeLessThan18
-
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,226