MENU

Design code for unit testing

Better Unit test requires better code

I’m learning C# unit test course by Mosh Hamedani.
I had thought unit test needs only unit test codes, so I should learn unit test frameworks or mocking framewroks like NUnit, xunit, and Moq.

But in this course, he said

In order to unit test them, you need to refactor them towards a testable and loosely coupled design.

Now, I agree with him. If I don’t care of my codes quality or testablility, unit test codes would end up being meaningless.

目次

We wanna test business logic

I think you would agree with the necessity of testable code. Before considering of how to write testable code, what is testable code and which design pattern should be followed,

I wanna say what is unit test target, business logic .

As you know, writing unit test requires not a little time in developing application and the most major reason of avoiding writing unit test is “We don’t have such a time.”. Therefore, develoers should focus on getting maximum value from minimum costs, so I wanna clear purposes of unit testing and target of unit testing.

I suppose to the purpose and target of unit testing stated by Khorikov 2020.

The goal is to enable sustainable growth of the software project.

Khorikov 2020

In most applications, the most important part is the part that contains business logic – the domain model.

Khorikov (2020)

Separate Logic and Controller from complicated code

The course of unit testing by Mosh Hamedani I’m learning, introduces 3steps of refactoring code.

  1. Extract the codes uses an external resource into seperate class.
  2. Extract interface from that class.
  3. Finally, you modify the class under test, you talks to the interface instead of one of its concrete implementations. So, instead of dependent only on the interface or contract.

For example, I considering stockManager in a library. It returns stocklist which meet the search condition passed (like below).

If I write code like below.

C#
public List<Book> GetBook(Book searchingBook)
{
    string connectionString = "url";
    string query = "query";
    List<Booking> result;
    using (SqlConnection connection = new SqlConnection(connectionString))
    {
        using (SqlCommand command = new SqlCommand(query, connection))
        {
            // Some code retrieve data from database by SqlCommand
            
        }
    }

    if(result != null)
    {
        // Cast result to appropriate class
        result = BookiViewModel(result);
    }

    return result;

Then, I suppose that I write a below test case.

There are no book in stocks,  which corresponds to searching book, so the method return null.

I think the test case is simple, but the code makes it difficult because I need to prepare real database and store real data. Including business logic and accessing database part within a method make unit test difficult or slow, so I should separete it from business logic as Mosh said.

C#
// StockRepository
interface IStockRepository
{
    IQueryable<Book> GetStocks(string[] conditions);
}

class StockRepository : IStockRepository
{
    public IQueryable<Book> GetStocks(Book searchCondition)
    {
        string connectionString = "url";
        string query = "query";
        IQueryable<Book> result;
        using (SqlConnection connection = new SqlConnection(connectionString))
        {
            using (SqlCommand command = new SqlCommand(query, connection))
            {
                // Retrieve data from database by SqlCommand
            }
        }
        return result;
    }

}
C#
public class Book
{
    public int Id;
    public string title;
}
C#
// StockManager.cs
 class StockManger
 {
     private IStockRepository _stubRepository;
     public StockManger(IStockRepository repository)
     {
         _stubRepository = repository;
     }
     public IQueryable<Book> GetBooks(Book searchCondition)
     {           
         var result = _stubRepository.GetStocks(searchCondition);


         if(result != null)
         {
             result = BookViewModel(result);
         }

         return result;
     }
 }

After rafactoring it, business logic and accessing DB code are separated, which lead to write unit test easily because I can replace accessing DB code to mock. Some design pattern consider this techniques yet.

MVC pattern is a one of them. Model takes a role of logic and data in memory, View take a role of showing page to User and Controller manage them. Developer write unit test easily since It separates of business logic(Model) from others. Frequently, developers implement repository, additional service classes don’t have domain only for business logic, but they would follow separate logic and controller.

Write Loosely – coupled code (depend on abstract)

Why did I implement interface(IBookRepository)?
I think there are two reasons.

First, I quote by West (2007)

Coding to an interface, rather than to an implementation, makes your software easier to extend.  

As he said, in general, depending on interface makes software extendable, for example, when customer will ask to add a functionality, developers might not modify dependent code, might just add a subclass.

Second, in unit testing perspective, when system under tests depend on concrete class, developer’s can’t pass test doubles like mock or stub. For example, if you use Moq for test double, Moq is one of the most popular mocking framework in c#, you pass test double like below (I use above code example) .

C#
public class BookingManagerTests
 {
     private StockManager stockManager;

     [SetUp]
     public void Setup()
     {
         var _stubRepository = new Mock<IStockRepository>();
         var conditions = new Book()
         {
             Title = "ThereAreStocks"
         };

         List<Book> result = null;
         _stubRepository.Setup(sr => sr.GetStocks(conditions)).Returns(result);

         stockManager = new StockManager(_stubRepository.Object);
     }

     [Test]
     public void GetBooks_ThereAreNoStocks_ReturnNull()
     {
         // Arrange
         var conditions = new Book()
         {
             Title = "ThereAreStocks"
         };


         // Act
         var result = stockManager.GetBooks(conditions);


         // Assert
         Assert.That(result, Is.EqualTo(null));
         
     }
 }

I wrote unit test by NUnit and Moq, each of them are Unit test framework and framework for test double. And I wrote class diagram for that.

I injected Mock<IBookRepository> for StockManager tests and defined return value of GetStocks() like below.

C#
var _stubRepository = new Mock<IStockRepository>();
var conditions = new Book()
{
    Title = "ThereAreStocks"
};

List<Book> result = null;

// Implement GetStocks() of Mock
_stubRepository.Setup(sr => sr.GetStocks(conditions)).Returns(result);

stockManager = new StockManager(_stubRepository.Object);

Moq create instance using Interface and implement act, so developer must implement interface for System Under Test when developers use Moq as Unit testing framework.

Summary

  • Unit testing purpose is “To enable sustainable growth of the software projects.” (Khorikov, 2020)
  • “In most applicatoins, the most important part is the part that contains business logic – the domain model”(Khorikov, 2020)
  • Unit testing requires better code.
  • Complicated code often include controller and business logic, so decouple it.
  • Implement interface because extadable code and mocking framework.

References

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

コメント

コメントする

目次