One place for hosting & domains

      How To Write Unit Tests in Go


      The author selected the FreeBSD Foundation to receive a donation as part of the Write for DOnations program.

      Introduction

      A unit test is a function that tests a specific piece of code from a program or package. The job of unit tests is to check the correctness of an application, and they are a crucial part of the Go programming language.

      In this tutorial, you will create a small program and then run a series of tests on your code using Go’s testing package and the go test command. Once you complete the tutorial, you will have a working unit-testing suite that includes a table-based unit test, a coverage test, a benchmark, and a documented example.

      Prerequisites

      To complete this tutorial, you’ll need the following:

      Note: This tutorial uses Go Modules, which is a package management system introduced in Go version 1.11. Go Modules are intended to replace the $GOPATH and became the default option starting with Go version 1.13. For a more comprehensive overview of the differences between Go Modules and the $GOPATH, consider reading this official blog post from the Go core team.

      This tutorial was tested using Go version 1.14

      Step 1 — Creating a Sample Program to Unit Test

      Before you can write any unit tests, you need some code for your tests to analyze. In this step, you will build a small program that sums two integers. In the subsequent steps, you will use go test to test the program.

      First, create a new directory called math:

      Move inside the new directory:

      This will be the root directory for your program, and you will run all remaining commands from here.

      Now, using nano or your preferred text editor, create a new file called math.go:

      Add the following code:

      ./math/math.go

      package math
      
      // Add is our function that sums two integers
      func Add(x, y int) (res int) {
          return x + y
      }
      
      // Subtract subtracts two integers
      func Subtract(x, y int) (res int) {
          return x - y
      }
      

      Here you are creating two functions called Add and Subtract. Each function accepts two integers and returns either their sum (func Add) or their difference (func Subtract).

      Save and close the file.

      In this step, you wrote some code in Go. Now, in the following steps, you will write some unit tests to ensure that your code functions properly.

      Step 2 — Writing Unit Tests in Go

      In this step, you will write your first test in Go. Writing tests in Go requires a test file link, and this test file must always end with _test.go. By convention, Go testing files are always located in the same folder, or package, where the code they are testing resides. These files are not built by the compiler when you run the go build command, so you needn’t worry about them ending up in deployments.

      And as with everything in Go, the language is opinionated about testing. The Go language provides a minimal yet complete package called testing that developers use alongside the go test command. The testing package provides some useful conventions, such as coverage tests and benchmarks, which you will now explore.

      Use your editor to create and open a new file called math_test.go:

      A test function in Go includes this signature: func TestXxxx(t *testing.T). This means that all test functions must start with the word Test, followed by a suffix whose first word is capitalized. Test functions in Go receive only one parameter, and in this case, it’s a pointer of type testing.T. This type contains useful methods that you will need to output results, log errors to the screen, and signal failures, like the t.Errorf() method.

      Add the following code to math_test.go:

      ./math/math_test.go

      
      package math
      
      import "testing"
      
      func TestAdd(t *testing.T){
      
          got := Add(4, 6)
          want := 10
      
          if got != want {
              t.Errorf("got %q, wanted %q", got, want)
          }
      }
      

      First, you declare the name of the package that you want to test—math. Then you import the testing package itself, which then makes available the testing.T type and the other types and methods exported by the package. The code and testing logic is contained in the TestAdd function.

      To summarize, the following are characteristics of a test in Go:

      • The first and only parameter must be t *testing.T
      • The testing function begins with the word Test followed by a word or phrase that starts with a capital letter (the convention is to use the name of the method under test, e.g., TestAdd)
      • The test calls t.Error or t.Fail to indicate a failure (you are calling t.Error because it returns more detail than t.Fail)
      • You can use t.Log to provide non-failing debug information
      • Tests are saved in files using this naming convention: foo_test.go, such as math_test.go.

      Save and then close the file.

      In this step, you wrote your first test in Go. In the next step, you will begin using go test to test your code.

      Step 3 — Testing Your Go Code Using the go test command

      In this step, you will test your code. go test is a powerful subcommand that helps you automate your tests. go test accepts different flags that can configure the tests you wish to run, how much verbosity the tests return, and more.

      From your project’s root directory, run your first test:

      You will receive the following output:

      Output

      PASS ok ./math 0.988s

      PASS means the code is working as expected. When a test fails, you will see FAIL.

      The go test subcommand only looks for files with the _test.go suffix. go test then scans those file(s) for special functions including func TestXxx and several others that we will cover in later steps. go test then generates a temporary main package that calls these functions in the proper way, builds and runs them, reports the results, and finally cleans everything up.

      Our go test is probably sufficient for our little program, but there will be times when you’ll wish to see what tests are running and how long each takes. Adding the -v flag increases verbosity. Rerun your test with the new flag:

      You will see the following output:

      Output

      === RUN TestAdd --- PASS: TestAdd (0.00s) PASS ok ./math 1.410s

      In this step, you ran a basic unit test using the go test subcommand. In the next step, you will write a more complex, table-driven unit test.

      Step 4 — Writing Table-Driven Tests in Go

      A table-driven test is like a basic unit test except that it maintains a table of different values and results. The testing suite iterates over these values and submits them to the test code. Using this approach, we get to test several combinations of inputs and their respective output.

      You will now replace your unit test with a table of structs, whose fields include the necessary two arguments (two integers) and the expected result (their sum) for your Add function.

      Reopen math_test.go:

      Delete all the code in the file and add the following table-driven unit test instead:

      ./math/math_test.go

      
      package math
      
      import "testing"
      
      // arg1 means argument 1 and arg2 means argument 2, and the expected stands for the 'result we expect'
      type addTest struct {
          arg1, arg2, expected int
      }
      
      var addTests = []addTest{
          addTest{2, 3, 5},
          addTest{4, 8, 12},
          addTest{6, 9, 15},
          addTest{3, 10, 13},
      
      }
      
      
      func TestAdd(t *testing.T){
      
          for _, test := range addTests{
              if output := Add(test.arg1, test.arg2); output != test.expected {
                  t.Errorf("Output %q not equal to expected %q", output, test.expected)
              }
          }
      }
      
      

      Here you are defining a struct, populating a table of structs that include the arguments and expected results for your Add function, and then writing a new TestAdd function. In this new function, you iterate over the table, run the arguments, compare the outputs to each expected result, and then returning any errors, should they occur.

      Save and close the file.

      Now run the test with the -v flag:

      You will see the same output as before:

      Output

      === RUN TestAdd --- PASS: TestAdd (0.00s) PASS ok ./math 1.712s

      With each iteration of the loop, the code tests the value calculated by the Add function against an expected value.

      In this step, you wrote a table-driven test. In the next step, you will write a coverage test instead.

      Step 5 — Writing Coverage Tests in Go

      In this step, you will write a coverage test in Go. When writing tests, it is often important to know how much of your actual code the tests cover. This is generally referred to as coverage. This is also why you have not written a test for your Subtract function — so we can view an incomplete coverage test.

      Run the following command to calculate the coverage for your current unit test:

      • go test -coverprofile=coverage.out

      You will receive the following output:

      Output

      PASS coverage: 50.0% of statements ok ./math 2.073s

      Go saved this coverage data in the file coverage.out. Now you can present the results in a web browser.

      Run the following command:

      • go tool cover -html=coverage.out

      A web browser will open, and your results will render:

      go unit testing coverage.out

      The green text indicates coverage, whereas the red text indicates the opposite.

      In this step, you tested the coverage of your table-driven unit test. In the next step, you will benchmark your function.

      Step 6 — Writing Benchmarks in Go

      In this step, you will write a benchmark test in Go. Benchmarking measures the performance of a function or program. This allows you to compare implementations and to understand the impact of the changes you make to your code. Using that information, you can reveal parts of your Go source code worth optimizing.

      In Go, functions that take the form func BenchmarkXxx(*testing.B) are considered benchmarks. go test will execute these benchmarks when you provide the -bench flag. Benchmarks are run sequentially.

      Let’s add a benchmark to our unit test.

      Open math_test.go:

      Now add a benchamrk function using the func BenchmarkXxx(*testing.B) syntax:

      ./math_test.go

      ...
      func BenchmarkAdd(b *testing.B){
          for i :=0; i < b.N ; i++{
              Add(4, 6)
          }
      }
      

      The benchmark function must run the target code b.N times, where N is an integer that can be adjusted. During benchmark execution, b.N is adjusted until the benchmark function lasts long enough to be timed reliably. The --bench flag accepts its arguments in the form of a regular expression.

      Save and close the file.

      Now let’s use go test again to run our benchmark:

      The . will match every benchmark function in a file.

      You can also declare benchmark functions explicitly:

      Run either command, and you will see an output like this:

      Output

      goos: windows goarch: amd64 pkg: math BenchmarkAdd-4 1000000000 1.07 ns/op PASS ok ./math 2.074s

      The resulting output means that the loop ran 10,000,000 times at a speed of 1.07 nanosecond per loop.

      Note: Try not to benchmark your Go code on a busy system being used for other purposes, or else you will interfere with the benchmarking process and get inaccurate results

      You have now added a benchmark to your growing unit test. In the next and final step, you will add examples to your documentation, which go test will also evaluate.

      Step 7 — Documenting Your Go Code with Examples

      In this step, you will document your Go code with examples and then test those examples. Go is very focused on proper documentation, and example code adds another dimension both to documentation and testing. Examples are based on existing methods and functions. Your examples should show users how to use a specific piece of code. Example functions are the third type of function treated specially by the go test subcommand.

      To begin, reopen math_test.go,

      Now add the highlighted code. This will add the fmt package to the import list and your example function at the end of the file:

      ./math/math_test.go

      
      package math
      
      import (
          "fmt"
          "testing"
      )
      
      // arg1 means argument 1 and arg2 means argument 2, and the expected stands for the 'result we expect'
      type addTest struct {
          arg1, arg2, expected int
      }
      
      var addTests = []addTest{
          addTest{2, 3, 5},
          addTest{4, 8, 12},
          addTest{6, 9, 15},
          addTest{3, 10, 13},
      }
      
      func TestAdd(t *testing.T) {
      
          for _, test := range addTests {
              if output := Add(test.arg1, test.arg2); output != test.expected {
                  t.Errorf("Output %q not equal to expected %q", output, test.expected)
              }
          }
      }
      
      func BenchmarkAdd(b *testing.B) {
          for i := 0; i < b.N; i++ {
              Add(4, 6)
          }
      }
      
      
      func ExampleAdd() {
          fmt.Println(Add(4, 6))
          // Output: 10
      }
      
      

      The Output: line is used to specify and document the expected output.

      Note: The comparison ignores leading and trailing space.

      Save and close the file.

      Now rerun your unit test:

      You will see an updated outout like this:

      Output

      === RUN TestAdd --- PASS: TestAdd (0.00s) === RUN ExampleAdd --- PASS: ExampleAdd (0.00s) PASS ok ./math 0.442s

      Your examples are now also tested. This feature improves your documentation and also makes your unit test more robust.

      Conclusion

      In this tutorial, you created a small program and then wrote a basic unit test to check its functionality. You then rewrote your unit test as a table-based unit test and then added a coverage test, a benchmark, and a documented example.

      Taking time to write adequate unit tests is useful to you as a programmer because it improves your confidence that the code or program you have written will continue to work as expected. The testing package in Go provides you with considerable unit-testing capabilities. To learn more, refer to Go’s official documentation.

      And if you wish to learn more about programming in Go, visit our tutorial series / free ebook, How To Code in Go.



      Source link

      Comment ajouter les tests unitaires à votre projet Django


      L’auteur a choisi le Open Internet/Free Speech Fund pour recevoir un don dans le cadre du programme Write for Donations.

      Introduction

      Il est presque impossible de construire des sites web qui fonctionnent parfaitement du premier coup sans erreurs. C’est pourquoi vous devez tester votre application web pour trouver ces erreurs et y remédier de manière proactive. Afin d’améliorer l’efficacité des tests, il est courant de décomposer les tests en unités qui testent des fonctionnalités spécifiques de l’application web. Cette pratique est appelée les tests unitaires.   Il est plus facile de détecter les erreurs, car les tests se concentrent sur de petites parties (unités) de votre projet indépendamment des autres parties.

      Tester un site web peut être une tâche complexe à entreprendre car il est constitué de plusieurs couches de logique comme le traitement des requêtes HTTP, la validation des formulaires et les modèles de rendu. Cependant, Django fournit un ensemble d’outils qui permettent de tester votre application web en toute transparence.   Dans Django, la façon préférée d’écrire des tests est d’utiliser le module Python unittest, bien qu’il soit possible d’utiliser d’autres cadres de test. 

      Dans ce tutoriel, vous allez mettre en place une suite de tests dans votre projet Django et écrire des tests unitaires pour les modèles et les vues de votre application. Vous effectuerez ces tests, analyserez leurs résultats et apprendrez comment trouver les causes des échecs.

      Conditions préalables

      Avant de commencer ce tutoriel, vous aurez besoin des éléments suivants :

      Étape 1 – Ajouter une séquence de tests à votre demande Django

      Une séquence de tests dans Django est une collection de tous les scénarios de test de toutes les applications de votre projet. Pour permettre à l’utilitaire de test de Django de découvrir les scénarios de test que vous avez, vous écrivez les scénarios de test dans des scripts dont le nom commence par test.   Au cours de cette étape, vous allez créer la structure des répertoires et des fichiers de votre séquence de tests, et y créer un scénario de test vide.

      Si vous avez suivi la série de tutoriels Django Development, vous aurez une application Django appelée blogsite.

      Créons un dossier pour contenir tous nos scripts de test. Tout d’abord, activez l’environnement virtuel :

      • cd ~/my_blog_app
      • . env/bin/activate

      Ensuite, naviguez vers le répertoire appblogsite, le dossier qui contient les fichiers models.py et views.py, puis créez un nouveau dossier appelé tests :

      • cd ~/my_blog_app/blog/blogsite
      • mkdir tests

      Ensuite, vous transformerez ce dossier en un paquet Python, ajoutez donc un fichier __init__.py :

      • cd ~/my_blog_app/blog/blogsite/tests
      • touch __init__.py

      Vous allez maintenant ajouter un fichier pour tester vos modèles et un autre pour tester vos vues :

      • touch test_models.py
      • touch test_views.py

      Enfin, vous allez créer un scénario de test vide dans test_models.py. Vous devrez importer la classe Django TestCase et en faire une super classe de votre propre classe de test case. Plus tard, vous ajouterez des méthodes à ce scénario de test pour tester la logique de vos modèles. Ouvrez le fichier test_models.py :

      Ajoutez maintenant le code suivant au fichier :

      ~/my_blog_app/blog/blogsite/tests/test_models.py

      from django.test import TestCase
      
      class ModelsTestCase(TestCase):
          pass
      

      Vous avez maintenant ajouté avec succès une séquence de tests à l’appli blogsite. Ensuite, vous remplirez les détails du modèle de cas test vide que vous avez créé ici.

      Étape 2 – Tester votre code Python

      Dans cette étape, vous allez tester la logique du code écrit dans le fichier models.py. En particulier, vous testerez la méthode de sauvegarde du modèle Post afin de vous assurer qu’il crée la bonne portion du titre d’un message lorsqu’il est appelé. 

      Commençons par regarder le code que vous avez déjà dans votre fichier models.py pour la méthode de sauvegarde du modèle Post :

      • cd ~/my_blog_app/blog/blogsite
      • nano models.py

      Vous verrez ce qui suit :

      ~/my_blog_app/blog/blogsite/models.py

      class Post(models.Model):
          ...
          def save(self, *args, **kwargs):
              if not self.slug:
                  self.slug = slugify(self.title)
              super(Post, self).save(*args, **kwargs)
          ...
      

      On peut voir qu’il vérifie si le message sur le point d’être sauvegardé a une valeur de slug, et si ce n’est pas le cas, il appelle slugify pour lui créer une valeur de slug. C’est le type de logique que vous pourriez vouloir tester pour vous assurer que les slugs sont effectivement créés lors de la sauvegarde d’un message.

      Fermez le dossier.

      Pour le tester, retournez à test_models.py :

      Mettez-le ensuite à jour en ajoutant les parties surlignées :

      ~/my_blog_app/blog/blogsite/tests/test_models.py

      from django.test import TestCase
      from django.template.defaultfilters import slugify
      from blogsite.models import Post
      
      
      class ModelsTestCase(TestCase):
          def test_post_has_slug(self):
              """Posts are given slugs correctly when saving"""
              post = Post.objects.create(title="My first post")
      
              post.author = "John Doe"
              post.save()
              self.assertEqual(post.slug, slugify(post.title))
      

      Cette nouvelle méthode test_post_has_slug crée un nouvel article avec le titre « My first post », puis donne à l’article un auteur et l’enregistre. Ensuite, à l’aide de la méthode assertEqual du module Python unittest, il vérifie si le slug du message est correct. La méthode assertEqual vérifie si les deux arguments qui lui sont transmis sont égaux tels que déterminés par l’opérateur « == » et signale une erreur s’ils ne le sont pas.

      Sauvegarder et quitter test_models.py.

      Voici un exemple de ce qui peut être testé. Plus vous ajoutez de logique à votre projet, plus il y a de choses à tester. Si vous ajoutez plus de logique à la méthode de sauvegarde ou créez de nouvelles méthodes pour le modèle Post, vous voudriez ajouter plus de tests ici. Vous pouvez les ajouter à la méthode test_post_has_slug ou créer de nouvelles méthodes de test, mais leurs noms doivent commencer par test.

      Vous avez créé avec succès un cas test pour le modèle Post où vous avez affirmé que les slugs sont correctement créés après la sauvegarde. Dans l’étape suivante, vous rédigerez un scénario de test pour tester les vues.

      Étape 3 – Utiliser le client test de Django

      Dans cette étape, vous allez écrire un scénario de test qui teste une vue en utilisant le client de test Django. Le test client est une classe Python qui agit comme un navigateur web factice, vous permettant de tester vos vues et d’interagir avec votre application Django de la même manière qu’un utilisateur le ferait. Vous pouvez accéder au client test en vous référant à self.client dans vos méthodes de test. Par exemple, créons un cas test dans test_views.py Tout d’abord, ouvrez le fichier test_views.py :

      Ajoutez ensuite ce qui suit :

      ~/my_blog_app/blog/blogsite/tests/test_views.py

      from django.test import TestCase
      
      
      class ViewsTestCase(TestCase):
          def test_index_loads_properly(self):
              """The index page loads properly"""
              response = self.client.get('your_server_ip:8000')
              self.assertEqual(response.status_code, 200)
      

      Le ViewsTestCase contient une méthode test_index_loads_properly qui utilise le client de test Django pour visiter la page d’index du site (http://your_server_ip:8000, où your_server_ip est l’adresse IP du serveur que vous utilisez). Ensuite, la méthode de test vérifie si la réponse a un code de statut de 200, ce qui signifie que la page a répondu sans aucune erreur. Vous pouvez donc être sûr que lorsque l’utilisateur se rendra sur le site, il répondra lui aussi sans erreur.

      Outre le code de statut, vous pouvez lire d’autres propriétés de la réponse du client de test que vous pouvez tester dans la page des réponses de test de la documentation de Django.

      Au cours de cette étape, vous avez créé un scénario de test pour vérifier que la vue rendant la page d’index fonctionne sans erreur. Il y a maintenant deux scénarios de test dans votre séquence de tests. Au cours de l’étape suivante, vous les exécuterez pour voir leurs résultats.

      Étape 4 – Effectuer vos tests

      Maintenant que vous avez terminé la construction d’une séquence de tests pour le projet, il est temps d’exécuter ces tests et de voir leurs résultats. Pour effectuer les tests, naviguez dans le dossier blog (contenant le fichier manage.py de l’application) :

      Puis, exécutez-les :

      Vous verrez un résultat similaire à ce qui suit dans votre terminal :

      Output

      Creating test database for alias 'default'... System check identified no issues (0 silenced). .. ---------------------------------------------------------------------- Ran 2 tests in 0.007s OK Destroying test database for alias 'default'...

      Dans cette sortie, il y a deux points .., dont chacun représente un cas de test réussi. Vous allez maintenant modifier test_views.py pour déclencher un test d’échec. Ouvrez le fichier avec :

      Ensuite, changez le code en surbrillance pour :

      ~/my_blog_app/blog/blogsite/tests/test_views.py

      from django.test import TestCase
      
      
      class ViewsTestCase(TestCase):
          def test_index_loads_properly(self):
              """The index page loads properly"""
              response = self.client.get('your_server_ip:8000')
              self.assertEqual(response.status_code, 404)
      

      Ici, vous avez changé le code de statut de 200 à 404. Maintenant, refaites le test à partir de votre répertoire avec manage.py :

      Vous verrez la sortie suivante :

      Output

      Creating test database for alias 'default'... System check identified no issues (0 silenced). .F ====================================================================== FAIL: test_index_loads_properly (blogsite.tests.test_views.ViewsTestCase) The index page loads properly ---------------------------------------------------------------------- Traceback (most recent call last): File "~/my_blog_app/blog/blogsite/tests/test_views.py", line 8, in test_index_loads_properly self.assertEqual(response.status_code, 404) AssertionError: 200 != 404 ---------------------------------------------------------------------- Ran 2 tests in 0.007s FAILED (failures=1) Destroying test database for alias 'default'...

      Vous voyez qu’il y a un message d’échec descriptif qui vous indique le script, le cas de test et la méthode qui a échoué. Il vous indique également la cause de l’échec, le code de statut n’étant pas égal à 404 dans ce cas, avec le message AssertionError: 200 !  404. L’AssertionError est ici soulevée à la ligne de code surlignée dans le fichier test_views.py :

      ~/my_blog_app/blog/blogsite/tests/test_views.py

      from django.test import TestCase
      
      
      class ViewsTestCase(TestCase):
          def test_index_loads_properly(self):
              """The index page loads properly"""
              response = self.client.get('your_server_ip:8000')
              self.assertEqual(response.status_code, 404)
      

      Il vous indique que l’affirmation est fausse, c’est-à-dire que le code de statut de réponse (200) n’est pas ce à quoi on s’attendait (404). Précédant le message d’échec, vous pouvez voir que les deux points .. sont maintenant passés à . F, qui vous indique que le premier cas test a réussi alors que le second ne l’a pas fait.

      Conclusion

      Dans ce tutoriel, vous avez créé une suite de tests dans votre projet Django, ajouté des scénarios de test au modèle de test et à la logique de visualisation, appris comment exécuter des tests et analysé les résultats des tests. Dans un deuxième temps, vous pouvez créer de nouveaux scripts de test pour le code Python qui n’est pas dansmodels.py et views.py.

      Voici quelques articles qui peuvent s’avérer utiles pour créer et tester des sites web avec Django :

      Vous pouvez également consulter notre page thématique sur Django pour d’autres tutoriels et projets.



      Source link