Behavior Based Testing چیست؟
آزمونهایی را که تاکنون بررسی کردیم از نوع state based testing بودند. در این حالت ابتدا یک Mock object را ایجاد و سپس وهلهای از سرویس مدنظر را توسط آن تهیه میکنیم. در ادامه تعدادی از متدهای این سرویس را مانند متد Process کلاس LoanApplicationProcessor، فراخوانی میکنیم. اینکار سبب اجرای فعالیتی در این سیستم شده و به همراه آن تعاملی با اشیاء Mock شده نیز صورت میگیرد. در نهایت، حالت و یا نتیجهای را دریافت میکنیم و آنرا با حالت یا نتیجهای که انتظار داریم، مقایسه خواهیم کرد. بنابراین در این روش پس از پایان اجرای سیستم در حال اجرا، حالت و نتیجهی نهایی حاصل از عملکرد آن، مورد بررسی قرار میگیرد.
در Behavior based testing نیز در ابتدا Mock objects مورد نیاز تهیه میشوند و سپس وهلهای از سرویس مدنظر را توسط آنها تهیه میکنیم. همانند قبل، سیستم در حال بررسی را اجرا میکنیم (برای مثال با فراخوانی متدی در یک سرویس) تا سیستم، با اشیاء Mock شده کار کند. در این حالت دسترسی به متدی و یا خاصیتی بر روی Mock object صورت میگیرد. اکنون همانند روش state based testing که نتیجهی عملیات را مورد بررسی قرار میدهد، در اینجا بررسی میکنیم که آیا خاصیت یا متد خاصی در Mock objectهای تنظیم شده، استفاده شدهاند یا خیر؟ بنابراین هدف از این نوع آزمایش، بررسی تعامل بین یک سیستم و وابستگیهای آن است.
برای مثال فرض کنید که میخواهیم کلاس ProductCache را بررسی و آزمایش کنیم. این کلاس از یک DB Provider واقعی برای دسترسی به اطلاعات استفاده میکند. برای مثال اگر محصول شمارهی 42 را از آن درخواست دهیم، اگر این محصول در کش موجود نباشد، ابتدا یک کوئری را به بانک اطلاعاتی صادر کرده و مقدار متناظری را دریافت میکند. سپس نتیجه را کش کرده و به فراخوان بازگشت میدهد. در اینجا میتوان بررسی کرد که آیا محصول صحیحی از کش دریافت شدهاست یا خیر؟ (یا همان state based testing). اما اگر بخواهیم منطق کش کردن را بررسی کنیم، چطور میتوان متوجه شد که برای مثال محصول دریافت شده مستقیما از کش دریافت شده و یا خیر از همان ابتدا از بانک اطلاعاتی واکشی شده، کش شده و سپس بازگشت داده شدهاست؟ برای این منظور میتوان توسط کتابخانهی Moq، یک نمونهی mock شدهی DB Provider را تهیه و سپس از آن به عنوان وابستگی شیء Product Cache استفاده کرد. اکنون زمانیکه اطلاعاتی از Product Cache درخواست میشود، میتوان Mock object تهیه شده را طوری تنظیم کرد تا اطلاعات مدنظر ما را بازگشت دهد. در این بین مزیت کار کردن با یک Mock object، امکان بررسی این است که آیا متدی بر روی آن فراخوانی شدهاست یا خیر؟ به این ترتیب میتوان تعامل و رفتار Product Cache را با وابستگی آن، تحت نظر قرار داد (Behavior based testing).
بررسی فراخوانی شدن یک متد بدون پارامتر بر روی یک Mock object
در مثال این سری و در کلاس LoanApplicationProcessor و متد Process آن، فراخوانی سطر زیر را مشاهده میکنید:
_identityVerifier.Initialize();
namespace Loans.Tests { [TestClass] public class LoanApplicationProcessorShould { [TestMethod] public void InitializeIdentityVerifier() { var product = new LoanProduct {Id = 99, ProductName = "Loan", InterestRate = 5.25m}; var amount = new LoanAmount {CurrencyCode = "Rial", Principal = 2_000_000_0}; var applicant = new Applicant {Id = 1, Name = "User 1", Age = 25, Address = "This place", Salary = 1_500_000_0}; var application = new LoanApplication {Id = 42, Product = product, Amount = amount, Applicant = applicant}; var mockIdentityVerifier = new Mock<IIdentityVerifier>(); mockIdentityVerifier.Setup(x => x.Validate(applicant.Name, applicant.Age, applicant.Address)) .Returns(true); var mockCreditScorer = new Mock<ICreditScorer>(); mockCreditScorer.Setup(x => x.ScoreResult.ScoreValue.Score).Returns(110_000); var processor = new LoanApplicationProcessor(mockIdentityVerifier.Object, mockCreditScorer.Object); processor.Process(application); mockIdentityVerifier.Verify(x => x.Initialize()); } } }
تنظیم mockCreditScorer.Setup را نیز در قسمت سوم این سری «تنظیم مقادیر خواص اشیاء» بررسی کردیم.
در ادامه، متد Process کلاس LoanApplicationProcessor فراخوانی شدهاست. اکنون با استفاده از متد Verify کتابخانهی Moq، میتوان بررسی کرد که آیا در سیستم در حال آزمایش، متدی که توسط آن به صورت strongly typed مشخص میشود، فراخوانی شدهاست یا خیر؟
پس از این تنظیمات اگر متد آزمایش واحد InitializeIdentityVerifier را بررسی کنیم با موفقیت به پایان خواهد رسید. برای نمونه یکبار هم سطر فراخوانی متد Initialize را کامنت کنید و سپس این آزمایش را اجرا نمائید تا بتوان شکست آنرا نیز مشاهده کرد.
بررسی فراخوانی شدن یک متد پارامتر دار بر روی یک Mock object
همان متد آزمون واحد InitializeIdentityVerifier را درنظر بگیرید، در انتهای آن یک سطر زیر را نیز اضافه میکنیم:
mockCreditScorer.Verify(x => x.CalculateScore(applicant.Name, applicant.Address));
بدیهی است اگر در این بین، متد CalculateScore با هر مقدار دیگری در کلاس LoanApplicationProcessor فراخوانی شود، آزمون فوق با شکست مواجه خواهد شد. اگر در اینجا مقدار پارامترها اهمیتی نداشتند، همانند قسمت دوم میتوان از ()<It.IsAny<string استفاده کرد.
بررسی تعداد بار فراخوانی یک متد بر روی یک Mock object
برای بررسی تعداد بار فراخوانی یک متد بر روی یک شیء Mock شده، میتوان از پارامتر دوم متد Verify استفاده کرد:
mockCreditScorer.Verify(x => x.CalculateScore(It.IsAny<string>(), applicant.Address), Times.Once);
بررسی فراخوانی Getter و Setter خواص یک شیء Mock شده
علاوه بر امکان دریافتن وقوع فراخوانی یک متد، میتوان از خوانده شدن و یا تغییر مقدار یک خاصیت نیز توسط کتابخانهی Moq مطلع شد. برای مثال در قسمتی از کدهای متد Process داریم:
if (_creditScorer.ScoreResult.ScoreValue.Score < MinimumCreditScore)
mockCreditScorer.VerifyGet(x => x.ScoreResult.ScoreValue.Score, Times.Once);
جهت بررسی تغییر مقدار یک متغیر بر روی یک شیء Mock شده، میتوان از متد VerifySet کمک گرفت:
mockCreditScorer.VerifySet(x => x.Count = It.IsAny<int>(), Times.Once);
mockCreditScorer.SetupProperty(x => x.Count, 10); Assert.AreEqual(11, mockCreditScorer.Object.Count);
روش بررسی فراخوانی تمام متدها و تمام خواص یک شیء Mock شده
با استفاده از متد زیر میتوان از «نوشتن شده بودن» آزمایش مورد استفاده قرار گرفتن تمام متدها و خواص یک شیء Mock شده، مطمئن شد:
mockIdentityVerifier.VerifyNoOtherCalls();
mockIdentityVerifier.Verify(x => x.Validate(It.IsAny<string>(), It.IsAny<int>(), It.IsAny<string>()));
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: MoqSeries-4.zip