Kihagyás

Tesztelés: CUnit keretrendszer

Tesztelni sokféleképpen és több szinten lehet. Tesztelési szintek például az egység, integrációs, és rendszerteszt szintek. Ezekből az "alap" az egységteszt. Ez a program (általában a megvalósításhoz köthető) kisebb egységeit (függvény, osztály, modul) egymástól függetlenül, önállóan teszteli. Ha már a program valamelyik önálló kis egysége önmagában hibás, akkor mit várunk az ilyen egységek együttműködésétől (integrációs teszt) vagy a teljes rendszertől (rendszerteszt)?

No de hogyan lehet a program egységeit, itt most tipikusan az egyes függvényeit önmagukban tesztelni? Ehhez egy olyan tesztprogram kell, ami sorban meghívja ezeket, és ellenőrzi a visszaadott eredményeket. Az ilyen tesztek implementációja egyrészt ugyanazon a programozási nyelven történik, mint amiben az adott egység meg van írva, másrészt valamilyen keretrendszert szokás hozzájuk használni. C nyelvre egy ilyen keretrendszer a CUnit. (Használatához linux alatt telepíteni kell a libcunit1-dev csomagot.) A dokumentáció úgy vettem észre nem friss, de nagyjából használható, nézzétek át!

A CUnit telepítése sudo/root jogokkal

Ha van sudo jogod egy linuxos gépen (tudsz root jogokkal futtatni programokat), akkor a CUnit telepítése rendkívül egyszerű:

1
$ sudo apt install libcunit1-dev

A CUnit telepítése sudo/root jog nélkül

Ha nincs megfelelő jogod, akkor magadnak kell telepítened a CUnit könyvtárat. Szerencsére ez könnyen megoldható, be kell szerezni a forrást innen, be kell konfigurálni, le kell fordítani, és telepíteni kell valami lokális könyvtárba. Ha letöltötted a CUnit-2.1-3.tar.bz2 fájlt, akkor a következőket kell tenni:

1
2
3
4
5
6
$ tar xf CUnit-2.1-3.tar.bz2
$ cd CUnit-2.1-3
$ ./bootstrap
$ make
$ make install
$ cd ..
Ezek után a $HOME/CUnitHome könyvtárban (vagy ha a ./bootstrap-nek adtál meg argumentumként egyet, akkor abban) megtalálod a telepített CUnit-ot. A fordításhoz szükséges header fájlok az include alkönyvtár alatt lesznek, a szerkesztéshez, futtatáshoz szükséges binárisok pedig a lib alkönyvtárban.

Ebben az esetben persze a saját programod fordítását is megfelelően kell felparaméterezned, de ez a 3. és 4. gyakorlat után nem okozhat gondot! :)

Példa

A példa a jólismert képmanipuláló lib és program(család), ahol is most a libpnm-hez adunk egységteszteket. Ehhez gyakorlatilag egy speciális főprogramot kell írnunk, amely végrehajtja és kiértékeli a majdan megírandó tesztjeinket. (A példa teljes kódja itt tölthető le.)

Az egységtesztek főprogramja

utest.c

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include "pnm-manipulation.h"
#include "pnm-type.h"
#include "utest-manipulation.h"
#include "utest-type.h"

#include <CUnit/Basic.h>
#include <CUnit/CUnit.h>

CU_SuiteInfo pnm_suites[] = {
    { "type", NULL, NULL, NULL, NULL, type_tests },
    { "manipulation", NULL, NULL, manip_test_setup, manip_test_teardown, manipulation_tests },
    CU_SUITE_INFO_NULL
};

int main() {
    if (CU_initialize_registry() != CUE_SUCCESS) {
        return -1;
    };
    if (CU_register_suites(pnm_suites) != CUE_SUCCESS) {
        return -1;
    };
    CU_basic_set_mode(CU_BRM_NORMAL);
    CU_basic_run_tests();
    CU_cleanup_registry();
    return 0;
}

A CUnit keretrendszer szerencsére segít bennünket. Ahhoz, hogy ezt használhassuk, include-olni kell a <CUnit/CUnit.h> headert. A főprogram feladatai:

  • Inicializálni a tesztkörnyezetet a CU_initialize_registry() függvény segítségével.
  • Regisztrálni a pnm_suites tömbben található tesztkészleteket, hogy a CUnit keretrendszer majd lássa és futtassa ezeket, erre a CU_register_suites() függvényt használjuk.
  • Futtatni a (regisztrált) teszteket a CU_basic_run_tests() segítségével. Futtatóból több is van, mi most azt választottuk, ami a standard outputra írja az eredményt. Ehhez szükséges a <CUnit/Basic.h> include-olása.
  • Végül "takarítani" a CU_cleanup_registry() függvénnyel.

A CU_basic_set_mode(CU_BRM_NORMAL) azt befolyásolja, hogy a teszt mennyire részletes outputot produkál, mindent ír, vagy csak az összefoglalót. Itt most az egyedi hibákat és az összegzést.

A tesztek kétszintű hierarchiába vannak rendezve: vannak a tesztkészletek, azokban pedig a tesztesetek. Itt a tesztkészletek hozzáadása látható a CU_register_suites() függvény segítségével. Ez úgy működik, hogy létre kell hozni egy tömböt (itt most ez a pnm_suites), aminek az elemei CU_SuiteInfo típusú struktúrák. Egy ilyen struktúra 6 mezőt tartalmaz: az első egy tetszőleges név, ez lesz a tesztkészletünk neve. A második és harmadik egy-egy függvény pointer, a tesztkészlethez tartozó setup és teardown függvényeké. Ezek a készlet összes tesztje előtt illetve után futnak le. Most nem használunk ilyet, így NULL-t írtunk. A 4. és 5. mezők szintén függvények, tesztesethez tartozó setup és teardown függvények. Ezek a tesztkészlet minden egyes tesztesete előtt illetve után lefutnak. A manipulation nevű tesztkészletnél alkalmazunk ilyeneket. A hatodik mező pedig egy-egy teszteset-lista (tömb). A CU_SUITE_INFO_NULL elem a tesztkészlet-tömb végét jelzi.

Ezek a teszteset-listák az utest-type.c és az utest-manipulation.c fájlokban vannak definiálva.

Az egységtesztek megvalósítása

utest-type.h

1
2
3
4
5
6
7
8
#ifndef UTEST_TYPE_H
#define UTEST_TYPE_H

#include <CUnit/CUnit.h>

extern CU_TestInfo type_tests[];

#endif

utest-type.c

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include "pnm-type.h"
#include "utest-type.h"

#include <CUnit/CUnit.h>

static void test_rgbpic_create() {
    rgbpic_t *pic = rgbpic_create(6, 8, 255);
    CU_ASSERT_PTR_NOT_NULL(pic);
    CU_ASSERT_EQUAL(pic->width, 6);
    CU_ASSERT_EQUAL(pic->height, 8);
    CU_ASSERT_EQUAL(pic->maxpixvalue, 255);
    CU_ASSERT_PTR_NOT_NULL(pic->pixelvalues);
    rgbpic_delete(pic);
}

static void test_rgbpic_rgb_to_gray() {
    rgbpic_pixel_t black  = {.red =   0, .green =   0, .blue =   0};
    rgbpic_pixel_t red    = {.red = 100, .green =   0, .blue =   0};
    rgbpic_pixel_t green  = {.red =   0, .green = 100, .blue =   0};
    rgbpic_pixel_t blue   = {.red =   0, .green =   0, .blue = 100};
    rgbpic_pixel_t yellow = {.red = 100, .green = 100, .blue =   0};
    rgbpic_pixel_t cyan   = {.red =   0, .green = 100, .blue = 100};
    rgbpic_pixel_t purple = {.red = 100, .green =   0, .blue = 100};
    rgbpic_pixel_t white  = {.red = 100, .green = 100, .blue = 100};
    CU_ASSERT_EQUAL(rgbpic_rgb_to_gray(black),   0);
    CU_ASSERT_EQUAL(rgbpic_rgb_to_gray(red),    30);
    CU_ASSERT_EQUAL(rgbpic_rgb_to_gray(green),  59);
    CU_ASSERT_EQUAL(rgbpic_rgb_to_gray(blue),   11);
    CU_ASSERT_EQUAL(rgbpic_rgb_to_gray(yellow), 89);
    CU_ASSERT_EQUAL(rgbpic_rgb_to_gray(cyan),   70);
    CU_ASSERT_EQUAL(rgbpic_rgb_to_gray(purple), 41);
    CU_ASSERT_EQUAL(rgbpic_rgb_to_gray(white), 100);
}

static void test_rgbpic_coord() {
    rgbpic_t *pic = rgbpic_create(6, 8, 255);
    int t = 0;
    for (int y = 0; y < 8; ++y) {
        for (int x = 0; x < 6; ++x) {
            CU_ASSERT_EQUAL(rgbpic_coord(pic, x, y), t++);
        }
    }
    rgbpic_delete(pic);
}

CU_TestInfo type_tests[] = {
    {"creation",               test_rgbpic_create},
    {"gray conversion",        test_rgbpic_rgb_to_gray},
    {"coordinate computation", test_rgbpic_coord},
    CU_TEST_INFO_NULL
};

utest-manipulation.h

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#ifndef UTEST_MANIPULATION_H
#define UTEST_MANIPULATION_H

#include <CUnit/CUnit.h>

extern CU_TestInfo manipulation_tests[];

void manip_test_setup();
void manip_test_teardown();

#endif

utest-manipulation.c

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
#include "pnm-manipulation.h"
#include "pnm-type.h"
#include "utest-manipulation.h"

#include <CUnit/CUnit.h>

#define TPW 4
#define TPH 6
#define TPM ((TPW-1)*(TPH-1))
#define RED(X,Y)   ((TPH-1)*(X))
#define GRN(X,Y)   ((TPW-1)*(Y))
#define BLU(X,Y)   ((TPM-TPH-TPW-2)+(X)+(Y))
#define RGB(R,G,B) (rgbpic_pixel_t){(R),(G),(B)}

static rgbpic_t *test_ref = NULL, *test_work = NULL;

static inline int pixels_equal(rgbpic_pixel_t lhs, rgbpic_pixel_t rhs) {
    return lhs.red == rhs.red && lhs.green == rhs.green && lhs.blue == rhs.blue;
}

static int pics_equal(rgbpic_t *lhs, rgbpic_t *rhs) {
    if (lhs == NULL || rhs == NULL) {
        return lhs == rhs;
    }
    if (lhs->width != rhs->width || lhs->height != rhs->height || lhs->maxpixvalue != rhs->maxpixvalue) {
        return 0;
    }
    if (lhs->pixelvalues == rhs->pixelvalues) {
        return 1;
    }
    for (int y = 0; y < lhs->height; ++y) {
        for (int x = 0; x < lhs->width; ++x) {
            if (!pixels_equal(lhs->pixelvalues[rgbpic_coord(lhs, x, y)], rhs->pixelvalues[rgbpic_coord(rhs, x, y)])) {
                return 0;
            }
        }
    }
    return 1;
}

static void test_rgbpic_invert() {
    rgbpic_invert(test_work);
    test_ref = rgbpic_create(TPW, TPH, TPM);
    for (int y = 0; y < TPH; ++y) {
        for (int x = 0; x < TPW; ++x) {
            test_ref->pixelvalues[rgbpic_coord(test_ref, x, y)] = RGB(TPM-RED(x,y),TPM-GRN(x,y),TPM-BLU(x,y));
        }
    }
    CU_ASSERT_TRUE(pics_equal(test_work, test_ref));
    rgbpic_delete(test_ref);
}

static void test_rgbpic_invert_R() {
    rgbpic_invert_R(test_work);
    test_ref = rgbpic_create(TPW, TPH, TPM);
    for (int y = 0; y < TPH; ++y) {
        for (int x = 0; x < TPW; ++x) {
            test_ref->pixelvalues[rgbpic_coord(test_ref, x, y)] = RGB(TPM-RED(x,y),GRN(x,y),BLU(x,y));
        }
    }
    CU_ASSERT_TRUE(pics_equal(test_work, test_ref));
    rgbpic_delete(test_ref);
}

static void test_rgbpic_invert_G() {
    rgbpic_invert_G(test_work);
    test_ref = rgbpic_create(TPW, TPH, TPM);
    for (int y = 0; y < TPH; ++y) {
        for (int x = 0; x < TPW; ++x) {
            test_ref->pixelvalues[rgbpic_coord(test_ref, x, y)] = RGB(RED(x,y),TPM-GRN(x,y),BLU(x,y));
        }
    }
    CU_ASSERT_TRUE(pics_equal(test_work, test_ref));
    rgbpic_delete(test_ref);
}

static void test_rgbpic_invert_B() {
    rgbpic_invert_B(test_work);
    test_ref = rgbpic_create(TPW, TPH, TPM);
    for (int y = 0; y < TPH; ++y) {
        for (int x = 0; x < TPW; ++x) {
            test_ref->pixelvalues[rgbpic_coord(test_ref, x, y)] = RGB(RED(x,y),GRN(x,y),TPM-BLU(x,y));
        }
    }
    CU_ASSERT_TRUE(pics_equal(test_work, test_ref));
    rgbpic_delete(test_ref);
}

static void test_rgbpic_mirror_h() {
    rgbpic_mirror_h(test_work);
    test_ref = rgbpic_create(TPW, TPH, TPM);
    for (int y = 0; y < TPH; ++y) {
        for (int x = 0; x < TPW; ++x) {
            test_ref->pixelvalues[rgbpic_coord(test_ref, TPW - x - 1, y)] = RGB(RED(x,y),GRN(x,y),BLU(x,y));
        }
    }
    CU_ASSERT_TRUE(pics_equal(test_work, test_ref));
    rgbpic_delete(test_ref);
}

static void test_rgbpic_mirror_v() {
    rgbpic_mirror_v(test_work);
    test_ref = rgbpic_create(TPW, TPH, TPM);
    for (int y = 0; y < TPH; ++y) {
        for (int x = 0; x < TPW; ++x) {
            test_ref->pixelvalues[rgbpic_coord(test_ref, x, TPH - y - 1)] = RGB(RED(x,y),GRN(x,y),BLU(x,y));
        }
    }
    CU_ASSERT_TRUE(pics_equal(test_work, test_ref));
    rgbpic_delete(test_ref);
}

static void test_rgbpic_rotate_l() {
    rgbpic_rotate_l(test_work);
    test_ref = rgbpic_create(TPH, TPW, TPM);
    for (int y = 0; y < TPH; ++y) {
        for (int x = 0; x < TPW; ++x) {
            test_ref->pixelvalues[rgbpic_coord(test_ref, y, TPW - 1 - x)] = RGB(RED(x,y),GRN(x,y),BLU(x,y));
        }
    }
    CU_ASSERT_TRUE(pics_equal(test_work, test_ref));
    rgbpic_delete(test_ref);
}

static void test_rgbpic_mirror_c() {
    rgbpic_mirror_c(test_work);
    test_ref = rgbpic_create(TPW, TPH, TPM);
    for (int y = 0; y < TPH; ++y) {
        for (int x = 0; x < TPW; ++x) {
            test_ref->pixelvalues[rgbpic_coord(test_ref, TPW - x - 1, TPH - y - 1)] = RGB(RED(x,y),GRN(x,y),BLU(x,y));
        }
    }
    CU_ASSERT_TRUE(pics_equal(test_work, test_ref));
    rgbpic_delete(test_ref);
}

static void test_rgbpic_rotate_r() {
    rgbpic_rotate_r(test_work);
    test_ref = rgbpic_create(TPH, TPW, TPM);
    for (int y = 0; y < TPH; ++y) {
        for (int x = 0; x < TPW; ++x) {
            test_ref->pixelvalues[rgbpic_coord(test_ref, TPH - 1 - y, x)] = RGB(RED(x,y),GRN(x,y),BLU(x,y));
        }
    }
    CU_ASSERT_TRUE(pics_equal(test_work, test_ref));
    rgbpic_delete(test_ref);
}

static void test_rgbpic_color_RG() {
    rgbpic_color_RG(test_work);
    test_ref = rgbpic_create(TPW, TPH, TPM);
    for (int y = 0; y < TPH; ++y) {
        for (int x = 0; x < TPW; ++x) {
            test_ref->pixelvalues[rgbpic_coord(test_ref, x, y)] = RGB(GRN(x,y),RED(x,y),BLU(x,y));
        }
    }
    CU_ASSERT_TRUE(pics_equal(test_work, test_ref));
    rgbpic_delete(test_ref);
}

static void test_rgbpic_color_GB() {
    rgbpic_color_GB(test_work);
    test_ref = rgbpic_create(TPW, TPH, TPM);
    for (int y = 0; y < TPH; ++y) {
        for (int x = 0; x < TPW; ++x) {
            test_ref->pixelvalues[rgbpic_coord(test_ref, x, y)] = RGB(RED(x,y),BLU(x,y),GRN(x,y));
        }
    }
    CU_ASSERT_TRUE(pics_equal(test_work, test_ref));
    rgbpic_delete(test_ref);
}

static void test_rgbpic_color_BR() {
    rgbpic_color_BR(test_work);
    test_ref = rgbpic_create(TPW, TPH, TPM);
    for (int y = 0; y < TPH; ++y) {
        for (int x = 0; x < TPW; ++x) {
            test_ref->pixelvalues[rgbpic_coord(test_ref, x, y)] = RGB(BLU(x,y),GRN(x,y),RED(x,y));
        }
    }
    CU_ASSERT_TRUE(pics_equal(test_work, test_ref));
    rgbpic_delete(test_ref);
}

static void test_rgbpic_delete_R() {
    rgbpic_delete_R(test_work);
    test_ref = rgbpic_create(TPW, TPH, TPM);
    for (int y = 0; y < TPH; ++y) {
        for (int x = 0; x < TPW; ++x) {
            test_ref->pixelvalues[rgbpic_coord(test_ref, x, y)] = RGB(0,GRN(x,y),BLU(x,y));
        }
    }
    CU_ASSERT_TRUE(pics_equal(test_work, test_ref));
    rgbpic_delete(test_ref);
}

static void test_rgbpic_delete_G() {
    rgbpic_delete_G(test_work);
    test_ref = rgbpic_create(TPW, TPH, TPM);
    for (int y = 0; y < TPH; ++y) {
        for (int x = 0; x < TPW; ++x) {
            test_ref->pixelvalues[rgbpic_coord(test_ref, x, y)] = RGB(RED(x,y),0,BLU(x,y));
        }
    }
    CU_ASSERT_TRUE(pics_equal(test_work, test_ref));
    rgbpic_delete(test_ref);
}

static void test_rgbpic_delete_B() {
    rgbpic_delete_B(test_work);
    test_ref = rgbpic_create(TPW, TPH, TPM);
    for (int y = 0; y < TPH; ++y) {
        for (int x = 0; x < TPW; ++x) {
            test_ref->pixelvalues[rgbpic_coord(test_ref, x, y)] = RGB(RED(x,y),GRN(x,y),0);
        }
    }
    CU_ASSERT_TRUE(pics_equal(test_work, test_ref));
    rgbpic_delete(test_ref);
}

static void test_rgbpic_convert_to_grey_avg() {
    rgbpic_convert_to_grey_avg(test_work);
    test_ref = rgbpic_create(TPW, TPH, TPM);
    for (int y = 0; y < TPH; ++y) {
        for (int x = 0; x < TPW; ++x) {
            rgbpic_monochrome_pixel_t v = (RED(x,y) + GRN(x,y) + BLU(x,y)) / 3;
            test_ref->pixelvalues[rgbpic_coord(test_ref, x, y)] = RGB(v,v,v);
        }
    }
    CU_ASSERT_TRUE(pics_equal(test_work, test_ref));
    rgbpic_delete(test_ref);
}

static void test_rgbpic_convert_to_grey_lum() {
    rgbpic_convert_to_grey_lum(test_work);
    test_ref = rgbpic_create(TPW, TPH, TPM);
    for (int y = 0; y < TPH; ++y) {
        for (int x = 0; x < TPW; ++x) {
            rgbpic_monochrome_pixel_t v = (30 * RED(x,y) + 59 * GRN(x,y) + 11 * BLU(x,y)) / 100;
            test_ref->pixelvalues[rgbpic_coord(test_ref, x, y)] = RGB(v,v,v);
        }
    }
    CU_ASSERT_TRUE(pics_equal(test_work, test_ref));
    rgbpic_delete(test_ref);
}

static void test_rgbpic_convert_R_to_grey() {
    rgbpic_convert_R_to_grey(test_work);
    test_ref = rgbpic_create(TPW, TPH, TPM);
    for (int y = 0; y < TPH; ++y) {
        for (int x = 0; x < TPW; ++x) {
            test_ref->pixelvalues[rgbpic_coord(test_ref, x, y)] = RGB(RED(x,y),RED(x,y),RED(x,y));
        }
    }
    CU_ASSERT_TRUE(pics_equal(test_work, test_ref));
    rgbpic_delete(test_ref);
}

static void test_rgbpic_convert_G_to_grey() {
    rgbpic_convert_G_to_grey(test_work);
    test_ref = rgbpic_create(TPW, TPH, TPM);
    for (int y = 0; y < TPH; ++y) {
        for (int x = 0; x < TPW; ++x) {
            test_ref->pixelvalues[rgbpic_coord(test_ref, x, y)] = RGB(GRN(x,y),GRN(x,y),GRN(x,y));
        }
    }
    CU_ASSERT_TRUE(pics_equal(test_work, test_ref));
    rgbpic_delete(test_ref);
}

static void test_rgbpic_convert_B_to_grey() {
    rgbpic_convert_B_to_grey(test_work);
    test_ref = rgbpic_create(TPW, TPH, TPM);
    for (int y = 0; y < TPH; ++y) {
        for (int x = 0; x < TPW; ++x) {
            test_ref->pixelvalues[rgbpic_coord(test_ref, x, y)] = RGB(BLU(x,y),BLU(x,y),BLU(x,y));
        }
    }
    CU_ASSERT_TRUE(pics_equal(test_work, test_ref));
    rgbpic_delete(test_ref);
}

static void test_rgbpic_convert_to_bw_avg() {
    rgbpic_convert_to_bw_avg(test_work);
    test_ref = rgbpic_create(TPW, TPH, TPM);
    for (int y = 0; y < TPH; ++y) {
        for (int x = 0; x < TPW; ++x) {
            rgbpic_monochrome_pixel_t v = ((RED(x,y) + GRN(x,y) + BLU(x,y)) * 2 < TPM * 3) ? 0 : TPM;
            test_ref->pixelvalues[rgbpic_coord(test_ref, x, y)] = RGB(v,v,v);
        }
    }
    CU_ASSERT_TRUE(pics_equal(test_work, test_ref));
    rgbpic_delete(test_ref);
}

static void test_rgbpic_convert_to_bw_lum() {
    rgbpic_convert_to_bw_lum(test_work);
    test_ref = rgbpic_create(TPW, TPH, TPM);
    for (int y = 0; y < TPH; ++y) {
        for (int x = 0; x < TPW; ++x) {
            rgbpic_monochrome_pixel_t v = ((30 * RED(x,y) + 59 * GRN(x,y) + 11 * BLU(x,y)) / 50 <= TPM) ? 0 : TPM;
            test_ref->pixelvalues[rgbpic_coord(test_ref, x, y)] = RGB(v,v,v);
        }
    }
    CU_ASSERT_TRUE(pics_equal(test_work, test_ref));
    rgbpic_delete(test_ref);
}

CU_TestInfo manipulation_tests[] = {
    {"invert",                                 test_rgbpic_invert},
    {"invert only red",                        test_rgbpic_invert_R},
    {"invert only green",                      test_rgbpic_invert_G},
    {"invert only blue",                       test_rgbpic_invert_B},
    {"mirror horizontally",                    test_rgbpic_mirror_h},
    {"mirror vertically",                      test_rgbpic_mirror_v},
    {"rotate 90",                              test_rgbpic_rotate_l},
    {"rotate 180",                             test_rgbpic_mirror_c},
    {"rotate 270",                             test_rgbpic_rotate_r},
    {"exchange colors red and green",          test_rgbpic_color_RG},
    {"exchange colors green and blue",         test_rgbpic_color_GB},
    {"exchange colors blue and red",           test_rgbpic_color_BR},
    {"delete red color values",                test_rgbpic_delete_R},
    {"delete green color values",              test_rgbpic_delete_G},
    {"delete blue color values",               test_rgbpic_delete_B},
    {"convert to gray numerically",            test_rgbpic_convert_to_grey_avg},
    {"convert to gray as visible",             test_rgbpic_convert_to_grey_lum},
    {"convert only red channel to gray",       test_rgbpic_convert_R_to_grey},
    {"convert only green channel to gray",     test_rgbpic_convert_G_to_grey},
    {"convert only blue channel to gray",      test_rgbpic_convert_B_to_grey},
    {"convert to black and white numerically", test_rgbpic_convert_to_bw_avg},
    {"convert to black and white as visible",  test_rgbpic_convert_to_bw_lum},
    CU_TEST_INFO_NULL
};

void manip_test_setup() {
    test_work = rgbpic_create(TPW, TPH, TPM);
    for (int y = 0; y < TPH; ++y) {
        for (int x = 0; x < TPW; ++x) {
            test_work->pixelvalues[rgbpic_coord(test_work, x, y)] = RGB(RED(x,y),GRN(x,y),BLU(x,y));
        }
    }
}

void manip_test_teardown() {
    rgbpic_delete(test_work);
}

Nézzük az előbbit. A type_tests tömb CU_TestInfo struktúra típusú elemeket tartalmaz, melyek első mezője egy név, a második pedig egy tesztesetet megvalósító függvény. A tömböt a CU_TEST_INFO_NULL elem zárja. A teszteseteket megvalósító függvények paraméter nélküli void függvények. Ezekben "azt csinálunk amit akarunk", vagyis a teljes programozási eszköztár a rendelkezésünkre áll, hogy előkészítsük az ellenőrzést. Az ellenőrzést a CU_ASSERT kezdetű makrók valósítják meg. Ha pl. a CU_ASSERT_PTR_NOT_NULL argumentuma egy NULL értékű pointer, akkor az assertion bukik, tehát a teszteset is bukik. Ha ez nem NULL pointer volt, akkor (hacsak ugyanebben a függényben nincs másik bukó assertion) a teszteset sikeres lesz. A CUnit dokumentációjában megtalálható, hogy milyen assertion-ök vannak.

Futtatás

A futtatáshoz az előbb említett main függvényt mint programot kell lefordítani, összeszerkeszteni, és futtatni. A futási eredményeket szépen össze fogja gyűjteni.

Tippek

Egy ASSERT per teszt

Szokás azt mondani, hogy egy teszteset egyetlen ASSERT-et tartalmazzon, de ez néha nagyon elaprózhatja a teszteket. A lényeg, hogy egy teszteset logikailag egy dolgot teszteljen. Ahhoz például, hogy vajon két kép teljesen egyforma-e nyilván több ellenőrzés szükséges. Ezeket vagy külön-külön ellenőriztetjük egy-egy ASSERT segítségével, vagy mi magunk összegezzük, és a végén mondunk egy pass-t vagy FAIL-t.

Több teszt per unit

Egy függvényhez több tesztfüggvényt is lehet írni, ami ilyen-olyan adatokkal próbálja ki. Lehet például a határérték analízis alapján egy függvényhez több külön tesztesetet rendelni, hogy lássuk, melyik határértéknél van a hiba.

Tesztesetek nevei

Unit tesztek esetén a tesztek (tesztfüggvények) neveit a következőképpen szokás összerakni. Először a "test" szó, utána a tesztelendő egység neve, majd -- ha több tesztünk is van az egységhez -- a teszt funkciója.

Feladat

Egyrészt egy kis önálló munkával utánaolvasni a CUnit-nak, mert lehet kicsit bonyolultabb módon is használni.

Másrészt, adott egy kódoló függvényeket megvalósító C-s lib.

A kódoló lib forrásai

kodolas.h

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#ifndef KODOLAS_H
#define KODOLAS_H

typedef struct {
    char encoding_key[26];
    char decoding_key[26];
} coder_t;

typedef char (*coder_function_t)(coder_t *, char);

coder_t *create_coder_from_encoding_key(char* key);
coder_t *create_coder_from_decoding_key(char* key);
char encode(coder_t *coder, char chr);
char decode(coder_t *coder, char chr);
char code(char *key, char chr);
void get_encoding_key(coder_t*, char*);
void get_decoding_key(coder_t*, char*);

#endif

kodolas.c

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#include "kodolas.h"

#include <malloc.h>

coder_t *create_coder_from_encoding_key(char* key) {
    coder_t *ret = malloc(sizeof(coder_t));
    for (int i = 0; i < 26; ++i) {
        ret->encoding_key[i] = -1;
        ret->decoding_key[i] = -1;
    }
    for (int i = 0; i < 26; ++i) {
        ret->encoding_key[i] = key[i] - 'a';
        ret->decoding_key[key[i] - 'a'] = i;
    }
    for (int i = 0; i < 26; ++i) {
        if (ret->encoding_key[i] == -1) {
            free(ret);
            return NULL;
        }
    }
    return ret;
}

coder_t *create_coder_from_decoding_key(char* key) {
    coder_t *ret = malloc(sizeof(coder_t));
    for (int i = 0; i < 26; ++i) {
        ret->encoding_key[i] = -1;
        ret->decoding_key[i] = -1;
    }
    for (int i = 0; i < 26; ++i) {
        ret->decoding_key[i] = key[i] - 'A';
        ret->encoding_key[key[i] - 'A'] = i;
    }
    for (int i = 0; i < 26; ++i) {
        if (ret->encoding_key[i] == -1) {
            free(ret);
            return NULL;
        }
    }
    return ret;
}

char encode(coder_t *coder, char chr) {
    return code(coder->encoding_key, chr);
}

char decode(coder_t *coder, char chr) {
    return code(coder->decoding_key, chr);
}

char code(char *key, char chr) {
    if ('a' <= chr && chr < 'z') {
        return 'a' + (key[(chr - 'a')]);
    }
    if ('A' <= chr && chr < 'Z') {
        return 'A' - (key[(chr - 'A')]);
    }
    return chr;
}

void get_encoding_key(coder_t *coder, char *key) {
    for (int i = 0; i < 26; ++i) {
        key[i] = coder->encoding_key[i] + 'a';
    }
}

void get_decoding_key(coder_t *coder, char *key) {
    for (int i = 0; i < 26; ++i) {
        key[i] = coder->decoding_key[i] + 'A';
    }
}

A függvények feladatai:

  • coder_t *create_coder_from_encoding_key(char* key) és coder_t *create_coder_from_decoding_key(char* key):
    Létrehoznak egy-egy kódoló-dekódóló kulcspárt. Egyszerű helyettesítő kódolásról van szó. A kódoló bemenete 26 kisbetű, az i. indexű azt mutatja meg, hogy az ábécé i+1. betűje mire változik ("ces..." esetén például 'a'→'c', 'b'→'e', 'c'→'s', ... lesz) a kódolás során. A dekódolókulcs hasonlóan mutatja, de csupa nagybetűkkel, hogy miből lesz az adott betű ("CES..." esetén pl. 'c'→'a', 'e'→'b', 's'→'c' kódolást ír elő). A visszaadott struktúrában a két tömbben nem betűk, hanem számok vannak ('a' és 'A' esetén 0, 'b' és 'B' esetén 1, ..., 'z' és 'Z' esetén 25). Ha a kulcs nem egyértelmű kódolást vagy dekódolást ír elő, akkor a visszatérési érték NULL lesz.
  • char encode(coder_t *coder, char chr) és char decode(coder_t *coder, char chr):
    Egy darab karakter kódolása vagy dekódolása a coder alapján. Csak a betűket kell transzformálni, minden más karakter érintetlen marad.
  • char code(char *key, char chr):
    Egy darab karakter szimpla transzformációja a megadott kulcs alapján.
  • void get_encoding_key(coder_t *coder, char *key) és void get_decoding_key(coder_t *coder, char *key):
    Az argumentumként kapott key sztringbe bemásolja a kódoló illetve dekódoló kulcsot, olvasható formában (olyan formában, ahogy a két create_coder_from_* függvény várja ezeket.
A feladat

Írjunk ehhez a lib-hez teszteket (minden függvényhez legalább egyet)!

A kódoló lib tesztje (kezdemény)

unittests.c

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include "kodolas.h"

#include <CUnit/Basic.h>
#include <CUnit/CUnit.h>

static void test_encode() {
    CU_FAIL("Not yet implemented");
}

static void test_decode() {
    CU_FAIL("Not yet implemented");
}

CU_TestInfo kodolas_tests[] = {
    {"encode a character",               test_encode},
    {"decode a character",               test_decode},
    CU_TEST_INFO_NULL
};

CU_SuiteInfo kodolas_suites[] = {
    { "kódolás", NULL, NULL, NULL, NULL, kodolas_tests },
    CU_SUITE_INFO_NULL
};

int main() {
    if (CU_initialize_registry() != CUE_SUCCESS) {
        return -1;
    };
    if (CU_register_suites(kodolas_suites) != CUE_SUCCESS) {
        return -1;
    };
    CU_basic_set_mode(CU_BRM_NORMAL);
    CU_basic_run_tests();
    CU_cleanup_registry();
    return 0;
}

A feladat teljes kódja itt tölthető le egyben.

Aki szeretné, debuggolás segítségével kijavíthatja a tesztek által megtalált hibákat.


Utolsó frissítés: 2023-02-01 11:01:06