Il y a quelque temps, j'ai essayé d'améliorer divers tests d'un système écrit en langage C pendant 20 ans, donc j'écrirai un peu de connaissances que j'ai acquises à ce moment.
- Attention Puisque je crée un environnement sur mon propre ordinateur tout en me souvenant de ces jours pour écrire des articles, il est en fait différent de l'environnement et de la version que j'ai fait dans la pratique. De plus, il y a du code dans cet article, mais ce n'est qu'un faux exemple auquel j'ai pensé pour écrire l'article.
Avec un système qui fonctionne depuis 20 ans, personne ne sait plus ce que cela signifie, mais il y a un certain nombre d'endroits où les comportements existants ne devraient pas être modifiés.
Si vous n'avez pas d'autre choix que de travailler sur une telle pièce, les méthodes suivantes sont efficaces.
Tout d'abord, écrivez le code de test pour le code existant. Et après avoir confirmé que tout passe, nous étendrons la fonction petit à petit. Cela permet de poursuivre le travail d'ajout de fonction tout en confirmant que l'ajout de nouvelle fonction ne rompt pas la fonction existante.
C'est un soi-disant ** test d'abord **, mais dans un environnement aussi conservateur avec une longue histoire, faire ce que vous avez dit sous XP entraînera diverses distorsions. Nous en parlerons à la fin de ce chapitre.
Vous pouvez faire de votre mieux avec assert etc., mais il est préférable d'utiliser le framework de test xUnit tel que CUnit.
** Cutter ** (la version 1.2.7 publiée le 13/09/2019 est la dernière) https://cutter.osdn.jp/index.html.ja
** UNITY ** (v2.4.3 publiée en novembre 2017. L'historique de certains commit est le 2019/10) http://www.throwtheswitch.org/unity https://github.com/ThrowTheSwitch/Unity
Il peut également être testé à partir de frameworks xUnit pour C ++ tels que CPPUnit, mais cette fois, il est supprimé.
Il vaut mieux utiliser un framework de test bien connu car il existe de nombreux exemples de réalisation dans le monde et cela sera utile en cas de problème. De plus, l'utilisation du framework de test bien connu facilite le travail avec Jenkins.
Par exemple, le [xUnit Plugin] de Jenkins (https://wiki.jenkins.io/display/JENKINS/xUnit+Plugin) a une fonction pour agréger la sortie XML du célèbre xUnit, et il est facile à tester automatiquement. Il est possible d'agréger les résultats de.
CUnit CUnit est un framework de xUnit pour langage C qui peut être utilisé à partir de gcc et visual c.
Puisqu'il n'y a que des solutions des époques VS2008 et 2005, il est ancien et des avertissements sortent.
** Comment vérifier le nombre de builds simultanés d'un projet dans Visual Stduio 2019 ** Sélectionnez "Outils" -> "Options" dans le menu pour ouvrir la boîte de dialogue des options, et sélectionnez "Projets et solutions" -> "Construire / Exécuter" pour afficher l'écran correspondant.
Ce qui suit est un exemple de construction à l'aide de la commande make sous l'environnement de Windows 10 + Ubuntu 18.04. Vous pouvez essentiellement le construire de la même manière que l'article suivant, mais la version est 2.1-3.
** Test unitaire du programme C avec CUnit ** https://qiita.com/muniere/items/ff9a984ed7e51eee7112
wget https://jaist.dl.sourceforge.net/project/cunit/CUnit/2.1-3/CUnit-2.1-3.tar.bz2
tar xvf CUnit-2.1-3.tar.bz2
cd CUnit-2.1-3
aclocal
autoconf
automake
./configure
make
make install
Exécutez ce qui suit pour installer.
sudo apt-get install autoconf
Exécutez la commande suivante et recommencez depuis autoconf
autoreconf -i
Installez libtool avec la commande suivante.
sudo apt-get install libtool
Définissez la variable d'environnement LD_LIBRARY_PATH.
export LD_LIBRARY_PATH=/usr/local/lib
Si vous voulez le rendre persistant, écrivez-le dans .bashrc
Lors de l'exécution à partir de la console, ce sera comme suit.
#include <CUnit/CUnit.h>
int max(int x, int y) {
if (x>y) {
return x;
} else {
return y;
}
}
int min(int x, int y) {
/** bug **/
if (x>y) {
return x;
} else {
return y;
}
}
void test_max_001(void) {
CU_ASSERT_EQUAL(max(5, 4) , 5);
}
void test_max_002(void) {
CU_ASSERT_EQUAL(max(4, 5) , 5);
}
void test_max_003(void) {
CU_ASSERT_EQUAL(max(5, 5) , 5);
}
void test_min_001(void) {
CU_ASSERT_EQUAL(min(5, 4) , 4);
}
void test_min_002(void) {
CU_ASSERT_EQUAL(min(4, 5) , 4);
}
void test_min_003(void) {
CU_ASSERT_EQUAL(min(5, 5) , 5);
}
int main() {
CU_pSuite max_suite, min_suite;
CU_initialize_registry();
max_suite = CU_add_suite("max", NULL, NULL);
CU_add_test(max_suite, "test_001", test_max_001);
CU_add_test(max_suite, "test_002", test_max_002);
CU_add_test(max_suite, "test_003", test_max_003);
min_suite = CU_add_suite("min", NULL, NULL);
CU_add_test(min_suite, "test_001", test_min_001);
CU_add_test(min_suite, "test_002", test_min_002);
CU_add_test(min_suite, "test_003", test_min_003);
CU_console_run_tests();
CU_cleanup_registry();
return(0);
}
Voir ci-dessous pour d'autres fonctions pour ASSERT. http://cunit.sourceforge.net/doc/writing_tests.html
Veuillez créer un lien vers libcunit.a lors de la compilation de la source qui exécute CUnit.
gcc unit.c -Wall -L/usr/local/lib -lcunit -o unit
Une fois exécuté à partir de la console, ce sera comme suit.
Apprenez à obtenir des résultats CUnit dans Jenkins.
Modifiez le code pour créer un fichier XML à transmettre à Jenkins. Lors de la sortie vers un fichier XML, utilisez CU_automated_run_tests et CU_list_tests_to_file au lieu de CU_console_run_tests.
/**Abréviation**/
/**Sortie de la console
CU_console_run_tests();
*/
/**Sortie XML**/
CU_set_output_filename("./unit");
CU_automated_run_tests();
CU_list_tests_to_file();
CU_cleanup_registry();
/**Abréviation**/
Si vous modifiez le fichier ci-dessus, compilez-le et exécutez-le, le fichier XML suivant sera créé au lieu d'être envoyé vers la console.
Voir ci-dessous pour plus de détails sur la façon d'exécuter CUnit. http://cunit.sourceforge.net/doc/running_tests.html#overview
Installez xUnit Plugin dans Plugin Management.
Dans les paramètres de la tâche, ajoutez «Publier le rapport de résultat du test xUnit» au processus de post-construction et sélectionnez CUnit-2.1.
Spécifiez unit-Results.xml pour Pattern.
Lorsque vous exécutez la compilation, vous obtiendrez le résultat suivant:
Lors de l'automatisation des tests, si vous ne préparez que le framework de test xUnit ** et essayez de l'automatiser, l'introduction échouera souvent. Soit vous ne pouvez tester que des fonctions utilitaires simples et elles sont obsolètes, soit vous finissez par écrire du code de test complexe pour rendre les bibliothèques dépendantes cohérentes.
Par exemple, supposons que vous ayez une bibliothèque 2 qui dépend de la bibliothèque 1 comme indiqué ci-dessous.
Lors du test de la bibliothèque 2, créez un stub pour tester la bibliothèque 1 afin qu'elle renvoie des données pratiques pour tester la bibliothèque 2. De cette façon, même si la bibliothèque 1 dépend du réseau et de la base de données, vous pouvez la mettre dans un état pratique pour les tester sans les utiliser réellement.
Considérez comment écrire un test pour la bibliothèque 2 qui dépend de la bibliothèque 1 avec un exemple simple.
La bibliothèque 1 a l'implémentation suivante. Préparez une fonction appelée add qui prend deux arguments et renvoie le résultat de leur ajout.
static1.h
#ifndef STATIC1_H
#define STATIC1_H
extern int add(int x, int y);
#endif
static1.c
#include "static1.h"
int add(int x, int y) {
return x + y;
}
La bibliothèque 2 a l'implémentation suivante. Dans la bibliothèque 2, le traitement est effectué en utilisant la fonction add de la bibliothèque 1 dans la fonction proc.
static2.h
#ifndef STATIC2_H
#define STATIC2_H
#include "static1.h"
extern int proc(int x, int y, int z);
#endif
static2.h
#include "static2.h"
int proc(int x, int y, int z) {
int a = add(x,y);
return add(a, z);
}
Créez un stub au lieu du vrai code. Le traitement de ce stub effectue le traitement suivant chaque fois que la fonction d'ajout est exécutée. ・ Enregistrez le nombre d'appels -Exécution de la fonction de rappel enregistrée à l'avance dans le code de test
stub_static.h
#ifndef STUB_static1_H
#define STUB_static1_H
#include "static1.h"
typedef int (*pfunc_add_def) ( int x , int y );
typedef struct {
int count_add;
pfunc_add_def pfunc_add;
} stub_data_def_static1;
extern stub_data_def_static1 stub_data_static1;
#endif
stub_static.c
#include "stub_static1.h"
stub_data_def_static1 stub_data_static1;
int add ( int x , int y ) {
++stub_data_static1.count_add;
return stub_data_static1.pfunc_add( x , y );
}
Voici un exemple d'exécution de la fonction proc en définissant la fonction de rappel exécutée à partir du stub de la fonction add dans le code de test.
#include <CUnit/CUnit.h>
#include "static2.h"
#include "stub_static1.h"
int test001_stub_add(int x, int y) {
/** add()Bout. X et y pour tester avec CUNIT**/
if (stub_data_static1.count_add == 1) {
/**Premier appel**/
CU_ASSERT_EQUAL(x , 10);
CU_ASSERT_EQUAL(y , 5);
return 15;
}
if (stub_data_static1.count_add == 2) {
CU_ASSERT_EQUAL(x , 15);
CU_ASSERT_EQUAL(y , 4);
return 19;
}
CU_FAIL("Nombre d'appels incorrect pour l'ajout");
return -1;
}
void test001() {
memset(&stub_data_static1, 0, sizeof(stub_data_static1));
stub_data_static1.pfunc_add = test001_stub_add;
int ret = proc(10, 5,4);
/**Vérifiez le nombre d'appels**/
CU_ASSERT_EQUAL(stub_data_static1.count_add , 2);
CU_ASSERT_EQUAL(ret , 19);
}
int main() {
CU_pSuite suite;
CU_initialize_registry();
suite = CU_add_suite("stubsample", NULL, NULL);
CU_add_test(suite, "test001", test001);
CU_console_run_tests();
CU_cleanup_registry();
return(0);
}
Si la fonction de stub est appelée plusieurs fois, elle vérifie la valeur de l'argument que le stub recevra en fonction du nombre d'appels et détermine la valeur que le stub doit renvoyer.
Dans la section précédente, nous avons présenté un exemple de code de test simple utilisant des stubs. Le problème suivant est le coût de création des stubs.
Il n'est pas impossible de le créer à la main, mais s'il y a un grand nombre de fonctions dans la bibliothèque 1 ou si elles sont fréquemment mises à jour, il peut être difficile de les modifier à la main. Réfléchissons donc à la façon de générer automatiquement des stubs.
C'est essentiellement une simple conversion. Par conséquent, n'importe quel langage de programmation peut générer automatiquement des stubs, mais si vous voulez vous amuser, un langage de programmation pouvant utiliser la bibliothèque de modèles et l'analyseur de langage C est recommandé.
Vous pouvez analyser le code source C / C ++ à l'aide de pycparser. https://github.com/eliben/pycparser
Notez que cette bibliothèque ne fonctionne que pour le code qui a été traité par le préprocesseur. Par conséquent, en fait, utilisez le résultat de l'exécution du préprocesseur comme indiqué ci-dessous pour le code source à analyser.
gcc -E static1.h
Cette fois, nous analyserons le fichier d'en-tête pré-compilé comme suit.
Fichier d'en-tête à analyser
extern int add(int x, int y);
typedef struct {
int x;
int y;
} in_data_t;
typedef struct {
int add;
int minus;
} out_data_t;
extern void calc(in_data_t* i, out_data_t *o);
extern int max(int data[], int length);
Exemple pour analyser le fichier d'en-tête
import sys
from pycparser import c_parser, c_ast, parse_file
# https://github.com/eliben/pycparser/blob/master/examples/cdecl.py
def _explain_type(decl):
""" Recursively explains a type decl node
"""
typ = type(decl)
if typ == c_ast.TypeDecl:
quals = ' '.join(decl.quals) + ' ' if decl.quals else ''
return quals + _explain_type(decl.type)
elif typ == c_ast.Typename or typ == c_ast.Decl:
return _explain_type(decl.type)
elif typ == c_ast.IdentifierType:
return ' '.join(decl.names)
elif typ == c_ast.PtrDecl:
quals = ' '.join(decl.quals) + ' ' if decl.quals else ''
return quals + _explain_type(decl.type) + '*'
elif typ == c_ast.ArrayDecl:
arr = 'array'
if decl.dim:
arr = '[%s]' % decl.dim.value
else:
arr = '[]'
return _explain_type(decl.type) + arr
elif typ == c_ast.FuncDecl:
if decl.args:
params = [_explain_type(param) for param in decl.args.params]
args = ', '.join(params)
else:
args = ''
return ('function(%s) returning ' % (args) +
_explain_type(decl.type))
elif typ == c_ast.Struct:
decls = [_explain_decl_node(mem_decl) for mem_decl in decl.decls]
members = ', '.join(decls)
return ('struct%s ' % (' ' + decl.name if decl.name else '') +
('containing {%s}' % members if members else ''))
def show_func_defs(filename):
# Note that cpp is used. Provide a path to your own cpp or
# make sure one exists in PATH.
ast = parse_file(filename, use_cpp=False,
cpp_args=r'-Iutils/fake_libc_include')
if not isinstance(ast, c_ast.FileAST):
return
for ext in ast.ext:
if type(ext.type) == c_ast.FuncDecl:
print(f"function name: {ext.name} -----------------------")
args = ''
print("parameters----------------------------------------")
if ext.type.args:
for arg in ext.type.args:
print(f"{_explain_type(arg)} {arg.name}")
print("return----------------------------------------")
print(_explain_type(ext.type.type))
if __name__ == "__main__":
if len(sys.argv) > 1:
filename = sys.argv[1]
else:
exit(-1)
show_func_defs(filename)
En tant que flux simple du processus d'analyse, FileAST peut être obtenu comme résultat de l'exécution de parse_file. En y analysant la propriété ext, vous pouvez obtenir les informations de la fonction définie dans le fichier. Le résultat de l'exécution du programme ci-dessus et de la vérification de la définition de la fonction dans le fichier d'en-tête est affiché comme suit.
C:\dev\python3\cparse>python test.py static1.h
function name: add -----------------------
parameters----------------------------------------
int x
int y
return----------------------------------------
int
function name: calc -----------------------
parameters----------------------------------------
in_data_t* i
out_data_t* o
return----------------------------------------
void
function name: max -----------------------
parameters----------------------------------------
int[] data
int length
return----------------------------------------
int
Il a été confirmé que le fichier source du langage C peut être facilement analysé en utilisant pycparser de cette manière.
Lors de la création de fichiers d'en-tête et source pour les stubs, la bibliothèque de modèles facilite la création. Veuillez vous référer à ce qui suit pour la bibliothèque de modèles en Python.
** Je veux générer du HTML en Python pour la première fois depuis longtemps, alors vérifiez le modèle ** https://qiita.com/mima_ita/items/5405109b3b9e2db42332
Cette fois, j'utilise Jinja2.
Voici un exemple d'implémentation d'un outil de création de stub à l'aide d'un analyseur de langage C et d'une bibliothèque de modèles.
create_stub.py
import sys
import os
from pycparser import c_parser, c_ast, parse_file
from jinja2 import Template, Environment, FileSystemLoader
stub_header_tpl = """
#ifndef STUB_{{name}}_H
#define STUB_{{name}}_H
#include "{{name}}.h"
{% for func in function_list %}
typedef {{func.return_type}} (*pfunc_{{func.name}}_def) ({% for arg in func.args %}{% if loop.index != 1 %},{% endif %} {{arg.typedef}} {{arg.name}}{{arg.array}} {% endfor %});
{% endfor %}
typedef struct {
{% for func in function_list %}
int count_{{func.name}};
pfunc_{{func.name}}_def pfunc_{{func.name}};
{% endfor %}
} stub_data_def_{{name}};
extern stub_data_def_{{name}} stub_data_{{name}};
#endif
"""
stub_source_tpl = """
#include "stub_{{name}}.h"
stub_data_def_{{name}} stub_data_{{name}};
{% for func in function_list %}
{{func.return_type}} {{func.name}} ({% for arg in func.args %}{% if loop.index != 1 %},{% endif %} {{arg.typedef}} {{arg.name}}{{arg.array}} {% endfor %}) {
++stub_data_{{name}}.count_{{func.name}};
{% if func.return_type != "void" %}
return stub_data_{{name}}.pfunc_{{func.name}}({% for arg in func.args %}{% if loop.index != 1 %},{% endif %} {{arg.name}} {% endfor %});
{% else %}
stub_data_{{name}}.pfunc_{{func.name}}({% for arg in func.args %}{% if loop.index != 1 %},{% endif %} {{arg.name}} {% endfor %});
{% endif %}
}
{% endfor %}
"""
class ParameterData():
def __init__(self, name, typedef):
self.__name = name
self.__typedef = typedef
self.__array = ""
if '[' in typedef:
self.__array = "[" + typedef[typedef.index("[")+1:typedef.rindex("]")] + "]"
self.__typedef = typedef[0: typedef.index("[")]
@property
def name(self):
return self.__name
@property
def typedef(self):
return self.__typedef
@property
def array(self):
return self.__array
class FunctionData:
def __init__(self, name, return_type):
self.__name = name
self.__return_type = return_type
self.__args = []
@property
def name(self):
return self.__name
@property
def return_type(self):
return self.__return_type
@property
def args(self):
return self.__args
# https://github.com/eliben/pycparser/blob/master/examples/cdecl.py
def _explain_type(decl):
""" Recursively explains a type decl node
"""
typ = type(decl)
if typ == c_ast.TypeDecl:
quals = ' '.join(decl.quals) + ' ' if decl.quals else ''
return quals + _explain_type(decl.type)
elif typ == c_ast.Typename or typ == c_ast.Decl:
return _explain_type(decl.type)
elif typ == c_ast.IdentifierType:
return ' '.join(decl.names)
elif typ == c_ast.PtrDecl:
quals = ' '.join(decl.quals) + ' ' if decl.quals else ''
return quals + _explain_type(decl.type) + '*'
elif typ == c_ast.ArrayDecl:
arr = 'array'
if decl.dim:
arr = '[%s]' % decl.dim.value
else:
arr = '[]'
return _explain_type(decl.type) + arr
elif typ == c_ast.FuncDecl:
if decl.args:
params = [_explain_type(param) for param in decl.args.params]
args = ', '.join(params)
else:
args = ''
return ('function(%s) returning ' % (args) +
_explain_type(decl.type))
elif typ == c_ast.Struct:
decls = [_explain_decl_node(mem_decl) for mem_decl in decl.decls]
members = ', '.join(decls)
return ('struct%s ' % (' ' + decl.name if decl.name else '') +
('containing {%s}' % members if members else ''))
def analyze_func(filename):
ast = parse_file(filename, use_cpp=False,
cpp_args=r'-Iutils/fake_libc_include')
if not isinstance(ast, c_ast.FileAST):
return []
function_list = []
for ext in ast.ext:
if type(ext.type) != c_ast.FuncDecl:
continue
func = FunctionData(ext.name, _explain_type(ext.type.type))
if ext.type.args:
for arg in ext.type.args:
param = ParameterData(arg.name, _explain_type(arg))
func.args.append(param)
function_list.append(func)
return function_list
if __name__ == "__main__":
if len(sys.argv) != 3:
print("python create_stub.dossier de sortie d'en-tête pré-traité py")
exit(-1)
filename = sys.argv[1]
dst_folder = sys.argv[2]
function_list = analyze_func(filename)
if len(function_list) == 0:
print("Impossible de trouver la fonction")
exit(-1)
data = {
'name' : os.path.splitext(filename)[0],
'function_list' : function_list
}
#Création d'un fichier d'en-tête de stub
with open(f"{dst_folder}/stub_{data['name']}.h", mode='w', encoding='utf8') as f:
f.write(Template(stub_header_tpl).render(data))
#Création d'un fichier source de stub
with open(f"{dst_folder}/stub_{data['name']}.c", mode='w', encoding='utf8') as f:
f.write(Template(stub_source_tpl).render(data))
Les stubs créés à l'aide de cet outil sont les suivants.
stub_static1.h
#ifndef STUB_static1_H
#define STUB_static1_H
#include "static1.h"
typedef int (*pfunc_add_def) ( int x , int y );
typedef void (*pfunc_calc_def) ( in_data_t* i , out_data_t* o );
typedef int (*pfunc_max_def) ( int data[] , int length );
typedef struct {
int count_add;
pfunc_add_def pfunc_add;
int count_calc;
pfunc_calc_def pfunc_calc;
int count_max;
pfunc_max_def pfunc_max;
} stub_data_def_static1;
extern stub_data_def_static1 stub_data_static1;
#endif
stub_static.c
#include "stub_static1.h"
stub_data_def_static1 stub_data_static1;
int add ( int x , int y ) {
++stub_data_static1.count_add;
return stub_data_static1.pfunc_add( x , y );
}
void calc ( in_data_t* i , out_data_t* o ) {
++stub_data_static1.count_calc;
stub_data_static1.pfunc_calc( i , o );
}
int max ( int data[] , int length ) {
++stub_data_static1.count_max;
return stub_data_static1.pfunc_max( data , length );
}
Il existe souvent des situations où Python n'est pas disponible ou l'analyseur de langage C n'est pas disponible. Dans ce cas, utilisez doxygen.
Doxygen peut analyser le fichier source et générer les informations de fonction au format XML. Il est également possible de créer un fichier pour les stubs basé sur ce XML. De nombreux sites C et C ++ utilisent doxygen, c'est donc probablement un obstacle à l'adoption plus faible que l'utilisation de Python. *
Je ne l'ai pas vraiment adopté, mais il existe également les méthodes suivantes.
CMock https://github.com/ThrowTheSwitch/CMock Il semble créer du code pour simuler en utilisant Ruby. Cela peut être possible si Ruby peut être utilisé dans l'environnement.
** Idées pour l'utilisation des fonctions stub dans les tests de langage C ** https://qiita.com/HAMADA_Hiroshi/items/e1dd3257573ea466d169 "Comment effectuer un test unitaire en stubing des fonctions dans la même source sans modifier la source existante", idée de base Utilise des macros pour remplacer les fonctions existantes par des alias. Heureusement, je n'ai pas eu besoin d'écrire un test pour appeler une fonction dans la même source, donc je ne l'ai pas vraiment utilisée.
Lorsque vous écrivez un code de test, vous pouvez juger si le test est exécuté sans exception en mesurant le ratio couvert par le code de test. Heureusement, gcc est livré avec un outil appelé gcov pour mesurer la couverture.
10 gcov—a Test Coverage Program https://gcc.gnu.org/onlinedocs/gcc/Gcov.html
** Comment utiliser gcov ** https://mametter.hatenablog.com/entry/20090721/p1
Mesurons la couverture avec l'exemple du stub utilisé précédemment. Cette fois, afin de créer intentionnellement une route inaccessible, modifiez static2.c de la bibliothèque 2 comme suit.
static2.c
#include "static2.h"
int proc(int x, int y, int z) {
int a = add(x,y);
return add(a, z);
}
int proc2() {
/**Non atteint**/
return 0;
}
Puis compilez avec l'option -coverage pour mesurer la couverture.
gcc -c -Wall -Wextra static2.c -coverage
ar r libstatic2.a static2.o
gcc -c -Wall -Wextra stub_static1.c
ar r libstub_static1.a stub_static1.o
gcc test.c -coverage -Wall -Wextra -L. -lstatic2 -lstub_static1 -L/usr/local/lib -lcunit -o test
L'exécution de la commande ci-dessus créera les fichiers suivants.
Après cela, l'exécution du test créera les fichiers suivants.
Pour vérifier la couverture de static2.c sous test, exécutez la commande suivante.
gcov static2.gcda
Lorsque cette commande est exécutée, le message suivant s'affiche.
File 'static2.c'
Lines executed:60.00% of 5
Creating 'static2.c.gcov'
Ici, vous pouvez voir que la couverture est de 60%, et pour savoir où elle n'est pas couverte, affichez static2.c.gcov. Dans cet exemple, vous pouvez voir que proc2 ne passe pas.
Nous jugeons qu'elle est efficace sur les points suivants.
・ Le risque d'étendre la fonction sans modifier le comportement de la fonction actuelle peut être supprimé. -En écrivant un code de test, vous pouvez clarifier les fonctions qui étaient dans le noir jusqu'à présent. ・ Si vous écrivez un code de test, vous pouvez détecter un défaut qui a dormi dans le code existant. → Le réparer ou non est une autre affaire. Un équilibre entre la fréquence d'occurrence, le degré d'impact au moment de l'occurrence et le coût de correction.
Cependant, il y a quelques problèmes, alors examinons-les dans la section suivante.
Dans un environnement hérité, vous devrez peut-être écrire du code SQL dans le code source et le connecter à une base de données et l'exécuter à des fins de test. Dans ce cas, il est souhaitable de diviser la zone de base de données utilisée par chaque développeur et d'effectuer un test unitaire.
Cependant, dans une grande organisation, il existe des départements spécialisés dans la base de données et il n'est pas facile de falsifier la base de données. De plus, si vous placez la base de données elle-même dans l'environnement local, un PC médiocre devient un goulot d'étranglement, ou même si vous le franchissez, si vous laissez la gestion de la base de données dans votre environnement personnel, certains développeurs continueront à utiliser l'ancien environnement. Il y a aussi un risque de le faire.
Dans un tel cas, vous pouvez y faire face par une méthode moins consciente de revenir en arrière lorsque le test automatique est terminé.
① Lancer le test automatique ② Lancer une transaction DB ③ Stocker les données de test dans DB ④ Effectuer un test automatique ⑤ Vérifiez comment le DB a changé en fonction du résultat du test automatique ⑥ Restauration de la base de données
Cette méthode n'est pas une idée parfaite, mais elle est facile à mettre en œuvre.
Si vous écrivez la valeur d'entrée et la valeur attendue dans le tableau de structures, vous pouvez effectuer de nombreux tests. L'une des méthodes les plus fréquemment utilisées consiste à écrire une liste d'entrées et de sorties dans Excel, de la convertir en un tableau de structures de langage C avec des macros VBA et de l'écrire dans le code de test.
Si la méthode est utilisée dans un environnement de cascade conservateur, une fois la mise en œuvre terminée, les spécifications de test et les listes de contrôle pour les tests unitaires seront créées et testées. Cependant, si vous faites un test en premier, l'implémentation sera terminée = le test unitaire sera terminé, donc une distorsion se produira ici.
Avec la méthode classique, l'échéancier est décrit en trois étapes: mise en œuvre, création d'une check-list pour le test unitaire et mise en œuvre du test unitaire. Et avec une organisation hautement contrôlée, complète et parfaite, chaque processus est chronométré exactement.
Si vous faites quelque chose de test d'abord, vous ne pourrez pas séparer le test unitaire de l'implémentation, il sera donc impossible de mesurer le temps de travail individuel, et vous le rapporterez séparément.
Il est nécessaire de décider comment gérer les spécifications de test unitaire et les listes de contrôle créées par la méthode conventionnelle. Ce serait bien si nous pouvions décider que la révision du code du code de test serait OK et que le produit du test unitaire serait le code de test, mais dans un environnement hérité, les documents sont importants. Dans ce cas, vous devez créer une spécification de test ou une liste de contrôle à partir du code de test.
La première contre-mesure consiste à transférer manuellement le contenu du code de test dans la liste de contrôle. Le problème est que le code de test et les spécifications de test ne sont pas synchronisés. De plus, cette méthode est une très mauvaise réponse qui élimine l'avantage d'exécuter des tests automatiquement, mais elle est souvent adoptée car tout le monde peut le faire si cela prend du temps.
La deuxième méthode consiste à générer une spécification de test à partir du commentaire du code de test. Décrivez le contenu du test dans le commentaire du code de test et résumez les résultats extraits par Doxygen etc. dans la check-list. Dans ce cas, il existe un risque de divergence entre le commentaire et le traitement réel.
La troisième méthode consiste à écrire du japonais qui ne semble pas étrange même s'il est affiché dans la liste de contrôle pour le nom du test spécifié dans le code de test.
CU_add_test(max_suite, "max(a,b)Pour exécuter un=5,b=Dans le cas de 4, confirmez que la valeur de a est obtenue", test_max_001);
Si vous exécutez le test avec la description ci-dessus, ce qui suit sera généré dans le XML du résultat du test.
Après cela, sortez la liste de contrôle XML et vous avez terminé. Le problème avec cette méthode est que les détails de la liste de contrôle sont augmentés car le contenu de la vérification effectuée par CU_ASSERT_EQUAL etc. n'est pas sorti en XML.
La quatrième méthode consiste à créer une fonction qui encapsule CU_ASSERT_EQUAL, etc., et dans cette fonction, à sortir le contenu de l'inspection dans un fichier sous une forme facile à afficher dans la liste de contrôle.
Quelle que soit la méthode que vous choisissez, sachez que s'en tenir au document peut être un problème. → Dans une culture qui colle à l'écriture, veillons à ce que ce travail soit inclus dans le devis.
Le test unitaire étant terminé lorsque l'implémentation est terminée, il est impossible de signaler l'échec du test unitaire. Cependant, une organisation hautement contrôlée, complète et parfaite peut se concentrer sur le nombre d'échecs de tests unitaires. Dans ce cas, le problème survenu lors de l'implémentation sera correctement constitué comme un bug comme celui-là.
La théorie qui devrait être censée être est de convaincre et de créer de nouvelles règles, mais je pense que c'est impossible parce que la culture est différente.
L'introduction de tests automatisés permet de traiter mécaniquement un grand nombre de tests combinatoires. En conséquence, le nombre de tests effectués automatiquement est supérieur au nombre de tests réalisés par la méthode conventionnelle. (C'est un ordre de grandeur)
Jusqu'à présent, le nombre d'éléments sur la liste de contrôle, qui était au maximum de centaines, est maintenant généralement de mille ou dix mille. C'est une histoire de "qu'est-ce qui ne va pas à faire beaucoup de tests?", Mais s'il s'agit d'une organisation hautement contrôlée, complète et parfaite, elle sera traitée comme une valeur aberrante, alors ajustez la granularité de manière appropriée et utilisez-la pour le reporting. Il peut être nécessaire d'ajuster le nombre à ce niveau.
De plus, si vous souhaitez prendre les mesures appropriées, il serait judicieux de créer un nouvel index sous forme de tableau séparé pour le test automatique.
Si vous effectuez un test unitaire complet, vous n'aurez aucun problème avec le test d'intégration.
C'est un problème avec ça. Par exemple, disons que vous avez eu des dizaines d'échecs dans le passé, mais que le nombre de défauts a presque disparu. Si vous ne regardez que les chiffres, vous ne pouvez pas vous empêcher de vous demander si la perspective du test d'intégration est fausse.
Comme je l'ai écrit ci-dessous, c'est ce que c'est. Laissons tomber.
** Automatisation des tests unitaires à l'aide de Visual Studio inconscient ** https://qiita.com/mima_ita/items/05ce44c3eb1fd6e9dd46
Il est possible d'expliquer l'effet du test automatique par la main-d'œuvre et la qualité, de le convertir en montant monétaire et de susciter un sentiment de crise, et de préparer du matériel pédagogique, mais honnêtement, c'est une culture qui ne peut être comprise, peu importe combien une telle histoire est accumulée. Ne fonctionne pas du tout ** ** </ font>.
Cependant, dans toute organisation, il n'y a que quelques personnes avec des aspirations, alors attendons-nous à de telles personnes.
Nous avons parlé des tests automatisés dans la section précédente. Après tout, il s'agit simplement de déplacer et de vérifier la plage où le code de test a été écrit. Cette section décrit comment trouver des implémentations douteuses sans réellement écrire et exécuter du code de test.
En utilisant un outil d'analyse statique, il est possible de détecter une pièce avec une implémentation douteuse. Des outils coûteux peuvent faire des découvertes efficaces, mais les sources ouvertes sont également suffisamment efficaces.
Pour le langage C, vous pouvez utiliser CppCheck. http://cppcheck.sourceforge.net/
cppcheck peut être exécuté à partir de Windows avec GUI, ou avec Linux basé sur CUI.
Cette section décrit comment utiliser cppcheck sous l'environnement de Windows10 + Ubuntu 18.04.
Tout d'abord, installez-le avec la commande suivante.
sudo apt-get install cppcheck
Lorsque l'installation est terminée, cppcheck sera exécuté en exécutant la commande suivante.
$ cppcheck --enable=all .
Checking cov.c ...
1/7 files checked 14% done
Checking main.c ...
2/7 files checked 28% done
Checking static1.c ...
3/7 files checked 42% done
Checking static2.c ...
4/7 files checked 57% done
Checking stub_static1.c ...
5/7 files checked 71% done
Checking test.c ...
6/7 files checked 85% done
Checking unit.c ...
7/7 files checked 100% done
[cov.c:7]: (style) The function 'bar' is never used.
[static1.c:7]: (style) The function 'calc' is never used.
[static2.c:8]: (style) The function 'proc2' is never used.
(information) Cppcheck cannot find all the include files (use --check-config for details)
(1) Veuillez télécharger le programme d'installation à partir de la page suivante. http://cppcheck.sourceforge.net/
(2) Après l'installation, démarrez CppCheck et l'écran suivant s'ouvrira.
(3) Sélectionnez "Fichier" -> "Nouveau projet" dans le menu.
(4) Définissez un chemin arbitraire pour enregistrer le fichier de projet et cliquez sur "Enregistrer".
(5) La boîte de dialogue "Fichier de projet" s'ouvre. Cliquez sur le bouton "Ajouter" pour ajouter le chemin contenant le fichier source du langage C.
(6) Cliquez sur OK dans la boîte de dialogue "Fichier projet" et il vous sera demandé si vous souhaitez créer un répertoire de construction. Sélectionnez "Oui".
(7) Les fichiers C du répertoire spécifié sont répertoriés.
(8) Développez l'arborescence des fichiers énumérés pour voir les détails de l'avertissement.
En utilisant Cppcheck Plulgin de Jenkins, il est possible d'agréger les résultats de CppCheck dans Jenkins. https://plugins.jenkins.io/cppcheck
(1) Sélectionnez et installez CppCheck dans le gestionnaire de plug-ins Jenkins.
(2) Exécutez cppcheck comme suit pour obtenir le résultat au format xml.
cppcheck --enable=all --xml --xml-version=2 ~/libsample/. 2>cppcheck.xml
(3) Ajoutez les résultats de Publsh Cppcheck au processus de post-construction.
(4) Lorsque vous exécutez la construction, l'élément CppCheck sera affiché dans le résultat.
Le nombre d'étapes dans le code source et la complexité cyclique sont l'un des indicateurs pour prédire la qualité du code source.
** Complexité cyclomatique ** https://ja.wikipedia.org/wiki/%E5%BE%AA%E7%92%B0%E7%9A%84%E8%A4%87%E9%9B%91%E5%BA%A6
Pour faire simple, plus il y a de boucles et de branches, plus la complexité cyclique est élevée. Plus la complexité est élevée, plus elle est susceptible de contenir des bogues, cette métrique peut donc être utilisée comme point de départ pour savoir où concentrer vos tests.
Source Monitor Source Monitor est une métrique de code source écrite en C ++, C, C #, VB.NET, Java, Delphi, Visual Basic (VB6). C'est un logiciel libre qui peut mesurer.
(1) Téléchargez-le à partir de la page suivante et extrayez-le dans n'importe quel dossier. http://www.campwoodsw.com/sourcemonitor.html
(2) Lorsque vous exécutez le fichier SourceMonitor.exe développé, l'écran suivant s'affiche: Sélectionnez "Démarrer le moniteur source".
(3) Sélectionnez [Fichier] -> [Nouveau projet] dans le menu pour créer un projet qui regroupe les métriques.
(4) La boîte de dialogue "Sélectionner la langue" s'ouvre. Sélectionnez "C" et cliquez sur "Suivant".
(5) Spécifiez le nom de fichier du fichier projet et le dossier dans lequel le stocker, puis cliquez sur "Suivant".
(6) Spécifiez le dossier contenant le code source à analyser et cliquez sur "Suivant".
(7) Sélectionnez une option et cliquez sur "Suivant".
option | La description |
---|---|
Use Modified Complexity Metrix | La métrique de complexité par défaut est calculée en comptant les observations dans chaque instruction de commutation, mais en activant la vérification, chaque bloc de commutation ne sera compté qu'une seule fois et chaque instruction de cas sera modifiée. Cela ne contribuera plus à la complexité. |
Do not count blank line | Ignorez les lignes vides lors du comptage des lignes. |
Ignore Continuous Header and Footer Comments | Cochez cette option si vous souhaitez ignorer les lignes de commentaires consécutives qui contiennent des informations telles que l'historique des fichiers générées automatiquement par les utilitaires de gestion de version tels que CVS et Visual Source Safe. |
(8) Cliquez sur "Suivant".
(9) Si vous souhaitez inclure des fichiers UTF8, cochez «Autoriser l'analyse des fichiers UTF-8» et cliquez sur «Suivant».
(10) Cliquez sur «Terminer».
(11) Une liste des fichiers à mesurer s'affiche.Après confirmation, cliquez sur OK.
(12) Le résultat de la mesure s'affiche.
Cliquez sur la liste pour voir une liste de fichiers.
Cliquez sur l'élément du fichier dans la liste pour voir les détails.
Vous pouvez vérifier le contenu du fichier en cliquant avec le bouton droit de la souris sur l'élément du fichier dans la liste pour afficher le menu contextuel et en sélectionnant "Afficher le fichier source".
Nom | La description |
---|---|
Lines | Le nombre de lignes physiques dans le fichier source. Si "Ne pas compter les lignes vides" est coché lors de la création du projet, les lignes vierges ne sont pas incluses. Utilisé pour voir une échelle approximative |
Max Complexity | Complexité circulaire. La gestion des cas dans la clause switch change selon que "Use Modified Complexity Metrix" est coché ou non lors de la création d'un projet. Plus ce nombre est élevé, plus le test est difficile. |
Max Depth | Profondeur de nidification maximale. Gardez un œil sur le code profondément imbriqué car il a tendance à être compliqué |
% Comment | Pourcentage de commentaires. 0%Au contraire, s'il est trop gros, vous devriez le vérifier. |
Il est possible de détecter le code copié en utilisant le PMD-CPD présenté ci-dessous.
** Voulez-vous que je corrige cette copie? ** https://qiita.com/mima_ita/items/fe1b3443a363e5d90a21
Contrairement au développement, il n'est pas utilisé dans le but de trouver un code similaire et de le rendre commun. ** Contrairement en cours de développement, un système qui est déjà en fonctionnement ne peut pas être facilement refactorisé, comme la standardisation, même si le code est copié. </ font> **
Le but de la détection d'un clone de code dans une telle situation est de vérifier s'il y a d'autres parties similaires qui doivent être corrigées lors de la modification d'un code source, par exemple, lorsqu'une réponse d'échec ou l'ajout de fonction se produit.
Par exemple, si vous faites une erreur, on vous demandera naturellement: "Est-ce qu'une autre partie va bien?" À ce stade, vous pouvez tricher en disant quelque chose comme "J'ai utilisé l'outil de détection de clone de code pour enquêter de manière approfondie sur des parties similaires et confirmé qu'il n'y avait pas de problème".
Vous pouvez évaluer le code et savoir où le corriger sans réellement déplacer le code.
Le coût du déploiement du code de test dans un environnement conservateur peut être compliqué, mais les outils d'analyse statique peuvent être utilisés à faible coût et peuvent être utilisés par de nombreuses organisations.
Laissez-moi vous raconter quelques histoires sur l'environnement pendant le test. De plus, puisque l'histoire du test d'intégration sort de ce chapitre, cela devient une arnaque au titre du "système made in C language".
Il existe des sites où la configuration suivante est utilisée pour les tests unitaires, qu'il s'agisse ou non d'un test d'intégration impliquant un autre système.
Les mauvais points de cette configuration sont les suivants. ・ Il arrive souvent que les tests qui se réussissent ont une influence et que les cas qui réussissent à l'origine sont rejetés, et vice versa. -En raison de la situation instable des tests unitaires, il est nécessaire de changer fréquemment d'environnement de test, et le travail des autres développeurs s'arrête à chaque fois.
En d'autres termes, la testabilité est extrêmement médiocre. Pour cette raison, il est souhaitable de préparer un environnement de test pour chaque développeur comme indiqué ci-dessous jusqu'au test unitaire.
Afin de donner un environnement de test à chaque développeur, il y a longtemps, j'avais l'habitude de forcer un programme fonctionnant sous Linux à être construit et exécuté sur Visual C ++ sous Windows. Cependant, ces jours-ci, vous pouvez créer un environnement virtuel avec VMWare ou VirtualBox, vous devez donc l'utiliser.
De plus, lorsque la mémoire du PC donnée au développeur est de 4 Go, je pense que je vais abandonner dans l'environnement virtuel.
Lors de la sortie dans un environnement de test où plusieurs personnes peuvent le toucher, il est préférable de le rendre aussi automatique que possible. Pour minimiser l'impact sur la publication, vous devez réduire le temps d'arrêt de l'environnement de test ou le libérer lorsque personne ne l'utilise. Cela peut être résolu en automatisant le processus de publication dans l'environnement de test.
De plus, en conservant un enregistrement de la publication dans l'environnement de test, il est possible de suivre quelle révision du matériel a été utilisée et à quel moment. Vous n'êtes pas obligé de vous en tenir à un outil spécifique comme Jenkins, vous pouvez simplement utiliser un script shell ou un fichier batch, c'est donc une bonne idée de le rendre automatique.
De plus, en utilisant la technique de l'article suivant, je pense qu'il sera possible de télécharger du matériel depuis Windows avec WinSCP, puis d'exécuter le script via TeraTerm et d'envoyer le résultat par e-mail.
** Je me demande s'il est correct d'automatiser WinSCP ** https://qiita.com/mima_ita/items/35261ec39c3c587210d8
** Essayez la macro TeraTerm ** https://qiita.com/mima_ita/items/60c30a28ea64972008f2
** Fonctionnement automatique d'Outlook avec notre PowerShell qui a abandonné Redmine ** https://qiita.com/mima_ita/items/37ab711a571f29346830
Vous devrez peut-être créer une grande quantité de données pendant le test d'intégration. À ce stade, du point de vue du test d'intégration, s'il y a une déclaration selon laquelle "les données à utiliser peuvent être créées par le système", il n'est pas possible de transférer les données dans la base de données avec SQL.
Pour utiliser les écrans fournis par le système et enregistrer une grande quantité de données, vous pouvez utiliser la technique présentée dans la section «Automatisation des opérations sur les écrans Windows» ci-dessous.
** Une société informatique autoproclamée s'est lancée sur le terrain sans trop utiliser l'informatique, je vais introduire une méthode d'automatisation de Windows ** https://qiita.com/mima_ita/items/453bb6c313e459c44689
Cependant, en raison des caractéristiques de l'outil à adopter et de l'application à utiliser, il existe des cas où des erreurs d'entrée, etc. peuvent être contournées, il faut donc être prudent lors de son adoption.
Je pense que vous devriez réduire la cible pour les raisons suivantes.
-En raison des caractéristiques des outils utilisés et de l'application à utiliser, il existe des cas où le fonctionnement automatique n'est pas exactement le même que le fonctionnement manuel. ・ L'automatisation du fonctionnement de l'écran est fondamentalement instable -Le fonctionnement automatique des opérations d'écran nécessite un coût de création élevé.
Si je le fais, je me concentrerai sur les tests de fonctionnement de l'écran pour les tests de fumée et une grande quantité de traitements itératifs (créant une grande quantité de données et des tests de charge).
Il y a des histoires inutiles qui sont idéales mais qui ne peuvent pas être présentées de manière pratique. Ici, je voudrais vous présenter une histoire aussi inutile.
** À propos de TestLink, un outil de gestion du processus de test ** https://qiita.com/mima_ita/items/ed56fb1da1e340d397b9
Je propose des choses comme ça dans divers endroits depuis 10 ans, mais en gros, je pense que c'est difficile dans un environnement comme SIer.
Les organisations qui s'intéressent à ces outils les utilisent déjà, et celles qui ne sont pas intéressées ne sont ** pas vraiment intéressées par la gestion des tests elle-même **, alors que devez-vous faire si vous dépensez beaucoup d'argent pour introduire des outils qui ont peu de chances de gagner. Je pense qu'il y en a d'autres.
En introduisant un système de suivi des bogues, vous pourrez gérer l'historique des bogues, déterminer le statut de qui a des bogues et combien, et gérer l'association avec le code source.
Malheureusement, j'ai réalisé que dans certaines situations, il est préférable de se concentrer sur l'éducation de base, comme «comment rédiger un vote de bogue», plutôt que de passer du temps sur cette théorie.
Lors de l'examen de la capacité de recherche et de la conservation de l'historique, il est préférable d'adopter Wiki plutôt que d'accumuler des connaissances dans les textes Office.
Cependant, de nombreux ingénieurs informatiques dans certains environnements n'ont utilisé Excel que pour écrire des phrases, alors n'en faites pas trop.
Dans le domaine où toutes les histoires idéales ci-dessus sont gaspillées, les spécifications de test, les formulaires de gestion des bogues et les phrases de connaissances seront écrits dans Excel ou Word et gérés dans un dossier partagé.
Il y a deux problèmes avec ceci. Le premier est le problème de la recherche Le second est la question de la gestion de la configuration.
Réfléchissez à la manière de traiter ces problèmes.
Vous pouvez rechercher des textes Office à l'aide de certains logiciels gratuits. Si quoi que ce soit, vous pouvez le créer avec VBS, etc.
De plus, je pense qu'il faut du temps pour chercher dans Office, donc si possible, je pense qu'il ne sera pas stressant d'adopter un logiciel qui utilise le cache.
Les fichiers placés au hasard dans un dossier partagé ne seront pas connus quand, qui ou quelles modifications. En outre, il disparaît rarement en raison d'une opération erronée.
En guise de réponse, utilisez des outils de gestion de la configuration plutôt que des dossiers partagés pour gérer vos documents.
S'il n'est pas possible d'introduire l'outil de gestion de la configuration, veuillez considérer le plan B en vous référant à l'article suivant.
** Que faire si vous vous réincarnez dans un monde dominé par l'enseignement des dossiers partagés ** https://qiita.com/mima_ita/items/ef3588df4d65cf68f745
J'ai présenté les résultats que j'ai obtenus en essayant d'améliorer le processus de test dans un environnement conservateur au fil des ans. Comme je l'ai écrit dans l'article suivant, en particulier lorsque l'on tente d'améliorer un environnement conservateur qui a passé de nombreuses années, il est nécessaire d'accepter la situation actuelle et de trouver un point d'atterrissage réaliste.
En premier lieu, la ligne elle-même disant "Améliorons le processus de test"
Il est inévitable de recevoir une telle réaction, alors je pense que ce serait bien si elle était acceptée.
Recommended Posts