Thursday, 7 July 2016

[Visual Studio] Write Unit Test to Test your Application in C# & VB [Part 2]

This post is the second part and continuation from the first part here http://jaryl-lan.blogspot.com/2016/07/visual-studio-write-unit-test-part-1.html. If you are already familiar with unit test, you may skip the first part. This post will focus more on how to test your system in different scenario for the same testable methods and some other stuff you can do with unit test.

(Main): [Fake Data]
Most applications will have some form of storage to keep information. It can be from database, file and so on. I believe there are many ways to make fake data for unit test, but in this case we will look into creating and initializing fake database (SQL Server) and data respectively.

In unit test, there is 1 attribute called "ClassInitialize" that can be used for the unit test to execute before executing methods that are decorated with "TestMethod". So we will make used of this to create and initialize fake database with data. You can write the SQL statement into a script file or write them in the resource file (.resx). Then with the SQL statement, you can use EnterpriseLibrary or SqlCommand to execute the statement. 

In this example, the method decorated with ClassInitialize will execute the following 2 methods. "InitializeTestDatabase" method will firstly drop existing database and recreate a new database. "InitializeData" method will initialize data into the newly created database.

[C#]
private static void InitializeTestDatabase(bool createDatabase = true)
{
    bool isDatabaseExist = false;

    using (var connection = new SqlConnection(ConfigurationManager.ConnectionStrings["master"].ConnectionString))
    {
        connection.Open();

        using (var command = new SqlCommand("SELECT name FROM master.sys.databases WHERE name = @Name ", connection))
        {
            command.Parameters.Add(new SqlParameter("@Name", "UTDemoTest"));
            using (var reader = command.ExecuteReader())
                isDatabaseExist = reader.Read();
        }

        if (isDatabaseExist)
            using (var command = new SqlCommand("ALTER DATABASE UTDemoTest SET SINGLE_USER WITH ROLLBACK IMMEDIATE; DROP DATABASE UTDemoTest;", connection))
                command.ExecuteNonQuery();

        if (createDatabase)
            using (var command = new SqlCommand("CREATE DATABASE UTDemoTest", connection))
                command.ExecuteNonQuery();
    }
}

private static void InitializeData()
{
    using (var connection = new SqlConnection(ConfigurationManager.ConnectionStrings["default"].ConnectionString))
    {
        connection.Open();

        using (var command = new SqlCommand(Properties.Resources.InitializeTestDatabase, connection))
            command.ExecuteNonQuery();
    }
}

[VB]
Private Shared Sub InitializeTestDatabase(Optional ByVal createDatabase As Boolean = True)
    Dim isDatabaseExist As Boolean = False

    Using connection As SqlConnection = New SqlConnection(ConfigurationManager.ConnectionStrings("master").ConnectionString)
        connection.Open()

        Using command As SqlCommand = New SqlCommand("SELECT name FROM master.sys.databases WHERE name = @Name ", connection)
            command.Parameters.Add(New SqlParameter("@Name", "UTDemoTest"))

            Using reader As SqlDataReader = command.ExecuteReader()
                isDatabaseExist = reader.Read()
            End Using
        End Using

        If isDatabaseExist Then
            Using command As SqlCommand = New SqlCommand("ALTER DATABASE UTDemoTest SET SINGLE_USER WITH ROLLBACK IMMEDIATE; DROP DATABASE UTDemoTest;", connection)
                command.ExecuteNonQuery()
            End Using
        End If

        If createDatabase Then
            Using command As SqlCommand = New SqlCommand("CREATE DATABASE UTDemoTest", connection)
                command.ExecuteNonQuery()
            End Using
        End If
    End Using
End Sub

Private Shared Sub InitializeData()
    Using connection As SqlConnection = New SqlConnection(ConfigurationManager.ConnectionStrings("default").ConnectionString)
        connection.Open()

        Using command As SqlCommand = New SqlCommand(My.Resources.InitializeTestDatabase, connection)
            command.ExecuteNonQuery()
        End Using
    End Using
End Sub


(Main): [Simulating Different & Failed Scenario]
Assuming that you have a system for customer to browse for products and redeem a product with points. There will be all kinds of checks and validation to be done before the customer successfully redeemed a product. In this kind of situation, you will need to write a few test method to cater for different scenario.

(Sub): [Test Method with ExpectedException Attribute]
You can create a test method and decorate it with ExpectedException and pass in an exception type. In the following example, the "Redeem" method is expecting a userId and productId. So assuming the user does not have sufficient points to redeem the product and I'm expecting the system to throw the exception type "ApplicationException".

[C#]
[TestMethod, ExpectedException(typeof(ApplicationException))]
public void InsufficientCreditExceptionTest()
{
    var component = new RedemptionComponent();
    component.Redeem(1, 1);
}

[VB]
<TestMethod(), ExpectedException(GetType(ApplicationException))>
Public Sub InsufficientCreditExceptionTest()
    Dim component As RedemptionComponent = New RedemptionComponent()
    component.Redeem(1, 1)
End Sub


(Sub): [Simulating Different Scenario]
There are times when the system need to cater for situation where the product is having discount for points redemption. With the discounted points, customer's remaining points gets subtracted lesser. So to cater for this kind of scenario, you may consider to fake the changes before the test and revert the changes after the test. Depending on your system, the changes can be from database, file, etc.

In this example, scenario 1 is to test the product redemption without discount, whereas Scenario 2 is to test the product redemption with discount. The discount percentage is controlled through configuration file. So in scenario 2, a changes is made to configuration before the test begin and revert the changes after the test is completed.

[C#] - [Scenario 1]
[TestMethod]
public void RedeemItemTest()
{
    const string NAME = "Bern";
    const long PRODUCT_ID = 1;

    // Retrieve user's current available point.
    var currentCredit = RetrieveAvailablePoint(NAME);

    var component = new RedemptionComponent();

    // Firstly verify whether Bern is a valid member.
    var user = component.VerifyUser(NAME);

    Assert.IsNotNull(user, "User does not exist");

    // Redeem item and substract point from Bern's membership account.
    component.Redeem(user.UserID, PRODUCT_ID);

    // Retreive user's current available point after deducted from redeeming product.
    var deductedCredit = RetrieveAvailablePoint(NAME);

    // Retrieve product's point requirement.
    var productPoint = RetrieveProductRequiredPoint(PRODUCT_ID);

    Assert.AreEqual(deductedCredit, (currentCredit - productPoint), "User's point did not deducted correctly.");
}

[C#] - [Scenario 2]
[TestMethod]
public void RedeemItemWithDiscountTest()
{
    const int DISCOUNT_VALUE = 20;
    var config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
    var defaultDiscountValue = config.AppSettings.Settings["discountPercentage"].Value;

    // Discount 20 percent.
    config.AppSettings.Settings["discountPercentage"].Value = DISCOUNT_VALUE.ToString();
    config.Save(ConfigurationSaveMode.Modified);
    ConfigurationManager.RefreshSection("appSettings");

    try
    {
        const string NAME = "Verl";
        const long PRODUCT_ID = 1;

        // Retrieve user's current available point.
        var currentCredit = RetrieveAvailablePoint(NAME);

        var component = new RedemptionComponent();

        // Firstly verify whether Verl is a valid member.
        var user = component.VerifyUser(NAME);

        Assert.IsNotNull(user, "User does not exist.");

        // Redeem item and substract point from Verl's membership account.
        component.Redeem(user.UserID, PRODUCT_ID);

        // Retreive user's current available point after deducted from redeeming product.
        var deductedCredit = RetrieveAvailablePoint(NAME);

        // Retrieve product's point requirement.
        var productPoint = RetrieveProductRequiredPoint(PRODUCT_ID);

        var discountedPoint = productPoint * (100 - DISCOUNT_VALUE) / 100;

        Assert.AreEqual(deductedCredit, (currentCredit - discountedPoint), "User's point did not deducted correctly.");
    }
    finally
    {
        config.AppSettings.Settings["discountPercentage"].Value = defaultDiscountValue;
        config.Save(ConfigurationSaveMode.Modified);
        ConfigurationManager.RefreshSection("appSettings");
    }
}

[VB] - [Scenario 1]
<TestMethod()>
Public Sub RedeemItemTest()
    Const NAME As String = "Bern"
    Const PRODUCT_ID As Long = 1

    ' Retrieve user's current available point.
    Dim currentCredit As Long = RetrieveAvailablePoint(NAME)

    Dim component = New RedemptionComponent()

    ' Firstly verify whether Bern is a valid member.
    Dim user As User = component.VerifyUser(NAME)

    Assert.IsNotNull(user, "User does not exist.")

    ' Redeem item and substract point from Bern's membership account.
    component.Redeem(user.UserID, PRODUCT_ID)

    ' Retreive user's current available point after deducted from redeeming product.
    Dim deductedCredit As Long = RetrieveAvailablePoint(NAME)

    ' Retrieve product's point requirement.
    Dim productPoint As Integer = RetrieveProductRequiredPoint(PRODUCT_ID)

    Assert.AreEqual(deductedCredit, (currentCredit - productPoint), "User's point did not deducted correctly.")
End Sub

[VB] - [Scenario 2]
<TestMethod()>
Public Sub RedeemItemWithDiscountTest()
    Const DISCOUNT_VALUE As Integer = 20
    Dim config As Configuration = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None)
    Dim defaultDiscountValue As String = config.AppSettings.Settings("discountPercentage").Value

    ' Discount 20 percent.
    config.AppSettings.Settings("discountPercentage").Value = DISCOUNT_VALUE.ToString()
    config.Save(ConfigurationSaveMode.Modified)
    ConfigurationManager.RefreshSection("appSettings")

    Try
        Const NAME As String = "Verl"
        Const PRODUCT_ID As Long = 1

        ' Retrieve user's current available point.
        Dim currentCredit As Long = RetrieveAvailablePoint(NAME)

        Dim component = New RedemptionComponent()

        ' Firstly verify whether Verl is a valid member.
        Dim user As User = component.VerifyUser(NAME)

        Assert.IsNotNull(user, "User does not exist.")

        ' Redeem item and substract point from Verl's membership account.
        component.Redeem(user.UserID, PRODUCT_ID)

        ' Retreive user's current available point after deducted from redeeming product.
        Dim deductedCredit As Long = RetrieveAvailablePoint(NAME)

        ' Retrieve product's point requirement.
        Dim productPoint As Integer = RetrieveProductRequiredPoint(PRODUCT_ID)

        Dim discountedPoint As Integer = productPoint * (100 - DISCOUNT_VALUE) / 100

        Assert.AreEqual(deductedCredit, (currentCredit - discountedPoint), "User's point did not deducted correctly.")
    Finally
        config.AppSettings.Settings("discountPercentage").Value = defaultDiscountValue
        config.Save(ConfigurationSaveMode.Modified)
        ConfigurationManager.RefreshSection("appSettings")
    End Try
End Sub


(Main): [Data-Driven Unit Test]
It can be quite messy when you wrote multiple test method to simulate multiple scenario. To solve this, consider implementing Data-Driven Unit Test. For more detail on how to create Data-Driven Unit Test with xml, refer here http://sylvester-lee.blogspot.my/2012/09/data-driven-unit-testing-with-xml.html.

In this example, the test method will test for different failed scenario when redeeming a product.

[C#]
[TestMethod, ExpectedException(typeof(ApplicationException))]
[DeploymentItem("\\RedeptionData.xml")]
[DataSource("Microsoft.VisualStudio.TestTools.DataSource.XML",
            "|DataDirectory|\\RedeptionData.xml",
            "Redeption",
            DataAccessMethod.Sequential)]
public void RedeemItemFailedTest()
{
    var component = new RedemptionComponent();
    var user = default(User);
    var needUserVerification = Convert.ToBoolean(TestContext.DataRow["VerifyUser"]);

    if (needUserVerification)
    {
        user = component.VerifyUser(TestContext.DataRow["Name"].ToString());
    }
    else
    {
        user = new User();
        user.UserID = 0;
    }
    component.Redeem(user.UserID, Convert.ToInt64(TestContext.DataRow["ProductId"]));
}

[VB]
<TestMethod(), ExpectedException(GetType(ApplicationException))>
<DeploymentItem("\RedeptionData.xml")>
<DataSource("Microsoft.VisualStudio.TestTools.DataSource.XML",
                "|DataDirectory|\RedeptionData.xml",
                "Redeption",
                DataAccessMethod.Sequential)>
Public Sub RedeemItemFailedTest()
    Dim component As RedemptionComponent = New RedemptionComponent()
    Dim user As User = Nothing
    Dim needUserVerification As Boolean = Convert.ToBoolean(TestContext.DataRow("VerifyUser"))

    If (needUserVerification) Then
        user = component.VerifyUser(TestContext.DataRow("Name").ToString())
    Else
        User = New User()
        User.UserID = 0
    End If

    component.Redeem(user.UserID, Convert.ToInt64(TestContext.DataRow("ProductId")))
End Sub


(Main): [Code Coverage]
Once you have written your unit tests, you might be wondering whether the codes that you have written is being executed and tested. With code coverage, you will get a summary of how much of your test methods cover the codes written for your system.

To get the code coverage for all test methods,
1) In case you don't see the "Test Explorer" windows, at the top menu in Visual Studio, click Test, hover to Windows and click Test Explorer. "Test Explorer" window will be shown in visual studio.
Visual Studio - TEST - Windows - Test Explorer
Open Test Explorer
2) In "Test Explorer" window, look for the triangle-bottom icon beside the "Run..." link button, click on it and click "Analyze Code Coverage for All Tests".
Visual Studio - Test Explorer Window - Analyze Code Coverage for All Tests
Analyse Code Coverage for All Tests
To get code coverage over services, check it out here http://jaryl-lan.blogspot.com/2016/06/visual-studio-get-unit-test-code.html.

The following link contain the demo project with samples for unit test.




[Visual Studio] Write Unit Test to Test your Application in C# & VB [Part 1]

This post is separated into 2 parts. If you are familiar with Unit Test, skip this post and proceed to second part. http://jaryl-lan.blogspot.com/2016/07/visual-studio-write-unit-test-part-2.html

Today's post is to guide you on how to write a unit test to test the functionality of your application. In case you are unsure what is unit test all about, you can check it out at msdn here https://msdn.microsoft.com/en-us/library/hh694602.aspx.

Before you can start writing a unit test method, you need to have a few functional methods. So as an example, we will write one simple dummy method. This method will treat the user "John" as an existing customer.

[C#]
public bool VerifyUser(string name)
{
    return name == "John";
}

[VB]
Public Function VerifyUser(ByVal name As String) As Boolean
    Return name = "John"
End Function

Once you have a testable methods, create a "unit test project" and add a new "test class" to your unit test project. You are required to add reference of the project that contain the testable methods.

Steps to create a unit test project:
1) On the Solution Explorer in your visual studio, right click on your solution, hover to "Add" and click "New Project...". A window named "Add New Project" will be prompted.
Add new Project
2) On the left pane under Installed, expand Visual C# or Visual Basic based on your desired programming language and click Test.

3) On the middle pane, click "Unit Test Project".

4) Fill in your desired name and location. Once you are done. Click "OK" button.
Add a new Unit Test Project
5) Back to Solution Explorer, right click on your newly created Unit Test project, hover to "Add" and click "New Item...". A window named "Add New Item - [Project Name]" will be prompted.
Add new Item
6) On the left pane under "Installed", expand until you see "Test", click on it.

7) On the middle pane, click "Unit Test".

8) Fill in your desired name and click "Add" button.
Add a new Unit Test Class
9) Back to Solution Explorer, right click on your Unit Test project, hover to "Add" and click "Reference...". A window named "Reference Manager - [Project Name]" will be prompted.
Add Reference
10) On the left pane, expand Solution and click Projects.

11) On the middle pane, tick the project that contain the testable methods and click "OK" button.
Select the desired reference to be added
Once you are done, open up the test class that you have added earlier. The different in this test class is the class is being decorated with "TestClass" attribute and every test method are decorated with "TestMethod" attribute. Both of these attributes are required for Unit Test and the "TestMethod" attribute is necessary for the test method to appear in "Test Explorer" window. Test method must not contain parameter and the return type must be void.

Assuming you want to test whether the user "John" is exist by executing the method named VerifyUser. So create a method and decorate it with "TestMethod" attribute. From this method, invoke the method VerifyUser and pass in the string value "John" as parameter value. Obtain the result from the method and verify it with Assert. The Assert.AreEqual is used to verify whether the result returned from VerifyUser matches the expected result. In this case, the expected result is true.

[C#]
[TestMethod]
public void IsJohnExistTest()
{
    var component = new RedemptionComponent();
    var result = component.VerifyUser("John");

    Assert.AreEqual(true, result, "User 'John' does not exist.");

}

[VB]
<TestMethod()>
Public Sub IsJohnExistTest()
    Dim component As RedemptionComponent = New RedemptionComponent()
    Dim result As User = component.VerifyUser("John")

    Assert.AreEqual(true, result, "User 'John' does not exist.")

End Sub

To run your test method, there are two ways.
[Option 1]
1) At the top menu in Visual Studio, click Test, hover to Windows and click Test Explorer. "Test Explorer" window will be shown in visual studio.
Visual Studio - TEST - Windows - Test Explorer
Open Test Explorer window
2) On the Test Explorer window, right click on the desired test method and click "Run Selected Tests". To debug the test, click "Debug Selected Tests". If the test method did not appear, Make sure that your solution is compiled.
Visual Studio - Test Explorer - Run Selected Tests
Run Selected Tests

[Option 2]
1) In your class file, locate the desired test method that you want to test.

2) Left click on the method and on keyboard, press CTRL+R and T. To debug the method, press "CTRL+R and CTRL+T".

Once the test method execution is completed, you will either see
1) A green tick icon if the test is successful, or
Visual Studio - Test Explorer - Successful Test
Successful Test
2) A red cross icon if the test failed.
Visual Studio - Test Explorer - Fail Test
Failed Test
This is how unit testing works. Simple and straightforward. But of course Unit Test are not limited to only this. If you want to find out more on what else can be done with Unit Test, check it out at the second part of this post. http://jaryl-lan.blogspot.com/2016/07/visual-studio-write-unit-test-part-2.html

Refer to the following for the sample project.
C#: https://1drv.ms/u/s!Aj2AA7hoIWHmgm-KRQQBuL3ZCCf7
VB: https://1drv.ms/u/s!Aj2AA7hoIWHmgm44Muju_EO1tGl9

Sunday, 3 July 2016

List of WCF Issues and How to Solve it

It is quite common for developers to develop application from scratch or to enhance existing project. But for developers who are required to implement WCF services, you may come across certain problems when trying to navigate or invoke the WCF service. The following are not a complete list of issues related to WCF, more will be added in the future.

Few things to take note:
1) The error messages will appear in Event Log when the WCF service is being invoked. It is located in Event Viewer > Windows Logs > Application.

2) The service and contract name defined here are just an example.


[Issue 1]
The type 'SA.Services.ComputerService', provided as the Service attribute value in the ServiceHost directive, or provided in the configuration element system.serviceModel/serviceHostingEnvironment/serviceActivations could not be found.

Note: "ComputerService" is your service / class name that are belongs to the namespace "SA.Services".

[How to solve]
What the error means is that it can't locate the WCF service that you have implemented. So do verify the following:
1) If your WCF service is implemented in another project, make sure that it is being referenced in your web application or self hosting WCF project, and

2) Double check and make sure the service name and namespace that you defined in configuration file are correct. The value should be define in the service attribute under system.serviceModel/serviceHostingEnvironment/serviceActivations/add element. Sample configuration is as follows.

<system.serviceModel>
  <serviceHostingEnvironment multipleSiteBindingsEnabled="true">
    <serviceActivations>
      <add factory="System.ServiceModel.Activation.ServiceHostFactory"
           relativeAddress="./ComputerService.svc"
           service="SA.Services.ComputerService" />
    </serviceActivations>
  </serviceHostingEnvironment>
</system.serviceModel>


[Issue 2]
The contract name 'SA.Services.Contracts.IComputerService' could not be found in the list of contracts implemented by the service 'ComputerService'. 

Note: "IComputerService" is your interface / contract name that are belongs to the namespace "SA.Services.Contracts"

[How to solve]
This error message is stating that your WCF service does not implement the interface / contract named "IComputerService". Verify the following:
1) Make sure that the WCF service that you implemented is implementing the contract that you defined in your configuration file, and

2) Check the spelling of your contract name and namespace in configuration file. Contract is defined in the contract attribute under system.serviceModel/services/service/endpoint element. The following are the sample configuration.

<system.serviceModel>
  <services>
    <service name="SA.Services.ComputerService"
             behaviorConfiguration="DefaultServiceBehavior">
      <endpoint name="basicHttpComputerService"
                address=""
                binding="basicHttpBinding"
                contract="SA.Services.Contracts.IComputerService" />
      <endpoint address="mex"
                binding="mexHttpBinding"
                contract="IMetadataExchange" />
    </service>
  </services>
</system.serviceModel>


[Issue 3]
Memory gates checking failed because the free memory (267431936 bytes) is less than 5% of total memory.  As a result, the service will not be available for incoming requests.  To resolve this, either reduce the load on the machine or adjust the value of minFreeMemoryPercentageToActivateService on the serviceHostingEnvironment config element.

[How to solve]
This error message appear when the machine that hosted your application does not have sufficient memory. You have a few options to resolve this issue.
1) Increase your machine's memory by adding more RAM, or

2) If your machine is a virtual machine, assign more memory to your virtual machine, or

3) Close and uninstall any unused applications or any applications that runs in background, or

4) Check and make sure your application is not the cause of memory leak, or

5) Deploy your application to another machine that has sufficient memory to run your application, or

6) Reduce number of deployed / hosted application, or

7) Lower the attribute "minFreeMemoryPercentageToActivateService" value as the default value is 5. It is strongly discourage to use this option. Imagine that if your machine has less than 5% available memory but your application still continue to run, it will not only makes the machine freeze, hang or not responsive when interact with it, but also causes other applications to run slower. Refer to below for the sample configuration.

<system.serviceModel>
  <serviceHostingEnvironment multipleSiteBindingsEnabled="true"
                             minFreeMemoryPercentageToActivateService="5">
  </serviceHostingEnvironment>
</system.serviceModel>