Fundamentals of Unit Testing: Understand AAA in Unit Testing

Introduction

In today's computing landscape, where multi-core processors are ubiquitous, optimizing the performance of your applications often involves leveraging parallelism. Parallelism enables you to divide tasks into smaller chunks that can be executed concurrently, taking full advantage of the available computing resources. One powerful tool in the .NET developer's arsenal for achieving parallelism is PLINQ - Parallel Language-Integrated Query.

What is PLINQ?

PLINQ, or Parallel Language-Integrated Query, is an extension of LINQ (Language-Integrated Query) introduced in the .NET Framework. LINQ revolutionized data querying in .NET by allowing developers to write SQL-like queries directly within C# or VB.NET code, enabling a more expressive and intuitive way to manipulate collections and query data sources.

PLINQ builds upon this foundation by introducing parallel processing capabilities to LINQ queries. By simply adding the .AsParallel() extension method to your LINQ query, you can instruct the compiler to execute the query in parallel, harnessing the power of multiple CPU cores for improved performance.

Why Use PLINQ?

The primary motivation behind using PLINQ is to accelerate the execution of LINQ queries, especially when dealing with large datasets or computationally intensive operations. By parallelizing the query execution, PLINQ can significantly reduce the overall processing time, leading to faster response times and improved scalability.

Consider a scenario where you have a collection of items and need to perform a computationally intensive operation on each item, such as calculating a complex mathematical function or processing large amounts of textual data. Without parallelism, this operation would be performed sequentially, potentially leading to long processing times, especially on modern multi-core processors where significant computational power remains untapped.

Example. Parallelizing a LINQ Query for Squares

Let's illustrate the use of PLINQ with a simple example. Suppose we have a list of numbers, and we want to calculate the square of each number in parallel using PLINQ:

using System;
using System.Linq;

class Program
{
    static void Main()
    {
        // Create a sample data source
        int[] numbers = Enumerable.Range(1, 1000000).ToArray();

        // Perform a parallel LINQ query to calculate squares
        var squares = numbers.AsParallel().Select(x => x * x);

        // Output the results
        foreach (var square in squares)
        {
            Console.WriteLine(square);
        }
    }
}
C#
Copy

In this example, we create an array of numbers ranging from 1 to 1,000,000 using Enumerable.Range(). We then use AsParallel() to indicate that the LINQ query should be executed in parallel. The Select() method is used to calculate the square of each number. Finally, we iterate over the results and output them to the console.

By running this code, you'll observe that the computation of squares is performed concurrently across multiple CPU cores, resulting in faster execution compared to the sequential counterpart.

Example. Parallelizing a LINQ Query for Filtering Employees

Now, let's consider another example where we have a collection of Employee objects, and we want to use PLINQ to perform parallel processing to filter out employees who meet certain criteria, such as having a specific job title or earning above a certain salary threshold.

using System;
using System.Linq;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        // Sample data source
        List<Employee> employees = new List<Employee>
        {
            new Employee { Id = 1, Name = "Alice", JobTitle = "Software Engineer", Salary = 80000 },
            new Employee { Id = 2, Name = "Bob", JobTitle = "Project Manager", Salary = 95000 },
            new Employee { Id = 3, Name = "Charlie", JobTitle = "Software Engineer", Salary = 85000 },
            new Employee { Id = 4, Name = "David", JobTitle = "Data Analyst", Salary = 75000 },
            new Employee { Id = 5, Name = "Eve", JobTitle = "Software Engineer", Salary = 90000 }
        };

        // Perform a parallel LINQ query to filter software engineers earning above 85000
        var filteredEmployees = employees.AsParallel().Where(emp => emp.JobTitle == "Software Engineer" && emp.Salary > 85000);

        // Output the filtered results
        Console.WriteLine("Software Engineers earning above 85000:");
        foreach (var emp in filteredEmployees)
        {
            Console.WriteLine($"ID: {emp.Id}, Name: {emp.Name}, Salary: {emp.Salary}");
        }
    }
}

class Employee
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string JobTitle { get; set; }
    public decimal Salary { get; set; }
}
C#
Copy

In this example, we define an Employee class with properties such as Id, Name, JobTitle, and Salary. We create a list of Employee objects representing different employees with various job titles and salaries.

Using PLINQ, we filter the employees who are software engineers (JobTitle == "Software Engineer") and have a salary greater than $85,000. We then iterate over the filtered results and output the relevant employee information.

Running this code will demonstrate how PLINQ can efficiently process the filtering operation in parallel, providing performance benefits, especially for larger datasets or computationally intensive queries.

Considerations and Best Practices

While PLINQ can offer significant performance benefits, it's essential to use it judiciously and consider the following best practices:

  1. Profile and Measure: Before parallelizing your LINQ queries with PLINQ, profile your application to identify performance bottlenecks. Measure the impact of parallelization on different workloads to ensure that it provides the desired performance improvements.
  2. Avoid Excessive Parallelism: Parallelism comes with overhead, so avoid parallelizing small or lightweight operations where the overhead may outweigh the benefits. Use parallelism judiciously for operations that are CPU-bound or I/O-bound and can be divided into independent tasks.
  3. Ensure Thread Safety: When working with shared data in parallel LINQ queries, ensure proper synchronization to prevent race conditions and data corruption. Use thread-safe collections or synchronization primitives such as locks or concurrent data structures when necessary.
  4. Control Degree of Parallelism: PLINQ provides options to control the degree of parallelism, allowing you to specify the maximum number of concurrent operations. Adjusting the degree of parallelism can help optimize performance and resource utilization based on the characteristics of your workload and hardware.

Conclusion

PLINQ is a powerful feature in the .NET Framework that enables parallel processing of LINQ queries, unlocking performance improvements by leveraging multi-core processors. By simply adding .AsParallel() to your LINQ queries, you can harness the power of parallelism and accelerate the execution of computationally intensive operations.

However, it's essential to use PLINQ judiciously and understand its implications on performance and resource utilization. By profiling your application, considering best practices, and controlling the degree of parallelism, you can maximize the benefits of PLINQ and build high-performance, scalable applications in .NET.

So, the next time you find yourself dealing with large datasets or computationally intensive operations in your .NET applications, remember to consider PLINQ as a powerful tool in your optimization toolkit.

 

 

This is the fundamentals of the unit testing article series. In our previous article, we saw how to do unit testing in the Visual Studio environment. You can read them here.

In this short article, we will understand the “AAA” concept of unit testing. This is a very important and fundamental idea of unit testing. In the overall view of any unit test application, we will see that a unit test is a three-step process. The first one is setup test or arrange test, Act test or execute unit test and Assert means verify test result with expected results. The concept of three "A"s are given below.

  • Arrange: This is the first step of a unit test application. Here we will arrange the test, in other words, we will do the necessary setup of the test. For example, to perform the test we need to create an object of the targeted class, if necessary, then we need to create mock objects and other variable initialization, something like this.
  • Act: This is the middle step of a unit-step application. In this step, we will execute the test. In other words, we will do the actual unit testing and the result will be obtained from the test application. Basically, we will call the targeted function in this step using the object that we created in the previous step.
  • Assert: This is the last step of a unit test application. In this step, we will check and verify the returned result with the expected results.

Ok, fine we have understood the concept, so let's implement the three steps practically. So, create a class library application with the following code.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TestProjectLibrary
{
    public class testClass
    {
        public Boolean testFunction()
        {
            return true;
        }
    }
}
C#
Copy

This is the code that we will test. The code is very simple, the class contains only one function and it will always return true since we are not interested in writing the business logic at all.

Now, add one unit test application to the solution and the following is the code to do the unit testing of the code above. If you are very new to unit testing then I will suggest you go through our first article to understand unit test setup by a Visual Studio unit test application. Have a look at the following code.

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using TestProjectLibrary;
//using Moq;

namespace UnitTest
{
    [TestClass]
    public class UnitTest
    {
        [TestMethod]
        public void TestMethod()
        {
            //Arrange test
            testClass objtest = new testClass();
            Boolean result;

            //Act test
            result = objtest.testFunction();

            //Assert test
            Assert.AreEqual(true, result);
        }
    }
}
C#
Copy

Here we clearly see three parts of the unit test, at first we are creating an object of the test class and then we declare one Boolean variable that will store the returned result, and in the next level, we check whether or not the return value is equal to the expected value.

Let's run the test and check the result.

Result

Conclusion

Oh, we see that it has been successfully executed and the test passed. I hope you have understood the concept of AAA in unit test applications.

 

Up Next
    Ebook Download
    View all
    Learn
    View all