Tuesday, May 12, 2020

Unit Tests with Moq - 1

Unit Tests

Why write automated tests?
  • Help to find defects and regressions
  • Free to run as often as required
  • Run at any time, on-demand or scheduled
  • Quicker to execute than manual testing
  • Find errors sooner
  • Generally reliable
  • Test code sites with production code
  • Happier development teams

Different test types

Depth – how details the tests can potentially get
Breadth – how broad the test is or how many difference sub systems of the application we are developing, the test can potentially cover
Speed – Tests execution speed
Brittle – how easy the test can be break or require refactoring



Unit Tests

May test a single class or unit of functionally cohesive classes. Unit test offers a best depth of testing and we can get details behavior of a class and check it all working as expected. But unit test doesn’t have broad reach, so unit test wouldn’t cover the whole system stack, right from the user interface down to the database
An “unit test” test a class or multiple classes without their external dependencies. They test unit of work.Unit test are cheap to write and execute fast. In this way, we can test each building block working as expected. However, this is not testing with external dependencies you can’t get lot of confidence in the reliability of your application. That the way Integration Test coming to rescue.


Integration Tests

Potential less in depth than Unit tests, but they are cover greater breadth of the system. Integration test might test sub-systems working together and potentially external resources such as database or the file systems
An “integration test” test a class or multiple classes with their external dependencies. 
This tests integration of your application code with these concrete dependencies like files, databases, so on. These tests take longer to execute, because they often reading and writing to external dependencies. It gives more confidence in the health of your application.


Subcutaneous Tests

Operate just under the User interface of the application. This means that this can cover potentially great breadth of the system we are developing but on in depth as Unit test
This is particularly valuable when doing functional testing of an application: when you want to test end-to-end behavior, but it's difficult to test through the UI itself. 


User Interface Tests

Can cover greatest breadth of the application.




The logical phases of an automated test







Asserts

Evaluate and verity the outcome of a test. Based on a returned result, final object state, or the occurrence of events observed during execution. An assert can either pass or fail. If all asserts pass, the test passes; if any assert fails the test fails.


How many asserts per test?
We can have more than one Assert in an individual test method as long as those asserts are testing the same behavior.



Type of xUnit Asserts: 

 

 xUnit Asserts types

 Example

Boolean

Assert.True(actualValue)

Assert.True(sut.Value)

String

 

Assert.Equal(“expected”, “actual”)

Assert.Equal(“abc”, “sut.Value”)

Assert.Equal(“expected”, “actual”, ignoreCase: true)

Assert.Equal(“ABC”, “sut.Value”, ignoreCase: true)

Assert.StartWith(“expected”, “actual”)

Assert.StartWith(“abc”, “sut.Value”)

Assert.EndWith(“expected”, “actual”)

Assert.EndWith(“abc”, “sut.Value”)

Assert.Matches(“expectedRegExp”, “actual”)

Assert.Matches(“[A-Z]”, “sut.Value”)

Number

Assert.Equal(expected, actual)

Assert.Equal(123, sut.Value)

Assert.NotEqual(expected, actual)

Assert.NotEqual(123, sut.Value)

Assert.True(numberComparison)

Assert.True(sut.Value >= 123 && sut.Value <= 321)

Assert.InRange(accrual, low, high)

Assert.InRange(sut.Value, 123, 321)

Assert.Equal(expected, actual, precision)

Assert.Equal(123.667, sut.Value, 3)

Null

Assert.Null(actual) 

Assert.Null(sut.Value) 

Assert.NotNull(actual)

Assert.NotNull(sut.Value)

Collection

Assert.Contains(expectedElement, actualCollection) 

Assert.Contains(“a”, sut. Collection) 

Assert.DoesNotContain(expectedElement, actualCollection) 

Assert.DoesNotContain(“a”, sut. Collection) 

Assert.Contains(actualCollection, expectedPredicate) 

Assert.Contains(sut.Collection, collection=> collection.Contains(“abc”) 

Assert.Contains(expectedCollection, actualCollection)

Assert.Contains(collection, sut.Collection)

Assert against each item in the collection, “All” will loop through the collection

Assert.All(sut.Collection, s => Assert.False(s))

Object

Check the type of object

Assert.IsType<ClassA>(obj)

Check the type of object is not

Assert.IsNotType<DateTime>(obj)

Check the type for inherited class

Assert.IsAssignableFrom<ClassA>(obj)

Object instances

Check object references are same

Assert.Same(obje1, obj2)

Check object references are not same

Assert.NotSame(obje1, obj2)

Throws Exception

Assert.Throws<expectedException, callingMethod)

Assert.Throws<ArgumentNullException>(() => sut.Create(null))

Particular argument causes the error and throws exception

Assert.Throws< ArgumentNullException>(“name”, () => sut.Create(null)

Event Raised

Asserting that events are raised

Assert.Raises<EventArgs>(handler => sut.Event += handler, handler => sut.Event -= handler, () => sut.Method());

Asserting Property change event

Assert.PropertyChanged(sut, “Health”, () => sut.TakeDamage(10))




Controlling Test Execution

Categorizing and Running sub tests

Set category method level:
public class BossEnemyShould
{
[Fact]
[Trait("Category", "Boss")]
public void HaveCorrectPower()
{
BossEnemy sut = new BossEnemy();
Assert.Equal(166.667, sut.SpecialAttackPower, 3);
}
}
Set category class level:
    [Trait("Category", "Enemy")]
    public class EnemyFactoryShould
    {
        [Fact]        
        public void CreateNormalEnemyByDefault()
        {
   
Search:
Trait:”Enemy”

Run all tests from dotnet core:
>dotnet test
Run tests for specific Trait from dotnet core:
>dotnet test –filter TraitName=TraitValue
>dotnet test --filter Category=Enemy
>dotnet test --filter “Category=Boss|Category=Enemy”

Skip test
[Fact(Skip = "Don't need to run this")] 
public void CreateNormalEnemyByDefault()
{

xUnit Setup and Dispose

In xUnit we can do setup in constructor, constructor will execute before each test case run. So, we can create any initialization on the constructor
Also, we can implement the IDisposable interface to dispose any resources. Dispose method will execute after each test case run.
public class PlayerCharacterShould : IDisposable
    {
        private readonly PlayerCharacter _sut;
        private readonly ITestOutputHelper _output;
        public PlayerCharacterShould(ITestOutputHelper output)
        {
            _output = output;
            _output.WriteLine("Creating new PlayerCharacter");
            _sut = new PlayerCharacter();
        }
        public void Dispose()
        {
            _output.WriteLine($"Disposing PlayerCharacter {_sut.FullName}");
            _sut.Dispose();
        }
        [Fact]
        public void BeInexperiencedWhenNew()
        {

Share Context between Tests methods in a test class

Create once and share the instance among all the test methods in the test class. For this we will create a new class and implement this class in the test class using IClassFiture
   public class GameStateFixture : IDisposable
    {
        public GameState State { get; private set; }
        public GameStateFixture()
        {
            State = new GameState();
        }
        public void Dispose()
        {
            // Cleanup
        }
    }

public class GameStateShould : IClassFixture<GameStateFixture>
    {
        private readonly GameStateFixture _gameStateFixture;
        private readonly ITestOutputHelper _output;
        public GameStateShould(GameStateFixture gameStateFixture,
                               ITestOutputHelper output)
        {
            _gameStateFixture = gameStateFixture;
            _output = output;
        }
        [Fact]
        public void DamageAllPlayersWhenEarthquake()
        {
            _output.WriteLine($"GameState ID={_gameStateFixture.State.Id}");
            var player1 = new PlayerCharacter();
            var player2 = new PlayerCharacter();
            _gameStateFixture.State.Players.Add(player1);

Share Context across Test Classes

If we want to share a context across multiple test classes. Doesn’t have any implementation in collection class, use to create the collection definition
    [CollectionDefinition("GameState collection")]
public class GameStateCollection : ICollectionFixture<GameStateFixture> {}
    [Collection("GameState collection")]
    public class TestClass1
    {
        private readonly GameStateFixture _gameStateFixture;
        private readonly ITestOutputHelper _output;
        public TestClass1(GameStateFixture gameStateFixture, ITestOutputHelper output)
        {
            _gameStateFixture = gameStateFixture;
            _output = output;
        }
        [Fact]
        public void Test1()
        {
So, from above configuration, before any of tests executes, xUnit going to create an instance of fixture class and supply same instance to test classes which have collection attribute.


Data-driven Tests


Will be use [Theory] attribute to mark the test going to be use multiple times
Pass parameters to test method to use inside method
Then use one on Test Data Source to pass values for parameters

Test Data Sources
  • Inline attribute
  • Property / field / method
  • Custom attribute
  • External data
If we want to share test data across Tests, we can use above Test data sources except Inline attribute. Inline attribute can’t share data amount test methods, it specific to the method it defines. Also, Except External data other three methods had to involve a developer and need to do code changes to add additional test data. But in the External data can be edit by non developer save in external file

Inline attribute
        [Theory]
        [InlineData(0, 100)]
        [InlineData(1, 99)]
        [InlineData(50, 50)]
        public void TakeDamage(int damage, int expectedHealth)
        {

Property / field / method

First save test data in a separate class and write a get method to get all test data
public class InternalHealthDamageTestData
    {
        //private static readonly List<object[]> Data = new List<object[]>
        //     {
        //            new object[] {0, 100},
        //            new object[] {1, 99},
        //            new object[] {50, 50},
        //            new object[] {101, 1}
        //     };
        //public static IEnumerable<object[]> TestData => Data;
        public static IEnumerable<object[]> TestData
        {
            get
            {
                yield return new object[] { 0, 100 };
                yield return new object[] { 1, 99 };
                yield return new object[] { 50, 50 };
                yield return new object[] { 75, 25 };
                yield return new object[] { 101, 1 };
            }
        }
    }
Add [MemberData] attribute to pass the test data to any test method
        [Theory]
        [MemberData(nameof(InternalHealthDamageTestData.TestData), 
            MemberType = typeof(InternalHealthDamageTestData))]
        public void TakeDamage(int damage, int expectedHealth)
        {
            NonPlayerCharacter sut = new NonPlayerCharacter();
            sut.TakeDamage(damage);
            Assert.Equal(expectedHealth, sut.Health);
        }

Getting Test Data from External file
Add a csv file to solution and in the properties change the “Copy to Output Directory” to “Copy always”
public class ExternalHealthDamageTestData
    {
        public static IEnumerable<object[]> TestData
        {
            get
            {
                string[] csvLines = File.ReadAllLines("TestData.csv");
                var testCases = new List<object[]>();
                foreach (var csvLine in csvLines)
                {
                    IEnumerable<int> values = csvLine.Split(',').Select(int.Parse);
                    object[] testCase = values.Cast<object>().ToArray();
                    testCases.Add(testCase);
                }
                return testCases;
            }
        }
    }
        [Theory]
        [MemberData(nameof(ExternalHealthDamageTestData.TestData), 
            MemberType = typeof(ExternalHealthDamageTestData))]
        public void TakeDamage(int damage, int expectedHealth)
        {
            NonPlayerCharacter sut = new NonPlayerCharacter();
            sut.TakeDamage(damage);
            Assert.Equal(expectedHealth, sut.Health);
        }

Create Custom Attribute as the Data Source
        [Theory]
        [HealthDamageData]
        public void TakeDamage(int damage, int expectedHealth)
        {
            NonPlayerCharacter sut = new NonPlayerCharacter();
            sut.TakeDamage(damage);
            Assert.Equal(expectedHealth, sut.Health);
        }
Method 1:
    public class HealthDamageDataAttribute : DataAttribute
    {
        public override IEnumerable<object[]> GetData(MethodInfo testMethod)
        {
            yield return new object[] { 0, 100 };
            yield return new object[] { 1, 99 };
            yield return new object[] { 50, 50 };
            yield return new object[] { 75, 25 };
            yield return new object[] { 101, 1 };
        }
    }


Method 2:
    public class HealthDamageDataAttribute : DataAttribute
    {
        public override IEnumerable<object[]> GetData(MethodInfo testMethod)
        {
            string[] csvLines = File.ReadAllLines("TestData.csv");
            var testCases = new List<object[]>();
            foreach (var csvLine in csvLines)
            {
                IEnumerable<int> values = csvLine.Split(',').Select(int.Parse);
                object[] testCase = values.Cast<object>().ToArray();
                testCases.Add(testCase);
            }
            return testCases;
        }
    }

Continue reading: Unit Tests with Moq - 2

Share This Post →

No comments:

Post a Comment

Powered By Blogger |   Design By Seo Blogger Templates Published.. Blogger Templates
DMCA.com