Database
 sql >> Teknologi Basis Data >  >> RDS >> Database

Mengapa Menggunakan Tes Unit adalah Investasi Besar untuk Arsitektur Berkualitas Tinggi

Saya telah memutuskan untuk menulis artikel ini untuk menunjukkan bahwa pengujian unit tidak hanya alat untuk bergulat dengan regresi dalam kode, tetapi juga merupakan investasi besar ke dalam arsitektur berkualitas tinggi. Selain itu, sebuah topik dalam komunitas .NET bahasa Inggris memotivasi saya untuk melakukan ini. Penulis artikel tersebut adalah Johnny. Ia menceritakan hari pertama dan terakhirnya di perusahaan yang bergerak di bidang pengembangan perangkat lunak untuk bisnis di sektor keuangan. Johnnie melamar posisi – sebagai pengembang unit test. Dia kesal dengan kualitas kode yang buruk, yang harus dia uji. Dia membandingkan kode itu dengan tempat barang rongsokan yang diisi dengan benda-benda yang saling mengkloning di tempat yang tidak cocok. Selain itu, dia tidak dapat menemukan tipe data abstrak dalam repositori:kode tersebut hanya berisi pengikatan implementasi yang saling meminta satu sama lain.

Johnnie yang menyadari semua kesia-siaan pengujian modul di perusahaan ini menjelaskan situasi ini kepada manajer, menolak kerjasama lebih lanjut, dan memberikan saran yang berharga. Dia merekomendasikan agar tim pengembangan mengikuti kursus untuk mempelajari pembuatan objek dan menggunakan tipe data abstrak. Saya tidak tahu apakah manajer mengikuti sarannya (saya pikir dia tidak). Namun, jika Anda tertarik dengan apa yang dimaksud Johnnie dan bagaimana penggunaan pengujian modul dapat memengaruhi kualitas arsitektur Anda, silakan membaca artikel ini.

Isolasi Ketergantungan adalah dasar dari pengujian modul

Pengujian modul atau unit adalah pengujian yang memverifikasi fungsionalitas modul yang diisolasi dari dependensinya. Isolasi ketergantungan adalah substitusi objek dunia nyata, yang dengannya modul yang diuji berinteraksi, dengan stub yang mensimulasikan perilaku prototipe yang benar. Substitusi ini memungkinkan fokus pada pengujian modul tertentu, mengabaikan kemungkinan perilaku yang salah dari lingkungannya. Kebutuhan untuk mengganti dependensi dalam pengujian menyebabkan properti yang menarik. Pengembang yang menyadari bahwa kode mereka akan digunakan dalam pengujian modul harus mengembangkan menggunakan abstraksi dan melakukan pemfaktoran ulang pada tanda pertama konektivitas tinggi.

Saya akan mempertimbangkannya pada contoh khusus.

Mari kita coba bayangkan seperti apa modul pesan pribadi pada sistem yang dikembangkan oleh perusahaan tempat Johnnie melarikan diri. Dan bagaimana modul yang sama akan terlihat jika pengembang menerapkan pengujian unit.

Modul harus dapat menyimpan pesan dalam database dan jika orang yang menjadi tujuan pesan ada di sistem — tampilkan pesan di layar dengan notifikasi toast.

//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);
        }
    }
}

Mari kita periksa dependensi apa yang dimiliki modul kita.

Fungsi SendMessage memanggil metode statis objek Notificationsservice dan Usersservice dan membuat objek Messagesrepository yang bertanggung jawab untuk bekerja dengan database.

Tidak ada masalah dengan fakta bahwa modul berinteraksi dengan objek lain. Masalahnya adalah bagaimana interaksi ini dibangun, dan itu tidak berhasil dibangun. Akses langsung ke metode pihak ketiga telah membuat modul kami terkait erat dengan implementasi tertentu.

Interaksi ini memiliki banyak kelemahan, tetapi yang terpenting adalah modul Messagingservice telah kehilangan kemampuan untuk diuji secara terpisah dari implementasi Notificationsservice, Usersservice dan Messagesrepository. Sebenarnya, kita tidak bisa mengganti objek ini dengan stub.

Sekarang mari kita lihat bagaimana modul yang sama akan terlihat jika pengembang mengurusnya.

//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);
        }
    }
}

Seperti yang Anda lihat, versi ini jauh lebih baik. Interaksi antar objek sekarang dibangun tidak secara langsung tetapi melalui antarmuka.

Kami tidak perlu lagi mengakses kelas statis dan membuat instance objek dalam metode dengan logika bisnis. Poin utamanya adalah kita dapat mengganti semua dependensi dengan melewatkan stub untuk pengujian ke dalam konstruktor. Jadi, sambil meningkatkan kemampuan pengujian kode, kami juga dapat meningkatkan pengujian kode dan arsitektur aplikasi kami. Kami menolak untuk langsung menggunakan implementasi dan meneruskan instantiasi ke lapisan di atas. Inilah yang diinginkan Johnnie.

Selanjutnya, buat pengujian untuk modul pengiriman pesan.

Spesifikasi pada pengujian

Tentukan apa yang harus diperiksa oleh pengujian kami:

  • Satu panggilan metode SaveMessage
  • Satu panggilan metode SendNotificationToUser() jika metode IsUserOnline() stub di atas objek IUsersService mengembalikan true
  • Tidak ada metode SendNotificationToUser() jika metode IsUserOnline() stub di atas objek IUsersService mengembalikan false

Mengikuti ketentuan ini dapat menjamin bahwa implementasi pesan SendMessage sudah benar dan tidak mengandung kesalahan.

Tes

Pengujian diimplementasikan menggunakan kerangka kerja Moq yang terisolasi

[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);
}

Singkatnya, mencari arsitektur yang ideal adalah tugas yang tidak berguna.

Tes unit sangat bagus untuk digunakan saat Anda perlu memeriksa arsitektur saat kehilangan sambungan antar modul. Namun, perlu diingat bahwa merancang sistem rekayasa yang kompleks selalu merupakan kompromi. Tidak ada arsitektur yang ideal dan tidak mungkin memperhitungkan semua skenario pengembangan aplikasi sebelumnya. Kualitas arsitektur tergantung pada beberapa parameter, seringkali saling eksklusif. Anda dapat memecahkan masalah desain apa pun dengan menambahkan tingkat abstraksi tambahan. Namun, itu tidak mengacu pada masalah sejumlah besar level abstraksi. Saya tidak menyarankan untuk berpikir bahwa interaksi antar objek hanya didasarkan pada abstraksi. Intinya adalah Anda menggunakan kode yang memungkinkan interaksi antar implementasi dan kurang fleksibel, yang berarti tidak memiliki kemungkinan untuk diuji oleh unit test.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Bagaimana cara menggunakan COUNT dalam SQL?

  2. Menerapkan pengurutan khusus

  3. Statistik Buatan Kustom

  4. Beberapa Cara Menyisipkan Split Delimited Strings dalam Kolom

  5. Apakah Sertifikat Profesional Google Data Analytics Layak?