Sunday, 24 January 2016

Unit Test (#2) : Common case in testing

We have written and run a simple test by xUnit and Fluent Assertions. Now let’s continue with more difficult cases in testing : exception, interface, asynchronous...

We ’ll take an example which covers all of them.

Create a simple business

Suppose that we have a class called MyArray whose feature is perform basic operations on an array: sort, search, get-third-element and count-elements.

In Solution Explorer, create these following classes :
-   MyArray.cs
-   ISearchMethod.cs
-   LinearSearch.cs
-   BinarySearch.cs

MyArray class contains 1 array and 5 functions:
-   void sort() – sort the array in acending order.
-   void _swap (int, int) – private function which swap two elements in the array.
-   int search (int, ISearchMethod) – search a value in array with different methods.
-   int getThirdElement() – get the third element in array.
-   Task <int> count() – asynchronous function counting the number of elements in the array.

ISearchMethod is the interface for search function

LinearSearch implements Linear Search method

BinarySearch implements Binary Search method

Copy the code below:

MyArray.cs
namespace MyFirstTest
{
    class MyArray
    {
        private int[] numbers;

        public MyArray(int[] arr)
        {
            numbers = arr;
        }

        // sorting
        public void sort()
        {
            for (int i = 0; i < numbers.Length - 1; i++)
            {
                for (int j = i + 1; j < numbers.Length; j++)
                {
                    if (numbers[j] < numbers[i])
                        _swap(i, j);
                }
            }
        }

        private void _swap(int i1, int i2)
        {
            int tmp = numbers[i1];
            numbers[i1] = numbers[i2];
            numbers[i2] = tmp;
        }

        // searching
        public int search(int value, ISearchMethod searchMethod)
        {
            return searchMethod.search(value, numbers);
        }

        // get 3rd element
        public int getThirdElement()
        {
            return numbers[2];
        }

        // count array length
        public async Task<int> count()
        {
            if (numbers != null)
            {
                await Task.Run(() =>
                {
                    Thread.Sleep(3000);
                });
                return numbers.Length;
            }
            else
                throw new NullReferenceException();
        }
    }
}
ISearchMethod.cs
namespace MyFirstTest
{
    interface ISearchMethod
    {
        int search(int value, int[] arr);
    }
}
LinearSearch.cs
namespace MyFirstTest
{
    class LinearSearch : ISearchMethod
    {
        public int search(int value, int[] arr)
        {
            for (int i = 0; i < arr.Length; i++)
            {
                if (arr[i] == value)
                    return i;
            }
            return -1;
        }
    }
}
BinarySearch.cs
namespace MyFirstTest
{
    class BinarySearch : ISearchMethod
    {
        public int search(int value, int[] arr)
        {
            int start = 0, end = arr.Length - 1;
            while (start != end)
            {
                int mid = start + (end - start) / 2;
                if (value == arr[mid])
                    return mid;
                else if (value > arr[mid])
                    start = mid + 1;
                else
                    end = mid - 1;
            }
            if (value == arr[start])
                return start;
            return -1;
        }
    }
}

Test MyArray class

Ok now in the Solution Explorer, create a new class named MyArrayTest.cs to test MyArray.

MyArrayTest.cs
using Xunit;
using FluentAssertions;
namespace MyFirstTest
{
    // Test class for MyArray
    // A class for testing must be "public"
    public class MyArrayTest
    {
        // test sort
        [Theory]
        [InlineData(new int[] { 1, 7, 5, 2 }, new int[] { 1, 2, 5, 7 })]
        [InlineData(new int[] { 1, 5, 9, 3 }, new int[] { 1, 9, 3, 5 })]
        void testSort(int[] input, int[] target)
        {
            MyArray arr = new MyArray(input);
            arr.sort();
            input.Should().Equal(target);
        }

        // test search with different approaches
        [Theory]
        [InlineData(new int[] { 1, 5, 7, 2, 8 }, typeof(LinearSearch), 2, 1)]
        [InlineData(new int[] { 1, 5, 7, 2, 8 }, typeof(BinarySearch), 2, 5)]
        void testSearch(int[] input, Type type, int value, int target)
        {
            MyArray arr = new MyArray(input);
            arr.sort();
            var searchMethod = Activator.CreateInstance(type) as ISearchMethod;
            int index = arr.search(value, searchMethod);
            index.Should().Be(target);
        }

        // test get-3rd-element, a case yields unknown exception
        [Theory]
        [InlineData(new int[] { 1, 2, 5 }, 5)]
        [InlineData(new int[] { 1, 3, 7, 9 }, 5)]
        [InlineData(new int[] { 1, 3 }, 5)] // unknown exception
        void testGetThirdElement(int[] input, int target)
        {
            MyArray arr = new MyArray(input);
            arr.getThirdElement().Should().Be(target);
        }

        // test get-3rd-element with expected exceptions
        [Theory]
        [InlineData(new int[] { 1, 2 }, "OutOfIndex")]
        [InlineData(null, "EmptyArray")]
        void etestGetThirdElement(int[] input, string exCase)
        {
            MyArray arr = new MyArray(input);
            Action act = () => { arr.getThirdElement(); };
            if (exCase == "OutOfIndex")
                act.ShouldThrow<IndexOutOfRangeException>();
            else if (exCase == "EmptyArray")
                act.ShouldThrow<NullReferenceException>();
        }

        // test count - an asynchronous function
        [Theory]
        [InlineData(new int[] { 1, 2 }, 2)]
        [InlineData(new int[] { 1, 2 }, 5)]
        void testCount(int[] input, int target)
        {
            MyArray arr = new MyArray(input);
            Func<Task> act = async () =>
            {
                int result = await arr.count();
                result.Should().Be(target);
            };
            act.ShouldNotThrow(); // must be included to wait the action
        }

        [Fact]
        void etestCount() // null exception
        {
            MyArray arr = new MyArray(null);
            Func<Task> act = async () => await arr.count();
            act.ShouldThrow<NullReferenceException>();
        }
    }
}
Everything is done ! We can run the test now. On the Menu > click Test > Run > All Tests. After run all tests, the result would be like this :

The code is straightforward and easy to understand. So explore it by yourself ! Next time, we will talk about the test case problem when a test case contains complex parameters. Join me there.

No comments :

Post a Comment