go语言桌面应用程序开发
问题-运行测试用例后,我们面对过多少次担心清理的问题。 当我们运行某些测试用例时,它们可能会将数据添加到我们的数据库中或将文件添加到我们的目录中,我们不想每次都担心。
解决方案-我们可以使用Docker轻松解决此问题。 想象一下,产生一个Docker容器只是为了运行测试用例,并在其目的解决之后将其杀死。 of! 解决了我们的问题,无需担心测试用例造成的附带损害,因为杀死该容器会为我们处理。
什么是码头工人?
有很多资源可以解释什么是docker和containerization。 为了以通俗易懂的方式在上下文中向您解释,它可以看作是一台免费的计算机,我们可以做一些事情,并且您可以自由地随心所欲地使用它,因为一旦完成,它将被销毁。下次您将获得全新的。
步骤1:建立我们的应用程式
我创建了一个简单的待办事项应用程序,该应用程序基本上允许用户保存,提取和删除项目。 我将为所有这些功能公开REST端点,并将使用MySQL作为数据库。
func main () {
// Initialize a connextion to DB. Sourcename contains the auth information, host information
// obtained from the env.
db, err := sql.Open( "mysql" , sourceName)
if err != nil {
panic (err)
}
Init(db)
}
// Init initializes the dependecies and boots up the server.
func Init (db *sql.DB) {
// Initialize our model
repo := todo.NewListRepository(db)
err := repo.Init()
if err != nil {
panic (err)
}
// Pass our model to our service which will handle business logic
listService := todo.NewListService(repo)
// start server
http.HandleFunc( "/save" , listService.AddItem)
http.HandleFunc( "/delete" , listService.DeleteItem)
http.HandleFunc( "/find" , listService.FindItem)
http.ListenAndServe( ":8080" , nil )
}
主文件初始化服务并启动服务器
如我们所见,我们连接到MySQL,并将连接传递到我们的模型/存储库,该模型/存储库是处理业务逻辑的服务的依赖项。
type ListService struct {
repo ListRepository
}
func NewListService (repo ListRepository) (service ListService) {
return ListService{
repo: repo,
}
}
func (service ListService) AddItem (w http.ResponseWriter, r *http.Request) {
param1 := r.URL.Query().Get( "item" )
if param1 != "" {
ID := uuid.New().String()
err := service.repo.SaveItem(ID, param1)
if err != nil {
w.Write([] byte (err.Error()))
w.WriteHeader( 400 )
return
}
w.Write([] byte (ID))
} else {
w.WriteHeader( 500 )
}
}
func (service ListService) DeleteItem (w http.ResponseWriter, r *http.Request) {
ID := r.URL.Query().Get( "ID" )
if ID != "" {
err := service.repo.DeleteItem(ID)
if err != nil {
w.Write([] byte (err.Error()))
w.WriteHeader( 400 )
return
}
w.Write([] byte ( "delete success" ))
} else {
w.WriteHeader( 500 )
}
}
...
上面是我们的ListService,它本质上是我们的控制器,具有3个端点的逻辑。
// ListRepository is an interface
type ListRepository interface {
SaveItem(ID string , name string ) (err error)
FindItem(ID string ) (res string , err error)
DeleteItem(ID string ) (err error)
Init() (err error)
}
// MySQLListRepository impl of Listrepo
type MySQLListRepository struct {
// conn sql.Conn
db *sql.DB
}
// NewListRepository is a constructor
func NewListRepository (db *sql.DB) (repo ListRepository) {
repo = MySQLListRepository{db: db}
return repo
}
func (repo MySQLListRepository) SaveItem (ID string , name string ) (err error) {
_, err = repo.db.Exec( "Insert into `items` (`ID`, `name`) values (?, ?)" , ID, name)
return
}
func (repo MySQLListRepository) FindItem (ID string ) (res string , err error) {
var row *sql.Row
row = repo.db.QueryRow( "SELECT `name` FROM `items` WHERE `ID` = ?; " , ID)
err = row.Scan(&res)
return
}
func (repo MySQLListRepository) DeleteItem (ID string ) (err error) {
var res sql.Result
var affected int64
res, err = repo.db.Exec( "DELETE FROM `items` WHERE `ID` = ?; " , ID)
affected, err = res.RowsAffected()
if err != nil {
return
}
if affected == 0 {
return errors.New( "invalid ID" )
}
return
}
func (repo MySQLListRepository) Init () (err error) {
// var result sql.Result
_, err = repo.db.Exec( `CREATE TABLE IF NOT EXISTS items (ID VARCHAR(255), name VARCHAR(255)) ENGINE=INNODB;` )
return err
}
这是我们处理数据库操作的模型。
步骤2:开始使用Docker测试它
单元测试
首先,我们将开始单元测试。 我们可以直接测试我们的模型。 这就是清除的重要性所在。正如我们所看到的,每次操作之后,我们实际上都是在修改数据库。 假设有时每个测试中有100个插入片段在运行。 在每次测试不必要地运行之后,DB都会开始填充。
我们将使用MySQL docker容器产生一个数据库,并将该连接传递给ListRepository。 在对我们的repo方法进行单元测试之后,我们将清除容器,从而避免了清理的麻烦。
通过代码启动和停止Docker容器
基本上每种语言都有docker客户端可用
我们将使用Go客户端来处理docker操作。
func TestMain (m *testing.M) {
// Create a new pool of connections
pool, err := dockertest.NewPool( "myPool" )
// Connect to docker on local machine
if err != nil {
log.Fatalf( "Could not connect to docker: %s" , err)
}
// Spawning a new MySQL docker container. We pass desired credentials for accessing it.
resource, err := pool.Run( "mysql" , "5.7" , [] string { "MYSQL_ROOT_PASSWORD=secret" })
if err != nil {
log.Fatalf( "Could not start resource: %s" , err)
}
connected := false
// Try connecting for 200secs
for i := 0 ; i < 20 ; i++ {
// Try establishing MySQL connection.
Conn, err = sql.Open( "mysql" , fmt.Sprintf( "root:secret@(localhost:%s)/mysql?parseTime=true" , resource.GetPort( "3306/tcp" )))
if err != nil {
panic (err)
}
err = Conn.Ping()
if err != nil {
// connection established success
connected = true
break
}
// Sleep for 10 sec and try again.
}
if !connected {
fmt.Println( "Couldnt connect to SQL" )
pool.Purge(resource)
}
// Run our unit test cases
code := m.Run()
// Purge our docker containers
if err := pool.Purge(resource); err != nil {
log.Fatalf( "Could not purge resource: %s" , err)
}
os.Exit(code)
}
我会解释发生了什么
我们创建了一个到docker的新连接池并尝试进行连接。docker连接成功后,我们产生了一个托管数据库的新MySQL容器。我们尝试以给定的超时循环连接到该容器,因为产生该容器将花费一些时间。 如果发生超时,请清除容器并退出。如果Conn.ping()没有返回错误,则祝贺我们已成功连接到SQL容器。运行测试用例,然后停止SQL容器。 没有清理数据库的麻烦,因为清除容器将解决它。
以下是我们测试用例的示例
func TestRepo (t *testing.T) {
assert := assert.New(t)
listRepo := todo.NewListRepository(Conn)
err := listRepo.Init()
assert.NoError(err, "error while initializing repo" )
ID := uuid.New().String()
name := "itemName 1"
// Save Item
err = listRepo.SaveItem(ID, name)
assert.NoError(err, "error while saving item" )
// Find Item
foundName, err := listRepo.FindItem(ID)
assert.NoError(err, "error while saving item" )
assert.Equal(foundName, name)
// Delete Item
err = listRepo.DeleteItem(ID)
assert.NoError(err, "error while saving item" )
foundName, err = listRepo.FindItem(ID)
assert.Error(err, "delete unsuccessful" )
}
因此,我们已经使用Docker MySQL成功测试了我们的存储库。
使用Docker进行端到端测试
以上,我们仅测试我们的存储库。 如果我们想同时使用docker测试业务逻辑和服务器怎么办? 假设我们的业务是否在我们的系统上进行了一些文件更改。 在运行测试用例时,我们还需要进行清理。
我们本质上需要做的是在一个单独的Docker容器中启动我们的应用服务器,并运行我们的e2e测试。
建立
FROM golang:latest
WORKDIR $GOPATH /src/todo
ENV GO111MODULE=on
COPY . .
RUN go build -o main .
EXPOSE 8080
CMD ./main
上面是我们应用程序的简单docker文件。 但是,此Docker容器不会安装MySQL。 我们需要在单独的容器中启动SQL并桥接网络。 我们可以使用Docker Compose做到这一点。
services:
mysql_db:
image: mysql:5.7
environment: # Set up mysql database name and password
MYSQL_ROOT_PASSWORD: password
MYSQL_DATABASE: todo
MYSQL_USER: user
MYSQL_PASSWORD: password
ports:
- 3306 :3306
networks:
- my-network
todo_app:
image: todo_app
container_name: todo_app
environment: # Set up mysql database name and password
host: 0.0 .0 .0 :3306 # Passing MySQL host
build:
context: .
dockerfile: Dockerfile
depends_on:
- mysql_db
ports:
- "8080:8080"
networks:
- my-network
networks:
my-network:
driver: bridge
这个Docker组成文件可以帮助我们同时运行服务(SQL,todo_app)和通过网桥连接它们
我们开始使用以下服务:
docker-compose up -d
这将确保我们的服务器已启动并正在运行,我们可以在下面进行e2e测试。
我们的服务器将在localhost:8080上启动,我们可以直接对其进行访问。
func TestE2E (t *testing.T) {
assert := assert.New(t)
client := &http.Client{}
req, err := http.NewRequest( "GET" , "localhost:8080/save?item=test122" , nil )
assert.NoError(err, "error while initializing request" )
res, err := client.Do(req)
assert.NoError(err, "error while making request" )
assert.Equal(res.StatusCode, 200 )
... // Other E2E requests
}
端到端测试
我们可以通过向docker服务器发出HTTP请求并声明响应来对其进行测试。
完成测试后,我们将不得不使用docker-compose down手动关闭待办事项服务器和SQL容器。 在这种情况下,我们必须手动启动和停止docker服务,但这也可以通过使用代码命令启动和停止容器来实现自动化。
因此,通过这样做,我们确保如果我们的应用在我们的系统或数据库中进行了更改,则这些更改不会持久存在,并且我们不必清理这些更改。
结论:
因此,我们看到使用docker我们可以运行e2e和单元测试,而不会造成应用程序渲染的系统混乱。 完整的代码在这里 。
先前发布在 https://medium.com/@mohdgadi52/testing-application-without-creating-a-mess-af1a26e9c2c4
翻译自: https://hackernoon.com/application-testing-in-go-do-it-right-and-dont-create-a-mess-xwfc3y19
go语言桌面应用程序开发