Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: Updating docs for e2e tests #1625

Merged
merged 11 commits into from
Mar 22, 2021
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ default.etcd
api/build-tools/apisix
/*.zip
.githash
.backup.yaml

# backend unit test output
api/coverage.txt
Expand Down
158 changes: 158 additions & 0 deletions api/test/docker/setup.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
#!/usr/bin/env bash
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

#Executes commands from the intended directory "/api/test/docker".
cd $(dirname $0)

main() {
#welcome message & check machine configuration
welcome
check

if [ $# -eq 0 ]; then
help
give_up "\nNo arguments provided."
fi

UPFLAG="-d"
UP=0
for arg in "$@"; do
case "$arg" in
up | -u | -U)
UP=1
;;
--non-detach)
UPFLAG=""
;;
down | -d | -D)
down
;;
build | -b | -B)
build
;;
help | -h | --help | -H)
help
;;
*)
echo -e "Invalid Argument.\n"
help
give_up
;;
esac
done

if [ $UP -eq 1 ]; then
up $UPFLAG
fi
echo "Execution complete."
}

check() {
set -e

if ! docker --version &>/dev/null; then
give_up "The script depends on docker. Please proceed after the installation."
fi

if ! docker-compose --version &>/dev/null; then
give_up "The script depends on docker-compose. Please proceed after the installation."
fi

if ! sed --version &>/dev/null; then
give_up "The script depends on sed (GNU Stream Editor). Please proceed after the installation."
fi
}

CONF_PATH=$(pwd | sed -e 's/test\/docker/conf\//g')
YAML_FILE="${CONF_PATH}conf.yaml"
BACKUP_FILE="${CONF_PATH}.backup.yaml"

up() {
set -e
if [ ! -f "Dockerfile-apisix" ]; then
echo "Downloading Dockerfile"
if ! curl --version &>/dev/null; then
give_up "The script depends on curl. Please proceed after the installation."
else
curl -o Dockerfile-apisix https://raw.githubusercontent.com/apache/apisix-docker/master/alpine/Dockerfile
fi
fi

#creating backup of current config
if [ ! -f "$BACKUP_FILE" ]; then
cp "$YAML_FILE" "$BACKUP_FILE"

#modifying config
sed -i 's/127.0.0.1:2379/172.16.238.10:2379/' "$YAML_FILE"
sed -i 's/127.0.0.1/0.0.0.0/' "$YAML_FILE"
sed -i '/172.16.238.10:2379/a\ - 172.16.238.11:2379' "$YAML_FILE"
sed -i '/172.16.238.10:2379/a\ - 172.16.238.12:2379' "$YAML_FILE"
sed -i '[email protected]/[email protected]/0@' "$YAML_FILE"
sed -i 's@# - dubbo-proxy@- dubbo-proxy@' "$YAML_FILE"

fi

#Spinning up services
if [ -z "$1" ]; then
docker-compose up
else
docker-compose up "$1"
fi
}

down() {
#restoring old configuration.
mv "$BACKUP_FILE" "$YAML_FILE"

docker-compose down -v
}

build() {
docker-compose build
}

help() {
echo -e 'Usage: ./setup.sh [OPTIONS]...\n'
echo "Single argument is mandatory."
echo "Supported Command Arguments"
echo -e ' up,\t-u\t spins up all the services in detach mode.'
echo -e ' --non-detach\t pass the flag with "up / -u" to spin up in non-detach mode.'
echo -e ' down,\t-d\t stop containers & delete containers, networks, volumes.'
echo -e ' build,\t-b\t rebuild all the images from dockerfile.'
echo -e ' help,\t-h\t info about how to use this script.'

echo -e '\nFull documentation at <https://github.com/apache/apisix-dashboard/blob/master/docs/en/latest/back-end-e2e.md>'
}

give_up() {
echo -e "$1"
exit 1
}

welcome() {
echo "WELCOME TO THE SETUP SCRIPT"
echo "It spins up a fleet of services required for local development."
echo "Capable of making the necessary changes in yaml file & revert back to original state."
echo "Services: "
echo "1. Three etcd nodes."
echo "2. Two apisix nodes."
echo -e "3. Single node of\n\t manager-api \n\t skywalking \n\t upstream-node"
echo -e "=====================================================\n"
}

main "$@"
76 changes: 53 additions & 23 deletions docs/en/latest/back-end-e2e.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,23 +82,25 @@ This document describes how to use E2E test locally.

1. [install docker-compose](https://docs.docker.com/compose/install/)

**NOTE:** In order to run docker compose locally, please change the values of `listen.host` and `etcd.endpoints` within `./api/conf/conf.yaml` as follows:

```sh
listen:
host: 0.0.0.0
port: 9000
etcd:
endpoints:
- 172.16.238.10:2379
- 172.16.238.11:2379
- 172.16.238.12:2379
```
**NOTE:** In order to run docker compose locally, please change the values of `listen.host` and `etcd.endpoints` within `./api/conf/conf.yaml` as follows:

```sh
listen:
host: 0.0.0.0
port: 9000
etcd:
endpoints:
- 172.16.238.10:2379
- 172.16.238.11:2379
- 172.16.238.12:2379
```

2. Use `docker-compose` to run services such as `manager-api`, `apisix`, `etcd` and `upstream-node`, run the command.

```sh
cd /(Your apisix-dashboard folder path)/api/test/docker
# Download the apisix dockerfile
curl -o Dockerfile-apisix https://raw.githubusercontent.com/apache/apisix-docker/master/alpine/Dockerfile
starsz marked this conversation as resolved.
Show resolved Hide resolved
docker-compose up -d
```

Expand All @@ -113,23 +115,51 @@ etcd:
```

4. Then you need to delete the image of the `manage-api`, rebuild the image of the `manage-api`, and start the cluster after the image is successfully built.
(Only if you have altered/added any core functionalities in `manager-api`, for simply adding/deleting a test case/file, rebuilding is not required).

** For ease of access and to avoid the repetitive hassle for setting up the required configurations, we have provided a `setup.sh` script
which is inside `api/test/docker` directory. You can directly run, delete and build services along with update and revert `conf.yaml` through the script.
For more details, run

```sh
./setup.sh help
```

(If you are setting up the environment for the first time, please go with the described manual steps. It'll help you to get the idea of what's going on in the background).

## Start test

1. After all the services are started, you can start the back-end E2E test.

**NOTE:** Sometimes we need to delete the etcd store info. Otherwise, it will make the test failed.
**NOTE:** Sometimes we need to delete the etcd store info. Otherwise, it will make the test failed.

2. Enter the E2E folder and execute the command to test all E2E test files.
- Enter the E2E folder and execute the command to test all E2E test files.

```sh
cd /(Your apisix-dashboard folder path)/api/test/e2e
go test -v
```
```sh
cd /(Your apisix-dashboard folder path)/api/test/e2e
go test -v
```

3. You can also do E2E test on a single file.
- You can also do E2E test on a single file.

```sh
cd /(Your apisix-dashboard folder path)/api/test/e2e
go test -v E2E-test-file.go base.go
```
```sh
cd /(Your apisix-dashboard folder path)/api/test/e2e
go test -v E2E-test-file.go base.go
```

2. Currently, a lot of tests has been migrated to E2ENEW folder using the ginkgo testing framework for its ability to provide
high expressiveness which makes reading and writing tests a pleasure.

- Enter the E2ENEW folder and execute the command to run all the E2ENEW test suites recursively.

```sh
cd /(Your apisix-dashboard folder path)/api/test/e2enew
ginkgo -r
```

- You can also run a single E2ENEW test suite using ginkgo.

```sh
cd /(Your apisix-dashboard folder path)/api/test/e2enew/(path of the specific test suite)
ginkgo -r
```
98 changes: 98 additions & 0 deletions docs/en/latest/back-end-tests.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
---
title: Backend Tests
juzhiyuan marked this conversation as resolved.
Show resolved Hide resolved
---

<!--
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
-->

This document describes how to write unit tests & e2e tests for the backend.

## Writing Unit Tests

Currently, all of the unit tests for `manager-api` have been written using Go's built-in testing package. There is nothing new about it. You can directly add tests in the existing `<module>_test.go` file or create a new one. There is one thing that needs to be addressed that is, since `manager-api` largely depends on handling data from etcd, in some cases, you need to write some feature that depends on storing & retrieval of information on and out of etcd. In such a scenario, you should write your unit tests using `store.MockInterface` instead of directly depending upon etcd.

The `MockInterface` embeds `mock.Mock` object from [mock](https://pkg.go.dev/github.com/stretchr/testify/mock) package by testify. If helps in simulating function calls of an object with desired inputs as arguments and outputs as return values. Currently, all the unit tests in `route`, `service`, `ssl` and `upstream` handlers uses mock interface. For eg.

```go
mStore := &store.MockInterface{}
mStore.On("<exact methodname of the real method>", mock.Anything)
.Run(func(args mock.Arguments) {
//arguements assertions or anything
//gets executed before returning
})
.Return("<same return signature of the original method>")
```

You may tinker with the mentioned tests to get an idea of how it works or go through the [docs](https://pkg.go.dev/github.com/stretchr/testify/mock#pkg-index).

## Writing E2E Tests

Currently, the backend of apisix-dashboard have two types of e2e tests. One is plain e2e, the other is e2enew, where in the first one, tests are written using Go's built-in, native testing package, for the later, the tests are grouped into test suites and are evaluated using [ginkgo](https://onsi.github.io/ginkgo/) - a testing framework which helps in writing more expressive tests such that reading and writing tests give a pleasant experience.

**Slowly, we are migrating all of our e2e tests to e2enew. So it is always recommended to write any new tests using ginkgo unless some situation arises. In such cases, please discuss your concerns with the community.

For value assertion, we are using the [assert](https://pkg.go.dev/github.com/stretchr/[email protected]/assert) package by testify. It provides lots of easy to use functions for assertion where the first argument is `*testing.T` object which you can obtain from the used framework for testing. For the built-in testing package, each test cases have this as the first argument of the test itself. For ginkgo `ginkgo.GinkgoT()` returns the mentioned object.

If you are creating any test which requires making HTTP calls to any of the following node which involves `manager-api` or `apisix`, after setting up the environment (please refer [`backend-e2e.md`](./back-end-e2e.md) for the details), you can use the `HttpTestCase` struct which provides a nice interface to make the calls along with checking the response. Here's a brief description of the most used fields of the struct,

```go
type HttpTestCase struct {
Desc string // Description about the test case.
Object *httpexpect.Expect // returns a httpexpect object i.e. on which host the request is going to be made.
Method string // HTTP request methods ( GET, POST, PATCH, PUT, DELETE, OPTIONS).
Path string // the route path of that host
Query string // Query params
Body string // The request Body. Commonly used in POST, PUT, PATCH.
Headers map[string]string // Request headers. Include authorization header for secure routes.
ExpectStatus int // Expected HTTP status code from the response
ExpectCode int // Code generated by the host. Generally 0 for http.StatusOK.
ExpectMessage string // The response message provided in the response by the host.
ExpectBody interface{} // The expected message body as a response.
Sleep time.Duration //ms // Cooldown period before making next request.
}
```

Now to run a test use `RunTestCase(tc HttpTestCase)` or `testCaseCheck(tc HttpTestCase, t *testing.T)` for the `e2enew` or `e2e` respectively.
starsz marked this conversation as resolved.
Show resolved Hide resolved

**NOTE:** Both e2e and e2enew provides standalone methods for making HTTP request for GET, POST, PUT, DELETE methods along with making a POST request with `multipart/form` data.

Now coming back to writing e2e tests,

1. Adding tests in `api/test/e2e` is dead simple.
- Create a function starting with `Test`"\<FuncName\>" in a relevant file present in the directory or create a new one. Then write the necessary logic and check the result using assertion.

2. adding tests in `api/test/e2enew`, it's always recommended to go through the ginkgo [docs](https://onsi.github.io/ginkgo/) first.

- To create a new tests suite, create the new directory under `e2enew`. Then use

```sh
mkdir <dirname> #inside e2enew
cd <dirname>
ginkgo bootstrap # Generates <dirname>_suite_test.go
#to add tests in separate files
ginkgo generate <testgroup> #Generates <testgroup>_test.go
```

- This can be done manually, however, grouping similar tests in specific test files is recommended. Try to separate tests in separate test files.

- We use different ginkgo containers for writing tests which includes `Describe`, `It`, `AfterSuite`, `BeforeEach` etc. [ [ref](https://onsi.github.io/ginkgo/#structuring-your-specs) ]

- It is always recommended to use ginkgo's table-driven tests for running the independent `HttpTestCase` using `table.DescribeTable` and `table.Entry` [ [ref](https://pkg.go.dev/github.com/onsi/ginkgo/extensions/table) ].

- FYI, internally ginkgo reduces each table entries to `It` block and run all the `It` blocks concurrently/parallelly. Ginkgo auto recovers from panics inside `It` blocks only, so always put your assertions inside `It` containers.