मैंने इस लेख को यह दिखाने के लिए लिखने का फैसला किया है कि यूनिट परीक्षण न केवल कोड में प्रतिगमन से निपटने के लिए एक उपकरण है, बल्कि उच्च गुणवत्ता वाली वास्तुकला में एक महान निवेश भी है। इसके अलावा, अंग्रेज़ी .NET समुदाय के एक विषय ने मुझे ऐसा करने के लिए प्रेरित किया। लेख के लेखक जॉनी थे। उन्होंने वित्तीय क्षेत्र में व्यापार के लिए सॉफ्टवेयर विकास में शामिल कंपनी में अपना पहला और आखिरी दिन बताया। जॉनी इस पद के लिए आवेदन कर रहा था - यूनिट परीक्षणों के एक डेवलपर के रूप में। वह खराब कोड गुणवत्ता से परेशान था, जिसका उसे परीक्षण करना था। उन्होंने कोड की तुलना एक कबाड़खाने से की, जो वस्तुओं से भरे हुए थे जो किसी भी अनुपयुक्त स्थानों में एक दूसरे को क्लोन करते थे। इसके अलावा, वह एक संग्रह में सार डेटा प्रकार नहीं ढूंढ सका:कोड में केवल कार्यान्वयन के बाध्यकारी थे जो एक दूसरे से अनुरोध करते थे।
जॉनी ने इस कंपनी में मॉड्यूल परीक्षण की सभी बेकारता को महसूस करते हुए प्रबंधक को इस स्थिति की रूपरेखा दी, आगे सहयोग से इनकार कर दिया, और एक मूल्यवान सलाह दी। उन्होंने सिफारिश की कि एक विकास दल तत्काल वस्तुओं को सीखने और अमूर्त डेटा प्रकारों का उपयोग करने के लिए पाठ्यक्रम पर जाए। मुझे नहीं पता कि प्रबंधक ने उनकी सलाह का पालन किया (मुझे लगता है कि उन्होंने नहीं किया)। हालांकि, यदि आप रुचि रखते हैं कि जॉनी का क्या मतलब है और मॉड्यूल परीक्षण का उपयोग आपके वास्तुकला की गुणवत्ता को कैसे प्रभावित कर सकता है, तो इस लेख को पढ़ने के लिए आपका स्वागत है।
निर्भरता अलगाव मॉड्यूल परीक्षण का आधार है
मॉड्यूल या यूनिट परीक्षण एक परीक्षण है जो मॉड्यूल की कार्यक्षमता को उसकी निर्भरता से अलग करके सत्यापित करता है। डिपेंडेंसी आइसोलेशन वास्तविक दुनिया की वस्तुओं का एक प्रतिस्थापन है, जिसके साथ परीक्षण किया जा रहा मॉड्यूल स्टब्स के साथ इंटरैक्ट करता है जो उनके प्रोटोटाइप के सही व्यवहार का अनुकरण करता है। यह प्रतिस्थापन किसी विशेष मॉड्यूल के परीक्षण पर ध्यान केंद्रित करने की अनुमति देता है, इसके पर्यावरण के संभावित गलत व्यवहार की अनदेखी करता है। परीक्षण में निर्भरता को बदलने की आवश्यकता एक दिलचस्प संपत्ति का कारण बनती है। एक डेवलपर जो महसूस करता है कि उनके कोड का उपयोग मॉड्यूल परीक्षणों में किया जाएगा, उन्हें एब्स्ट्रैक्शन का उपयोग करके विकसित करना होगा और उच्च कनेक्टिविटी के पहले संकेतों पर रिफैक्टरिंग करना होगा।
मैं इसे विशेष उदाहरण पर विचार करने जा रहा हूं।
आइए कल्पना करने की कोशिश करें कि कंपनी द्वारा विकसित सिस्टम पर एक व्यक्तिगत संदेश मॉड्यूल कैसा दिख सकता है जिससे जॉनी बच निकला। और अगर डेवलपर्स यूनिट टेस्टिंग लागू करते हैं तो वही मॉड्यूल कैसा दिखेगा।
मॉड्यूल संदेश को डेटाबेस में संग्रहीत करने में सक्षम होना चाहिए और यदि जिस व्यक्ति को संदेश संबोधित किया गया था वह सिस्टम में है - टोस्ट अधिसूचना के साथ स्क्रीन पर संदेश प्रदर्शित करें।
//A module for sending messages in C#. Version 1. public class MessagingService { public void SendMessage(Guid messageAuthorId, Guid messageRecieverId, string message) { //A repository object stores a message in a database new MessagesRepository().SaveMessage(messageAuthorId, messageRecieverId, message); //check if the user is online if (UsersService.IsUserOnline(messageRecieverId)) { //send a toast notification calling the method of a static object NotificationsService.SendNotificationToUser(messageAuthorId, messageRecieverId, message); } } }
आइए देखें कि हमारे मॉड्यूल में क्या निर्भरताएँ हैं।
SendMessage फ़ंक्शन, Notificationsservice और Userservice ऑब्जेक्ट्स के स्थिर तरीकों को आमंत्रित करता है और Messagesrepository ऑब्जेक्ट बनाता है जो डेटाबेस के साथ काम करने के लिए ज़िम्मेदार है।
इस तथ्य में कोई समस्या नहीं है कि मॉड्यूल अन्य वस्तुओं के साथ बातचीत करता है। समस्या यह है कि यह इंटरैक्शन कैसे बनाया जाता है, और इसे सफलतापूर्वक नहीं बनाया गया है। तीसरे पक्ष के तरीकों तक सीधी पहुंच ने हमारे मॉड्यूल को विशिष्ट कार्यान्वयन से मजबूती से जोड़ा है।
इस इंटरैक्शन में कई कमियां हैं, लेकिन महत्वपूर्ण बात यह है कि मैसेजिंग सर्विस मॉड्यूल ने अधिसूचना सेवा, उपयोगकर्ता सेवा और संदेश भंडार के कार्यान्वयन से अलगाव में परीक्षण करने की क्षमता खो दी है। दरअसल, हम इन वस्तुओं को स्टब्स से नहीं बदल सकते।
अब देखते हैं कि यदि कोई डेवलपर इसकी देखभाल करता है तो वही मॉड्यूल कैसा दिखेगा।
//A module for sending messages in C#. Version 2. public class MessagingService: IMessagingService { private readonly IUserService _userService; private readonly INotificationService _notificationService; private readonly IMessagesRepository _messagesRepository; public MessagingService(IUserService userService, INotificationService notificationService, IMessagesRepository messagesRepository) { _userService = userService; _notificationService = notificationService; _messagesRepository = messagesRepository; } public void AddMessage(Guid messageAuthorId, Guid messageRecieverId, string message) { //A repository object stores a message in a database. _messagesRepository.SaveMessage(messageAuthorId, messageRecieverId, message); //check if the user is online if (_userService.IsUserOnline(messageRecieverId)) { //send a toast message _notificationService.SendNotificationToUser(messageAuthorId, messageRecieverId, message); } } }
जैसा कि आप देख सकते हैं, यह संस्करण बहुत बेहतर है। वस्तुओं के बीच की बातचीत अब सीधे नहीं बल्कि इंटरफेस के माध्यम से बनाई गई है।
हमें अब व्यावसायिक तर्क के साथ स्थैतिक वर्गों तक पहुँचने और वस्तुओं को त्वरित करने की आवश्यकता नहीं है। मुख्य बिंदु यह है कि हम एक कंस्ट्रक्टर में परीक्षण के लिए स्टब्स पास करके सभी निर्भरताओं को बदल सकते हैं। इस प्रकार, कोड टेस्टेबिलिटी को बढ़ाते हुए, हम अपने कोड की टेस्टेबिलिटी और हमारे एप्लिकेशन की आर्किटेक्चर दोनों में सुधार कर सकते हैं। हमने सीधे कार्यान्वयन का उपयोग करने से इनकार कर दिया और ऊपर की परत को तत्काल पारित कर दिया। जॉनी ठीक यही चाहता था।
इसके बाद, संदेश भेजने के मॉड्यूल के लिए एक परीक्षण बनाएं।
परीक्षणों पर विशिष्टता
परिभाषित करें कि हमारे परीक्षण को क्या जांचना चाहिए:
- SaveMessage मेथड का सिंगल कॉल
- SendNotificationToUser() विधि का एक एकल कॉल यदि IUsersService ऑब्जेक्ट पर IsUserOnline() विधि स्टब सही है
- कोई SendNotificationToUser() विधि नहीं है यदि IUsersService ऑब्जेक्ट पर IsUserOnline() विधि स्टब गलत है
इन शर्तों का पालन करना गारंटी दे सकता है कि SendMessage संदेश का कार्यान्वयन सही है और इसमें कोई त्रुटि नहीं है।
परीक्षण
परीक्षण को अलग किए गए Moq ढांचे का उपयोग करके कार्यान्वित किया जाता है
[TestMethod] public void AddMessage_MessageAdded_SavedOnce() { //Arrange //sender Guid messageAuthorId = Guid.NewGuid(); //receiver who is online Guid recieverId = Guid.NewGuid(); //a message sent from a sender to a receiver string msg = "message"; // stub for the IsUserOnline interface of the IUserService method Mock<IUserService> userServiceStub = new Mock<IUserService>(new MockBehavior()); userServiceStub.Setup(x => x.IsUserOnline(It.IsAny<Guid>())).Returns(true); //mocks for INotificationService and IMessagesRepository Mock<INotificationService> notificationsServiceMoq = new Mock<INotificationService>(); Mock<IMessagesRepository> repositoryMoq = new Mock<IMessagesRepository>(); //create a module for messages passing mocks and stubs as dependencies var messagingService = new MessagingService(userServiceStub.Object, notificationsServiceMoq.Object, repositoryMoq.Object); //Act messagingService.AddMessage(messageAuthorId, recieverId, msg); //Assert repositoryMoq.Verify(x => x.SaveMessage(messageAuthorId, recieverId, msg), Times.Once); } [TestMethod] public void AddMessage_MessageSendedToOffnlineUser_NotificationDoesntRecieved() { //Arrange //sender Guid messageAuthorId = Guid.NewGuid(); //receiver who is offline Guid offlineReciever = Guid.NewGuid(); //message sent from a sender to a receiver string msg = "message"; // stub for the IsUserOnline interface of the IUserService method Mock<IUserService> userServiceStub = new Mock<IUserService>(new MockBehavior()); userServiceStub.Setup(x => x.IsUserOnline(offlineReciever)).Returns(false); //mocks for INotificationService and IMessagesRepository Mock<INotificationService> notificationsServiceMoq = new Mock<INotificationService>(); Mock<IMessagesRepository> repositoryMoq = new Mock<IMessagesRepository>(); // create a module for messages passing mocks and stubs as dependencies var messagingService = new MessagingService(userServiceStub.Object, notificationsServiceMoq.Object, repositoryMoq.Object); //Act messagingService.AddMessage(messageAuthorId, offlineReciever, msg); //Assert notificationsServiceMoq.Verify(x => x.SendNotificationToUser(messageAuthorId, offlineReciever, msg), Times.Never); } [TestMethod] public void AddMessage_MessageSendedToOnlineUser_NotificationRecieved() { //Arrange //sender Guid messageAuthorId = Guid.NewGuid(); //receiver who is online Guid onlineRecieverId = Guid.NewGuid(); //message sent from a sender to a receiver string msg = "message"; // stub for the IsUserOnline interface of the IUserService method Mock<IUserService> userServiceStub = new Mock<IUserService>(new MockBehavior()); userServiceStub.Setup(x => x.IsUserOnline(onlineRecieverId)).Returns(true); //mocks for INotificationService and IMessagesRepository Mock<INotificationService> notificationsServiceMoq = new Mock<INotificationService>(); Mock<IMessagesRepository> repositoryMoq = new Mock<IMessagesRepository>(); //create a module for messages passing mocks and stubs as dependencies var messagingService = new MessagingService(userServiceStub.Object, notificationsServiceMoq.Object, repositoryMoq.Object); //Act messagingService.AddMessage(messageAuthorId, onlineRecieverId, msg); //Assert notificationsServiceMoq.Verify(x => x.SendNotificationToUser(messageAuthorId, onlineRecieverId, msg), Times.Once); }
संक्षेप में, एक आदर्श वास्तुकला की तलाश एक बेकार काम है।
यूनिट परीक्षण उपयोग करने के लिए बहुत अच्छे हैं जब आपको मॉड्यूल के बीच युग्मन खोने पर आर्किटेक्चर की जांच करने की आवश्यकता होती है। फिर भी, ध्यान रखें कि जटिल इंजीनियरिंग सिस्टम को डिजाइन करना हमेशा एक समझौता होता है। कोई आदर्श वास्तुकला नहीं है और अनुप्रयोग विकास के सभी परिदृश्यों को पहले से ध्यान में रखना संभव नहीं है। वास्तुकला की गुणवत्ता कई मापदंडों पर निर्भर करती है, अक्सर परस्पर अनन्य। आप अमूर्तता का एक अतिरिक्त स्तर जोड़कर किसी भी डिज़ाइन समस्या को हल कर सकते हैं। हालांकि, यह बड़ी मात्रा में अमूर्त स्तरों की समस्या का उल्लेख नहीं करता है। मैं यह सोचने की अनुशंसा नहीं करता कि वस्तुओं के बीच बातचीत केवल अमूर्तता पर आधारित है। मुद्दा यह है कि आप उस कोड का उपयोग करते हैं जो कार्यान्वयन के बीच बातचीत की अनुमति देता है और कम लचीला है, जिसका अर्थ है कि इसकी इकाई परीक्षणों द्वारा परीक्षण की संभावना नहीं है।