1
0

tex: add stuff on integration testing

This commit is contained in:
leo 2023-05-24 16:41:45 +02:00
parent 6c2ccf4206
commit 81412b8898
Signed by: wanderer
SSH Key Fingerprint: SHA256:Dp8+iwKHSlrMEHzE3bJnPng70I7LEsa3IJXRH/U+idQ

@ -1150,12 +1150,143 @@ intended/expected.
\n{2}{Integration tests}
Integrating with external software, namely the database in case of this program
is designed to utilise the same mechanism that was mentioned in the previous
section: Go's \texttt{testing} package. These test verify that the code changes
can still perform the same actions with the external software that were
possible before the change and are equally run before every commit locally and
then in the CI.
Integrating with external software, namely the database in case of this
program, is designed to utilise the same mechanism that was mentioned in the
previous section: Go's \texttt{testing} package. These tests verify that the
code changes can still perform the same actions with the external software that
were possible before the change and are run before every commit locally and
then after pushing to remote in the CI.
\n{3}{func TestUserExists(t *testing.T)}
An example integration test shown in Figure~\ref{fig:integrationtest} can be
seen to declare a helper function \texttt{getCtx() context.Context}, which
takes no arguments and returns a new \texttt{context.Context} initialised with
a value of the global logger, which is how the logger gets injected into the
user module functions. The function \texttt{TestUserExists(t *testing.T)} first
declares a database connection string and attempting to open a connection to
the database. The database in use here is SQLite3 running in memory mode,
meaning no file is actually written to disk during this process. Since the
testing data is not needed after the test, this is deemed good enough. Next, a
defer statement calling the \texttt{Close()} method on the database object is
made, which is the idiomatic Go way of closing files and network connections
(which are also an abstraction over files on UNIX-like operating systems such
as GNU/Linux). The \emph{defer} statement gets called when after all of the
statements in the surrounding function, which makes sure no file descriptors
(FDs) are leaked and the file is properly closed when the function returns.
In the next step a database schema creation is attempted, handling the
potential error in a Go idiomatic way, which uses the return value from the
function in an assignment to a variable declared in the \texttt{if} statement,
and checks whether the \texttt{err} was \texttt{nil} or not. In case the
\texttt{err} was not \texttt{nil}, i.e.\ \emph{there was an error in the callee
function}, the condition evaluates to \texttt{true}, which is followed by
entering the inner block. Inside the inner block the error is announced to the
user (likely a developer running the test in this case) and the testing
object's \texttt{FailNow()} method is called, which marks the test function as
having failed and stops its execution, which in this case is the desired
outcome, since if the database schema creation call fails there really is no
point in continuing testing of user creation.
Conversely, if the schema does get created without an error, the code continues
to declare a few variables: \texttt{username}, \texttt{email} and \texttt{ctx},
to which the context injected with the logger is saved. Some of them are
subsequently passed into the \texttt{UsernameExists} function, context as the
first argument, with the database pointer and username being passed next, while
the \texttt{email} variable is only used at a later stage, but was declared
here to give a sense of grouping. The error value returned from this function
is again checked and if everything goes well, the value of the
\texttt{usernameFound} boolean is checked next. Since the database has just
been created, there should be no users, which is checked in the next
\texttt{if} statement. The same check is then performed for the
earlier-declared user email that is also expected to fail.
The final statements of the described test attempts a user creation call, which
is again checked for both error and \emph{nilability}. The test continues with
more similar checks but it has been cut short for brevity.
A neat thing about error handling in Go is it allows for very easy checking of
all paths, not just the \emph{happy path} where there are no issues.
\begin{figure}[!h]
\centering
\scriptsize
\begin{varwidth}{\linewidth}
\begin{verbatim}
// modules/user/user_test.go
package user
import (
"context"
"testing"
"git.dotya.ml/mirre-mt/pcmt/ent/enttest"
"git.dotya.ml/mirre-mt/pcmt/slogging"
_ "github.com/xiaoqidun/entps"
)
func getCtx() context.Context {
l := slogging.Init(false)
ctx := context.WithValue(context.Background(), CtxKey{}, l)
return ctx
}
func TestUserExists(t *testing.T) {
connstr := "file:ent_tests?mode=memory&_fk=1"
db := enttest.Open(t, "sqlite3", connstr)
defer db.Close()
if err := db.Schema.Create(context.Background()); err != nil {
t.Errorf("failed to create schema resources: %v", err)
t.FailNow()
}
username := "dude"
email := "dude@b.cc"
ctx := getCtx()
usernameFound, err := UsernameExists(ctx, db, username)
if err != nil {
t.Errorf("error checking for username {%s} existence: %q",
username,
err,
)
}
if usernameFound {
t.Errorf("unexpected: user{%s} should not have been found",
username,
)
}
if _, err := EmailExists(ctx, db, email); err != nil {
t.Errorf("unexpected: user email '%s' should not have been found",
email,
)
}
usr, err := CreateUser(ctx, db, email, username, "so strong")
if err != nil {
t.Errorf("failed to create user, error: %q", err)
t.FailNow()
} else if usr == nil {
t.Error("got nil usr back")
t.FailNow()
}
if usr.Username != username {
t.Errorf("got back wrong username, want: %s, got: %s",
username, usr.Username,
)
}
// ...more checks...
}
\end{verbatim}
\end{varwidth}
\caption{Example integration test}
\label{fig:integrationtest}
\end{figure}
\n{2}{Click-ops}