Start testing your Go app in the right way

Source: Internet
Author: User
Tags assert
/** * to Yoyo * * Source
 : https://www.toptal.com/go/your-introductory-course-to-testing-with-go
 * @ Author Dogstar.huang <chanzonghuang@gmail.com> 2016-08-11
 * *

It is important to have a clear mind when learning anything new.

If you're quite unfamiliar with go and come from a language like JavaScript or Ruby, you're probably accustomed to using out-of-the-box frameworks to help you mock, assert, and do other tests of your ridicule, assertions, and other tests of witchcraft.

Now, eliminate ideas based on external dependencies or frameworks. A few years ago, when I was learning this remarkable programming language, testing was the first hurdle I encountered, when only a few resources were available.

Now I know that testing success in Go means being light-dependent (as with go everything), relying at least on external class libraries, and writing better, reusable code. This Blake Mizerany experience introduces the courage to try a third-party test library, is a good start to adjust your mind. You'll see some arguments about using the external class library and the "Go Way" framework.

Want to learn go? Take a look at our Golang introductory tutorial.

The idea of building your own test framework and simulation seems counterintuitive, but it's also easy to think that this is a good starting point for learning the language. Also, unlike what I learned at the time, you have this article as a guide throughout the common test scripts and the introduction of what I think is the best practice for effective testing and keeping the code clean.

Take the "Go" approach and eliminate the reliance on external frameworks. table Test in Go

The basic test unit-the reputation of "unit testing"-can be any part of a program, which in its simplest form requires only one input and returns an output. Let's look at a simple function that will be written for testing. Obviously, it's far from perfect and complete, but for demonstration purposes it's good enough:
Avg.go

Func AVG (nos ... int) int {  
    sum: = 0
    for _, N: = range Nos {
        sum + = n
    }
    if sum = = 0 {
        return 0
    }
    return Sum/len (NOS)
}

The above function, func Avg (nos. int), returns 0 or gives it an integer mean of a series of numbers. Now let's write a test for it.

In go, give the test file the same name as the file that contains the code to be tested, and take the additional suffix _test as the best practice. For example, the above code is a file named Avg.go, so our test file will be named Avg_test.go.

Note that these examples are just extracts from the actual files, because package definitions and imports are omitted for simplification.

Here is the test for the AVG function:

Avg_test.go

Func testavg (t *testing. T) {for  
    _, TT: = range []struct {
        nos]    []int
        Result int} {
        {nos: []int{2, 4}, Result:3},
        {NOS : []int{1, 2, 5}, Result:2},
        {nos: []int{1}, Result:1},
        {nos: []int{}, result:0},
        {nos: []int{2,-2}, Resul t:0},
    } {
        if avg: = Average (Tt.nos ...); AVG! = TT. Result {
            t.fatalf ("expected average of%v to be%d, got%d\n", TT. Nos, TT. Result, Avg)}}}

There are a few things to note about function definitions: first, the "test" prefix of the function name. This is required so that the tool detects it as an effective test. The second half of the test function name is usually the name of the function or method to be tested, and here is avg. We also need to pass in called testing. The test structure of T, which allows control of the test flow. For more details on this API, please visit this document.

Now, let's talk about the format that this example writes. A test suite (a series of tests) is running through the AGV () function, and each test contains a specific input and expected output. In our example, each test passes in a series of integers (Nos) and expects a specific return value (Result).

The table test derives its name from its structure and is easily represented as a two-column table: the input variable and the expected output variable. Golang Interface Simulation

The greatest and most powerful feature that the go language provides is called the interface. In addition to gaining the power and flexibility of the interface in the design of the program architecture, the interface provides us with an amazing opportunity to decouple components and thoroughly test them at junctions.

An interface is a collection of specified methods and is also a variable type.

Let's look at a fictitious scenario, assuming that it needs to be from IO. Reader reads the first n bytes and returns them as a string. It looks like this:
Readn.go

Readn reads at the most n bytes from R and returns them as a string.
Func Readn (R io. Reader, n int) (string, error) {  
    buf: = Make ([]byte, N)
    m, Err: = R.read (BUF)
    if err! = Nil {
        return "", E RR
    }
    return string (Buf[:m]), nil
}


Obviously, the main thing to test is the READN function, which returns the correct output when given a variety of inputs. This can be done with a tabular test. But there are also two special scenes to cover, and that is to check: Readn is called by a buffer of size n READN returns an error if an exception is thrown

To know passed to R. The size of the read buffer, and the error that controls its return, we need to simulate the R passed to Readn. If you look at the reader type in the go document, we see IO. Reader looks like this:

Type Reader Interface {  
       Read (p []byte) (n int, err error)
}

It seems quite easy. In order to satisfy IO. Reader what we need to do is have a self-simulation of a read method. So, Readermock can be like this:

Type readermock struct {  
    readmock func ([]byte) (int, error)
}

func (M readermock) Read (P []byte) (int, error) {  
    return M.readmock (p)
}

Let's analyze the above code a little bit. Any instance of Readermock clearly satisfies the Io.reader interface because it implements the necessary Read method. Our simulations also contain field readmock, which allows us to set the exact behavior of the simulation method, which makes it easy for any dynamic instance to behave.

To ensure that the interface is able to meet the needs at runtime, a great technique of not consuming memory is to insert the following code into our code:

var _ io. Reader = (*mockreader) (nil)  

This checks the assertion but does not allocate anything, which allows us to ensure that the interface is implemented correctly at compile time, before the program actually uses it to run to any function. Optional tips, but very practical.

Go ahead and let's write the first test: R. Read is called by a buffer of size n. In order to do this, we use the Readermock, as follows:

Func testreadn_bufsize (t *testing. T) {  
    total: = 0
    MR: = &mockreader{func (b []byte) (int, error) {Total
        = Len (b)
        return 0, nil
    }}
  readn (MR, 5)
    if Total! = 5 {
        t.fatalf ("Expected 5, got%d", total)
    }
}

As you can see above, we define "false" IO through a local variable. Reader function, which can be used to assert the validity of our tests later. Pretty easy.

Take a look at the second scenario that needs to be tested, which requires us to simulate read to return an error:

Func testreadn_error (t *testing. T) {  
    Expect: = errors. New ("Some Non-nil error")
    MR: = &mockreader{func (b []byte) (int, error) {
        return 0, expect
    }}
    _, Err: = Readn (MR, 5)
    if err! = expect {
        t.fatal ("expected error")
    }
}

In the above test, no matter what the call to Mr. Read (our simulated reader) will return both defined errors, so it would be reliable to assume that Readn's normal operation would do the same. Golang Method Simulation

Usually we don't need a simulation method, because instead we tend to use structs and interfaces. These are easier to control, but occasionally encounter this necessity, and I often see confusion around this topic. Someone even asked how to simulate a similar log. Println such a thing. Although it is seldom necessary to test to log. Println's input, we will use this opportunity to prove it.

Consider the following simple if statement to output records based on the value of N:

Func printsize (n int) {  
    if n < {
        log. Println ("SMALL")
    } else {
        log. Println ("LARGE")
    }
}

In the example above, let's assume such a ridiculous scenario: a specific test log. Println is called by the correct value. To simulate this feature, you first need to wrap it up:

var show = func (v. ... interface{}) {  
    log. Println (v ...)
}

The sound method in this way-as a variable-allows us to overwrite it in the test and assign it any behavior we want. Indirectly, put the log. Println's code line is replaced with show, then our program will become:

Func printsize (n int) {  
    if n < {
        Show ("SMALL")
    } else {
        show ("LARGE")}
}

Now we can test it out:

Func testprintsize (t *testing. T) {var got string oldshow: = Show Show = Func (v. ... interface{}) {if Len (v)! = 1 {t.f Atalf ("expected show to is called with 1 param, got%d", Len (v))} var ok bool got, OK = v[0]. (string) If!ok {t.fatal ("expected show to is called with a string")}} for _, TT: = range []struct{N int out string} {{2, "SMALL"}, {3, "SMALL"}, {9, "SMALL"} , {ten, "LARGE"}, {one, "LARGE"}, {, "LARGE"},} {got = "" PrintSize (TT. N) if got! = TT. Out {t.fatalf ("on%d, expected '%s ', got '%s ' \ n", TT. N, TT. Out, Got)}}//Careful though, we must no forget to restore it original value//before Fin Ishing the test, or it might interfere with other tests in our//suite, giving us unexpected and hard to trace Behavi
    Or.
 Show = Oldshow}

We should not "simulate log." Println ", but in those very occasional cases when we really need to simulate a package-level approach for good reason, the only way to do that (as I know it) is to declare it as a package-level variable so that we can control its value.

However, if we do need to simulate like log. Println such things, if you use a custom logger, we can write a more elegant solution. golang Template Rendering Test

Another fairly common scenario is to test the output of a render template as expected. Let's consider a GET request for Http://localhost:3999/welcome?name=Frank, which returns the following body:

 

If it is not clear enough now, the query parameter name matches the span label of the class name, which is not a coincidence. In this case, the obvious test should verify that this happens correctly each time the multilayer output is crossed. Here I find the Goquery class library very useful.

Goquery uses a jquery-like API to query the HTML structure, which is essential for testing the validity of the program's label output.

Now we can write our tests in this way:
Welcome__test.go

Func testwelcome_name (t *testing. T) {  
    resp, err: = http. Get ("Http://localhost:3999/welcome?name=Frank")
    if err! = Nil {
        t.fatal (err)
    }
    if resp. StatusCode! = http. Statusok {
        t.fatalf ("expected, got%d", resp. StatusCode)
    }
    doc, err: = Goquery. Newdocumentfromresponse (RESP)
    if err! = Nil {
        t.fatal (err)
    }
    if V: = Doc. Find ("H1.header-name span.name"). Text (); V! = "Frank" {
        t.fatalf ("Expected markup to contain ' Frank ', got '%s '", V)
    }
}

First, we check that the response status code is not 200/ok before processing.

I think it's not too farfetched to assume that the rest of the code snippet above is self-explanatory: we use an HTTP packet to extract the URL and create a new Goquery-compliant document based on the response, which we then use to query the returned DOM. We examined the span.name encapsulated text ' Frank ' inside the H1.header-name.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.