Go Through 추상화에서 데이터베이스 상호 작용을 테스트하는 방법

13330 단어 unittestingdatabasego

내용의 테이블


  • Unit tests
  • Strategies for creating unit tests
  • To use or not to use mocking libraries?
  • Is Golang an OOP language?
  • Interfaces
  • Abstraction
  • The coded solution
  • Conclusion



  • LTV의 엔지니어링 팀은 코드를 작성하면서 테스트하는 데 관심이 있습니다. 코드를 더 탄력적으로 만들고 의도하지 않은 결과가 발생할 가능성을 줄이며 코드를 반복적으로 배포할 수 있는 자신감을 줍니다. 많은 유형의 테스트가 있지만(예: 단위 테스트, 회귀 테스트, 통합 테스트, 보안 테스트 등 [1] ) 이 게시물은 단위 테스트에 중점을 둡니다.


    단위 테스트

    A unit test is a low-level test that accesses a small component of our software and follows these steps:

    1. Prepares the “environment” to make sure that the portion of code to be tested is going to be functional when it is executed.
    2. Updates the environment with fixed arguments or updates needed according to the test case.
    3. Runs the component that is being tested.
    4. Compares the results with what is expected.

    When we say “environment”, we are referring to artifacts in the application such as environment variables and arguments to the code under test, rather than external factors such as ports, internet status or system memory.

    Understanding why tests must be isolated is important. There are many variables outside our code that can affect the behavior of the program. When the test runs, having those dependencies could make our tests return different results every time they run. One example of this is when a program interacts with a database, there are many errors that can happen while our program is running: wrong credentials were used, the database just restarted, a query had a bad format, a deadlock occurs, a port is already in use, etc. To avoid some situations (if they are not relevant for our tests), it’s a good idea to abstract the database interaction and use a mock 우리가 테스트해야 할 부분에 대해.

    우리는 각 테스트를 다른 테스트와 독립적인 독립적인 단위로 작성합니다. 즉, 테스트 T1, T2 및 T3이 있으면 각각 블랙 박스처럼 격리되어 실행될 수 있습니다. "하위 테스트"는 전적으로 부모와 관련이 있으며 서로 격리되어야 한다는 점을 언급할 가치가 있습니다. 다음 이미지는 이 아이디어를 명확하게 보여줍니다.



    이 이미지에서 다음을 볼 수 있습니다.

  • T1, T2 및 T3는 그들 사이에 격리되어 있습니다.

  • 아버지 T1의 상태와 관련된 T1-1 및 T1-2.

  • T1-1과 T1-2는 그들 사이에 격리되어 있습니다.



  • 단위 테스트 생성 전략

    Creating unit tests is not always simple. Keep a few important points in mind while you create your tests:

    • Keep them isolated: Keep tests isolated from databases, the file system, OS services, networking interaction, etc. Because if they fail, the result of our tests will change for reasons unrelated to the code. If we keep these dependencies, we may end up with many different results for the same tests while they are running and the review of all those possibilities may waste time.
    • Use interfaces in your code: By using interfaces, we can interchange objects or even use a custom object (like a mock) that implements the interface with specific behavior.
    • Use independent tests: Keep each test independent to ensure that one test will not change the result of others.
    • Think about all possible cases: Think about the happy path and potential unhappy paths when writing your test cases.

    조롱 라이브러리를 사용하거나 사용하지 않으려면?

    There are different mocking libraries for Golang, and we have done some analysis to understand if using them is beneficial.

    Even if libraries reduce coding time and are versatile tools that allow us to mock or stub, they could add unnecessary and unused code that make the tests difficult to understand. What you could end up with is obscure code that is not easily understood nor maintained.

    Granted, we write more code by not using mocking libraries, but it shouldn’t be an arduous task because our code should be modular, easy to understand and well decoupled. It also depends on the nature of what we are testing.

    A very important aspect of writing our own mocks is that the tests explain the main code by themselves, so they really complement the code.


    Golang은 OOP 언어입니까?

    In the following sections there will be more Golang code, and we found meaningful to answer if Golang is an object-oriented programming (OOP) language and as the Golang team explains, the answer is “yes and no”, as you can see on their official web page: https://golang.org/doc/faq#Is_Go_an_object-oriented_language .

    우리의 목적을 위해 그것이 객체 지향 프로그래밍 언어라는 데 동의합시다.


    인터페이스

    An interface is a type or structure that defines the methods (with their signatures) that need to be implemented for any object to fulfill it. This allows us to have objects that implement this interface with different behaviors.

    To clarify this let’s see an example:

    Having an interface “Teacher”.

    type Teacher interface {
        GetMainCourse() string
        SetName(string)
    }
    

    We could have an object that implements this interface, which could be achieved by adding methods that follow the signatures of the methods defined in the interface.

    type CSTeacher struct {
        name       string
        mainCourse string
        salary     float64
    }
    
    func (cst *CSTeacher) GetMainCourse() string {
        return cst.mainCourse
    }
    
    func (cst *CSTeacher) GetSalary() float64 {
        return cst.salary
    }
    
    func (cst *CSTeacher) SetName(name string) {
        cst.name = name
    }
    

    We can add more methods as needed but we should implement the ones defined in the interface at a minimum. Golang is an intelligent language such that it checks if the contracts are being followed in order to define if an interface is being implemented. We don’t need to specify that CSTeacher implements Teacher in the above example. This way we can abstract the solution to our problem using interfaces.


    추출

    Before we go deeper into our solution for testing database interaction in Golang through abstraction, we should define what we mean by abstraction.

    Abstraction in programming is a design technique where we avoid the irrelevant details of something and keep the relevant ones exposed to the exterior world.

    The same is available in Object Oriented Programming (“OOP”) where we can expose the methods for a class (as public methods) and hide their implementation (e.g., We usually don’t care about the implementation of a method of a library, we just use it) and also hide methods (as private methods) that are usually helper methods in a library (e.g. Methods like validators that do something specific for the internal use of a library and we don’t need to export them).

    We can take advantage of interfaces that will allow us to use a custom implementation of the exported methods with the needed behavior, or just add new methods to the object that implements the interface.

    Are we talking about abstract database interactions for testing?

    Yes, database libraries export some methods and their implementation is not very important to us. But their behavior could be important, so we define interfaces in our code to use custom implementations of the interfaces and use a custom implementation for our tests.


    코딩된 솔루션

    In this section we present a code snippet to show how to use interfaces and mocks with the application of abstraction to test how our code interacts with external factors like a database.

    인터페이스 만들기

    First, let’s create the interfaces that will allow us to keep the database library code and implemented mocks separate.

    package mydb
    
    import (
        "context"
    )
    
    // IDB is the interface that defines the methods that must be implemented.
    type IDB interface {
        QueryRowContext(ctx context.Context, query string, args ...interface{}) IRow
    }
    
    // IRow defines the methods that belong to a Row object.
    type IRow interface {
        Scan(dest ...interface{}) error
    }
    

    모의 만들기

    We can create mocks with different behaviors as needed.

    package mydb_test
    
    import (
        "context"
        "some_path/.../mydb"
    )
    
    type dbMock struct {
        queryRowContextResult *rowMock
    }
    
    // QueryRowContext queries a database.
    func (db *dbMock) QueryRowContext(ctx context.Context, query string, args ...interface{}) mydb.IRow {
        // Here we can do something with the arguments received,
        // like saving the query to validate it later, etc.
        return db.queryRowContextResult
    }
    
    type rowMock struct {
        scanError   error
        valuesInRow []interface{}
    }
    
    // Scan reads a value from a row.
    func (r *rowMock) Scan(dest ...interface{}) error {
        if r.scanError != nil {
            return r.scanError // We can have a specific error.
        }
    
        // Specific and customized scan code goes here, using valuesInRow if you want.
    
        return nil
    }
    

    인터페이스 사용

    In the code below, the method ValidateStudents receives an interface as a parameter, so we can pass it the real database object or use a custom mock.

    In the tests, we pass our mock objects and in the production code we pass the objects that the database library provides.

    package blog
    
    import (
        "context"
        "some_path/.../mydb"
    )
    
    // ValidateStudents does some validation over the recorded students.
    func ValidateStudents(ctx context.Context, db mydb.IDB) error {
        students := NewStudents()
        err := db.QueryRowContext(ctx, "SELECT ...").Scan(&students)
        if err != nil {
            return err
        }
    
        // Code that validates students would go here.
    
        return nil
    }
    

    결론

    Testing is an important part of any project. Without tests, we are less equipped to recognize failing code before placing into production and our end-user experience could suffer.

    To achieve the isolation in unit testing we can apply abstraction. With abstraction and the use of interfaces, we can have modules decoupled to interchange between a mocked module for testing and another module used for production.


    Interested in working with us? Have a look at our careers page 저희 팀의 일원이 되고 싶다면 저희에게 연락하세요!


    서지

    Softwaretestinghelp.com. n.d. Types Of Software Testing: Different Testing Types With Details. [online] Available at: https://www.softwaretestinghelp.com/types-of-software-testing/ [2020년 6월 24일 액세스].

    좋은 웹페이지 즐겨찾기