12/08/2018, 10:30

Tìm hiểu về Stub, Mock và Fake trong unit test

Trong bài viết này, tôi tìm hiểu về ba khái niệm rất quan trọng trong unit test là Stub, Mock và Fake, ý nghĩa khái niệm của chúng là gì và chúng được sử dụng như thế nào trong điều kiện nào. Đầu tiên, chúng ta cần định nghĩa unit test là gì và mô hình TDD là như thế nào. Các bạn có thể tham khảo ...

Trong bài viết này, tôi tìm hiểu về ba khái niệm rất quan trọng trong unit test là Stub, Mock và Fake, ý nghĩa khái niệm của chúng là gì và chúng được sử dụng như thế nào trong điều kiện nào. Đầu tiên, chúng ta cần định nghĩa unit test là gì và mô hình TDD là như thế nào. Các bạn có thể tham khảo các khái niệm trên ở link sau: unit-test-voi-phat-trien-phan-mem-hien-dai

Cả ba khái niệm Stub, Mock và Fake đều rất quan trọng trong unit test và gắn liền với định nghĩa về "de-coupled" architecture design. "de-coupling" architecture có mục đích chính là thiết kế phần mềm sao cho chúng ta có thể tách phần mềm ra thành các phần riêng biệt (các unit) mà không bị ảnh hưởng bởi các phần khác hoặc chỉ bị ảnh hưởng một cách tối thiểu.

Ví dụ, tôi có một class cần viết unit test và các class khác liên quan như kết nối DB hoặc call service api. Bây giờ, tôi muốn kiểm tra các class, nơi các đối tượng phụ thuộc là chưa sẵn sàng, vì vậy trong tình huống này, tôi cần phải thực hiện một đối tượng Mock hoặc Fake để kiểm tra class của tôi. Chúng ta sẽ bỏ qua thực tế hoạt động DB hoặc kết quả thực sự của việc call service. Theo cách này chúng ta có thể kiểm tra logic chính của class mà chúng ta cần thực hiện unit test.

1. Mock là gì?

Mock object (MO) là một đối tượng ảo mô phỏng các tính chất và hành vi giống hệt như đối tượng thực được truyền vào bên trong khối mã đang vận hành nhằm kiểm tra tính đúng đắn của các hoạt động bên trong. Mock object có các đặc điểm sau:

  • Đơn giản hơn đối tượng thực nhưng vẫn giữ được sự tương tác với các đối tượng khác.

  • Không lặp lại nội dung đối tượng thực.

  • Cho phép thiết lập các trạng thái riêng trợ giúp cho việc thực hiện unit test.

2. Stub là gì?

Stub là một chương trình hoặc thành phần giả lập (thay thế cho chương trình hoặc thành phần chưa code xong để kiểm thử) nó dùng để kiểm thử... ví dụ, trong một dự án có 4 modules, nhưng đến lúc test mà còn một module chưa code xong, để test được thì cần phải có 4 modules này, vậy thì cần phải có một chương trình giả lập module này để thực hiện test. Chương trình giả lập cho module này được gọi là STUB.

Oh, cả hai đều có vẻ khó hiểu!

Khi tôi bắt đầu tìm hiểu về unit test, tôi rất khó để phân biệt được Mock và Stub khác nhau như thế nào, cả hai đều rất giống nhau trong khái niệm. Nhưng một khi bạn hiểu đúng cách rồi thì bạn sẽ nhận ra sự khác biệt thực sự.

Một Stub không thể trả về kết quả unit test là fail bởi vì bạn biết những gì bạn đang thực hiện và lý do tại sao bạn đang thực hiện nó, Nó được sử dụng là để thay thế cho một module và được giả sử là phải thực hiện đúng các nhiệm vụ được giao. Tuy nhiên, Mock object chỉ là một đối tượng mà bắt chước các đối tượng thực sự. Nếu logic chính của method là sai thì các unit test sẽ fail ngay cả khi chúng ta thiết lập mock object chính xác.

3. Fake là gì

Như chúng ta biết, ý nghĩa của Fake là giả, không thật. Đó là một ý nghĩa nói chung, nó có thể chỉ tới một một object thật hoặc một object giả hoặc giả hoặc một cái gì đó không có thật.

Vì vậy, Fake là một thuật ngữ chung chung, mà có thể trỏ đến bất cứ điều gì.

4. Ví dụ:

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

namespace ConsoleApp
{
    public interface IExtensionNanager
    {
        Boolean CheckExtension(string FileName);
    }

    public class ExtensionManager : IExtensionNanager
    {
        public bool CheckExtension(string FileName)
        {
            //Some complex business logic might goes here. May be DB operation or file system handling
            return false;
        }
    }

    //Stub implementation to bypass actual Extension manager class.
    public class StubExtensionManager : IExtensionNanager
    {
        public bool CheckExtension(string FileName)
        {
            return true;
        }
    }

    public class FileChecker
    {
        IExtensionNanager objmanager = null;
        //Default constructor
        public FileChecker()
        {
            objmanager = new ExtensionManager();
        }
        //parameterized constructor
        public FileChecker(IExtensionNanager tmpManager)
        {
            objmanager = tmpManager;
        }

        public Boolean CheckFile(String FileName)
        {
            return objmanager.CheckExtension(FileName);
        }
    }
}

Code này là rất đơn giản để hiểu. Chúng ta đã có một class FileChecker đơn giản. Class quản lý FileExtension thực tế không được implement đầy đủ và vì vậy chúng ta có một phiên bản sơ khai của class. Chúng ta có thể thấy rằng các chức năng CheckExtension sẽ luôn luôn đúng, khi chúng ta định nghĩa một cách rõ ràng.

Dưới đây là code unit test:

using System;
using System.Web.Mvc;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MVCApplication.Controllers;
using Moq;
using ConsoleApp.Company;
using ConsoleApp;

namespace TestMVC
{
    [TestClass]
    public class UnitTest1
    {
        [TestMethod]
        public void TestMethod1()
        {
            //Act
            StubExtensionManager stub = new StubExtensionManager();
            FileChecker checker = new FileChecker(stub);

            //Action
            bool IsTrueFile = checker.CheckFile("myFile.whatever");

            //Assert
            Assert.AreEqual(true, IsTrueFile);
        }
    }
}

Và code unit test trên sẽ luôn luôn pass bởi vì chúng ta đã sử dụng các chức năng được định nghĩa Stub và nó sẽ làm cho code unit test luôn đúng.

Vậy một lần nữa để làm rõ hơn ý tưởng ban đầu, Stub được sử dụng để luôn luôn vượt qua unit test bởi vì chúng ta biết những gì chúng ta đang thực hiện và tại sao? Mục đích của chúng ta là để có thể pass qua phần unit test này.

Thực hiện Mock trong ví dụ

Bây giờ chúng ta sẽ thực hiện với một Mock object. Có rất nhiều các cách mocking khác nhau, nhưng để hiểu rõ hơn về bản chất tôi sẽ thực hiện một cách mockig viết tay đơn giản.

Một điểm quan trọng về mock là, chúng ta có thể sử dụng một đối tượng mock để kiểm tra class chính trong unit test.

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

namespace ConsoleApp
{
    public interface IServiceProvider
    {
        void extensionService(String fileName);
    }
    //Mock extenison service provider
    public class MockExtensionService : IServiceProvider
    {
        public string ErrorMessage = null;
        public void extensionService(string fileName)
        {
            if (fileName.Split('.')[1] != "myType")
            {
                ErrorMessage = "Wrong Type";
            }
        }
    }

    //Actual incomplete ExtensionManager functionality
    public class ExtensionManager : IServiceProvider
    {
        public void extensionService(string fileName)
        {
            throw new NotImplementedException();
        }
    }

    public class ExtensionAnalyzer
    {
        public IServiceProvider provider = null;
        public ExtensionAnalyzer(IServiceProvider tmpProvider)
        {
            provider = tmpProvider;
        }

        public void ExtensionCheck(string fileName)
        {
            provider.extensionService(fileName);
        }
    }
}

Thực hiện rất đơn giản, chúng tôi đã chỉ cần thực hiện một class Mock và class đó sẽ bắt chước các chức năng thực tế. Dưới đây là code unit test.

using System;
using System.Web.Mvc;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MVCApplication.Controllers;
using Moq;
using ConsoleApp.Company;
using ConsoleApp;

namespace TestMVC
{
    [TestClass]
    public class UnitTest1
    {
        [TestMethod]
        public void TestMethod1()
        {
            //Act
            MockExtensionService mockobject = new MockExtensionService();
            //Inject mock object now
            ExtensionAnalyzer analyzer = new ExtensionAnalyzer(mockobject);
            //Action
            analyzer.ExtensionCheck("somefile.someextension");

            //Assert
            Assert.AreEqual(mockobject.ErrorMessage, "Wrong Type");
        }
    }
}

Kết luận

Trong bài viết này, chúng ta đã học được những khái niệm về Stub và Mock trong unit test. Tôi hy vọng bài viết này sẽ giúp bạn hiểu rõ hơn về chúng. Chúng ta cần phải chọn sử dụng Stub hay Mock trong những tình huống riêng biệt. Một stub sẽ giúp bạn khi bạn muốn thay thế các chức năng thực tế, Mock sẽ giúp đỡ khi bạn muốn bắt chước các chức năng thực tế.

0