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.
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.