Commit 378c736d authored by Guillaume CLOCHARD's avatar Guillaume CLOCHARD
Browse files

Merge branch 'tests' into 'master'

Ajout tests unitaires

Ajout petit à petit de tests unitaires sur les fonctions disponibles sur master.

Réparation / optimisation des fonctions testées.

![c1](https://gitlab.univ-nantes.fr/E132397K/Ragnulf/uploads/e6a0e67680f896f3e1a03941316c2bd9/c1.png)

![c2](https://gitlab.univ-nantes.fr/E132397K/Ragnulf/uploads/88f851152d2bb8e836f0b68f240c330e/c2.png)


See merge request !12
parents 10596f16 d112ac16
[submodule "tests/samples/cubejs"]
path = tests/samples/cubejs
url = git@github.com:akheron/cubejs.git
......@@ -100,7 +100,7 @@ class Cube():
Blue (B) = 1
Red (R) = 2
Green (G) = 3
Orange (0) = 4
Orange (O) = 4
Yellow (Y) = 5
Convention des couleurs des faces :
......@@ -237,13 +237,15 @@ class Cube():
correct
'''
if not cube in PETITS_CUBES:
return False
raise ValueError(str(cube) + " n'est pas un petit cube")
elif not len(cube) == len(val):
raise ValueError("La taille du cube ne correspond pas")
else:
groupes = [0] * 3
for c in val:
i = codeToGroup(c)
if i == None:
return False
raise ValueError(str(c) + " n'est pas une couleur")
else:
groupes[i] += 1
......@@ -256,7 +258,7 @@ class Cube():
self.cubes[cube] = Array(val)
return True
else:
return False
raise ValueError("Un même groupe de couleur est présent 2 fois")
def rot_L(self):
"""
......@@ -823,6 +825,9 @@ class Cube():
:Args:
petit_cube {String} Le petit cube qu'il faut regarder
c1 {int}
c2 {int}
c3 {int} Defaut à None pour le cas d'un cube arrête
:Returns:
{Boolean|None} True si les couleurs sont présentes
......@@ -840,7 +845,6 @@ class Cube():
else:
return None
def scramble(self, str):
'''
scramble
......@@ -895,9 +899,9 @@ class Cube():
return True
def face_resolu(self,face):
def face_resolue(self, face):
"""
face_resolu
face_resolue
Fonction qui dit si une face du cube (passé en paramètre) est résolu ou non
......@@ -905,7 +909,7 @@ class Cube():
face {Sting} une face du cube
:Example:
c.face_resolu(('U')
c.face_resolue(('U')
:Returns:
{Boolean} True toute la face correspond à sa couleur
......@@ -922,7 +926,6 @@ class Cube():
self.get_facette('FRU',2),
self.get_facette('RBU',2),
self.get_facette('BLU',2),
)
return faceJaune == (5,5,5,5,5,5,5,5) # Test si toute les facettes sont jaune
elif face == 'D': # Si la face Down du cube
......@@ -991,7 +994,7 @@ class Cube():
)
return faceOrange == (4,4,4,4,4,4,4,4) # Test si toute les facettes sont orange
else:
return "Erreur dans les paramètres de la fonction"
raise ValueError(str(face) + "n'est pas une face")
def resolu(self):
"""
......@@ -1055,13 +1058,13 @@ if __name__ == '__main__':
# Exemple d'utilisation du Cube
c = Cube() #par défaut, ce cube est résolu
print(c)
#Test fonction face_resolu
print(c.face_resolu("U"))
print(c.face_resolu("D"))
print(c.face_resolu("B"))
print(c.face_resolu("F"))
print(c.face_resolu("L"))
print(c.face_resolu("R"))
#Test fonction face_resolue
print(c.face_resolue("U"))
print(c.face_resolue("D"))
print(c.face_resolue("B"))
print(c.face_resolue("F"))
print(c.face_resolue("L"))
print(c.face_resolue("R"))
print(c.to_line())
print('Couleur facette BLD/indice 0 : ' + str(c.get_facette('BLD',0))) #test du getter
......
......@@ -5,8 +5,6 @@ Ragnulf
Résolution d'un Rubik's Cube par la méthode CFOP.
Projet d'Algorithmique et Programmation INFO3 Polytech Nantes.
# TL;DR
```bash
python poqb.y --cube OGRBWYBGBGYYOYOWOWGRYOOOBGBRRYRBWWWRBWYGROWGRYBRGYWBOG
......@@ -59,6 +57,19 @@ print(poqb.solve('OGRBWYBGBGYYOYOWOWGRYOOOBGBRRYRBWWWRBWYGROWGRYBRGYWBOG'))
# En savoir plus
[:link: Wiki Gitlab](https://gitlab.univ-nantes.fr/E132397K/Ragnulf/wikis/home)
# Tests
Pour lancer les tests unitaires :
```python
python -m unittest discover -v
```
Ou, avec `green` (`pip3 install green`), pour avoir un peu de couleurs :
```python
green -vvv
#ou
green -vvv -r #avec coverage d'installé sur la machine
```
# Membres :
- CLOCHARD Guillaume
- LANGELIER Arnaud
......
......@@ -54,7 +54,7 @@ def algo_cfop(c):
différents mouvements à effectuer pour résoudre le cube
'''
face_resolue = lambda x: x.face_resolu('U')
face_resolue = lambda x: x.face_resolue('U')
resolu = lambda x: x.resolu()
noop = lambda x: (x, ())
......@@ -1525,7 +1525,7 @@ if __name__ == '__main__':
c,mouv2 = ftl(c)
validiteFtl = "ftl ok" if ftl_valide(c) else "FTL INVALIDE"
c,mouv3=oll(c)
validiteOll = "oll ok" if c.face_resolu('U') else "OLL INVALIDE"
validiteOll = "oll ok" if c.face_resolue('U') else "OLL INVALIDE"
c,mouv4=pll(c)
validitePll = "pll ok" if c.resolu() else "PLL INVALIDE"
......
from Cube import Cube
from utils import colorToCode, colorize
def decomposition_faces(str):
def decomposition_faces(s):
"""
decomposition_faces
Décompose la chaîne représentant les 54 facettes en 6 faces
:Args:
str {String} chaîne de 54 facettes
s {String} chaîne de 54 facettes
:Returns:
{Boolean|String}, {List|None} (error, faces)
Liste de 6 liste de 9 éléments (les 6 faces)
couleurs codées en entiers
{List|None} Liste de 6 liste de 9 éléments (les 6 faces)
couleurs codées en entiers
"""
if not len(str) == 54: #check taille str
return 'La chaîne ne fait pas 54 caractères', None #returns error
if not type(s) == str:
raise TypeError(str(s) + " n'est pas une chaîne")
elif not len(s) == 54: #check taille str
raise ValueError('La chaîne ne fait pas 54 caractères')
else:
faces = [[] for i in range(6)] #init des faces
for i in range(len(str)):
case = colorToCode(str[i])
for i in range(len(s)):
case = colorToCode(s[i])
if i < 9: #face up
faces[0].append(case)
elif i > 44: #face down
......@@ -39,7 +40,7 @@ def decomposition_faces(str):
else: # i_ < 12, face back
faces[4].append(case)
return False, faces
return faces
def check_faces(faces):
'''
......@@ -47,7 +48,6 @@ def check_faces(faces):
Contrôle de la validité des faces passées en paramètes
On vérifie ici :
- on a bien 9 facettes par face
- on a bien une face de chaque couleur (facette du milieu)
- on a bien 9 facettes pour chacune des 6 couleurs
......@@ -67,24 +67,17 @@ def check_faces(faces):
while i < l:
face = faces[i]
if not len(face) == 9: #on vérifie le nombre de facettes dans la face
error = 'Face ' + str(i) + ' n\'a pas 9 facettes'
elif face[4] == None: #on valide la couleur de la face
error = 'Caractères non autorisés'
else:
couleurs_faces[face[4]] = True
for c in face:
couleurs[c] += 1 #on compte le nombre de facettes de la couleur `c`
couleurs_faces[face[4]] = True #on enregistre la présence de cette face
for c in face:
couleurs[c] += 1 #on compte le nombre de facettes de la couleur `c`
i += 1
if not error:
if not sum(couleurs_faces) == 6: #on a pas une couleur différente par face
error = 'Chaque face ne possède pas une couleur différente'
if not sum(couleurs_faces) == 6: #on a pas une couleur différente par face
error = 'Chaque face ne possède pas une couleur différente'
if not couleurs.count(9) == 6: #on a pas 6 couleurs présentes 9 fois, erreur
error = 'Toutes les couleurs ne sont pas présentes 9 fois'
if not couleurs.count(9) == 6: #on a pas 6 couleurs présentes 9 fois, erreur
error = 'Toutes les couleurs ne sont pas présentes 9 fois'
return error
......@@ -110,9 +103,10 @@ def lecture_cube(str_cube):
#1. Découpage des faces de la chaîne en entrée
c = Cube()
error, faces = decomposition_faces(str_cube) #on découpe en faces
if error:
return error, None
try:
faces = decomposition_faces(str_cube) #on découpe en faces
except ValueError as e:
return str(e), None
#2. Vérification des faces
......@@ -158,16 +152,15 @@ def lecture_cube(str_cube):
#on insert ces petits cubes tant qu'on ne détecte pas de petit
#cube défaillant
ok = True
i = 0
l = len(insertions)
while ok and i < l:
ok = c.edit_cube(insertions[i][0], insertions[i][1])
while i < l:
try:
c.edit_cube(insertions[i][0], insertions[i][1])
except ValueError as e:
return "Petits cubes invalides", None
i += 1
if not ok: #si erreur dans les petits cubes, on ne va pas plus loin
return ('Petits cubes invalides', None)
#4. Mettre le cube dans la bonne position
#(face blanche en bas, bleue en front)
......@@ -220,42 +213,13 @@ def lecture_cube(str_cube):
if __name__ == "__main__":
tests = [
'AAAA', #erreur de taille
#incorrect, mauvais codage
'VVVVVVVVVOOOBBBRRRGGGOOOBBBRRRGGGOOOBBBRRRGGGWWWWWWWWW',
#incorrect, toutes les faces ne possède pas une couleur différente
'YYYYYYYYYOOOOOOOOOBBBBBBBBBRRRRRRRRRGGGGGGGGGWWWWWWWWW',
#incorrect, on n'a pas 9 facettes de chaque couleur
'YYYYYYYYYOOOOOOOOOOOOOOOBBBRRRGGGOOOOOOOOOOOOWWWWWWWWW',
#incorrect, on a un coin BLU OOO, mais non détecté par check_faces()
'YYYOYGYYYYOOBBBRRRGGYOOOBBBRRRGGGOOOBBBRRRGGGWWWWWWWWW',
'YYYYYYYYYOOOBBBRRRGGGOOOBBBRRRGGGOOOBBBRRRGGGWWWWWWWWW', #correct
'WWWWWWWWWOOOGGGRRRBBBOOOGGGRRRBBBOOOGGGRRRBBBYYYYYYYYY', #correct
'YYYYYYYYYGGGOOOBBBRRRGGGOOOBBBRRRGGGOOOBBBRRRWWWWWWWWW', #correct
'GGGGGGGGGOOOYYYRRRWWWOOOYYYRRRWWWOOOYYYRRRWWWBBBBBBBBB', #correct
'RRRRRRRRRYYYBBBWWWGGGYYYBBBWWWGGGYYYBBBWWWGGGOOOOOOOOO', #correct
#donné par profs
'OGRBWYBGBGYYOYOWOWGRYOOOBGBRRYRBWWWRBWYGROWGRYBRGYWBOG',
#correct, exemple réel
'WGWBGGYRBOOBRBYOWGRRBOYYORBWWYROGORRYYGOOWBBYGGWWBWGYR',
'GRYRRGBOROBWRBGYOGOGWYGWOYWBBRWWGYOBYWWRBBWRORGGYOYBYO', #correct
]
print('Tests check_faces')
print('=================')
for test in tests:
error, f = decomposition_faces(test)
print(''.join([colorize(c) for c in test]))
print(' Erreur :', check_faces(f) if not error else error)
print('\nTests lecture_cube')
print('\nExemple lecture_cube()')
print('====================')
for test in tests :
print('Cube :')
print('input :', ''.join([colorize(c) for c in test]))
error, cube = lecture_cube(test)
if not error:
print('output:', cube.to_line())
print(cube)
print(' Erreur :', error)
exemple = 'OGRBWYBGBGYYOYOWOWGRYOOOBGBRRYRBWWWRBWYGROWGRYBRGYWBOG'
print('input :', ''.join([colorize(c) for c in exemple]))
error, cube = lecture_cube(exemple)
if not error:
print('output:', cube.to_line())
print(cube)
print('Erreur :', error)
Tests Ragnulf
============
Tests unitaires pour Ragnulf
# Tests
Les tests sont implémentés avec la librairie par défaut `unittest`.
Pour lancer les tests unitaires :
```python
python -m unittest discover -v
```
Ou, avec `green` (`pip3 install green`), pour avoir un peu de couleurs :
```python
green -vvv
```
Et, avec l'information de `coverage` (`pip3 install coverage`) :
```
green -vvv -r #avec coverage d'installé sur la machine
```
# Le jeu de test
Par déaut, un jeu de test est présent dans `samples/sample.json`.
Il contient 60 cubes sur lesquels ont a appliqué les 18 mouvements
`{(X, X', X2) | X = (U, R, F, L, B, D)}` avec la librairie nodejs
[cube.js](https://github.com/akheron/cubejs) et sauvegardé le résulat de chacun.
Il a été généré par `samples/build.js`. On peut lancer la création d'un autre
jeu de test avec `node samples/build.js -o sample_bis.js`.
(function () {
/**
* Générateur de jeux de test pour Ragnulf
*
* Génère un fichier JSON contenant 60 cubes et l'application de tous les
* mouvements sur chacun.
* Le jeu de test va servir pour les tests unitaires de Ragnulf.
*
* @example
*
{
<cube original 1> : {
'U' : <résulat de U sur 1>,
'Ui' : <résulat de Ui sur 1>,
...
},
...
}
*
* @example
*
node build.js //sortie dans sample.js
node build.js -o monBeauFichier_RoiDeNoël.js
*
*/
'use strict';
var fs = require('fs'),
path = require('path'),
Cube = require('./cubejs/lib/cube.js');
var argv = process.argv;
var DEFAULT_FILE = 'sample.json';
var CONVERT_COLORS = {
'U' : 'Y',
'L' : 'O',
'F' : 'B',
'R' : 'R',
'B' : 'G',
'D' : 'W'
};
var CONVERT_MOVES = {
'U' : 'U',
'L' : 'B',
'F' : 'L',
'R' : 'F',
'B' : 'R',
'D' : 'D'
};
/**
* translateRotation
*
* 2 choses ici :
* - convertion des "i" en "'" pour cube.js
* - traduction des rotations
*
* Le dev de cube.js doit avoir une autre logique pour nommer ses rotations
* Pour lui, une rotation B, c'est une rotation L pour nous
* une rotation F, c'est une rotation R pour nous, etc.
*
* @param {String} r Une rotation (notation Ragnulf, ie. Ri)
*
* @return {String} Une rotation (notation cube.js, ie. B')
*/
var translateRotation = function (r) {
var face = CONVERT_MOVES[r[0]],
option = r.length > 1 ? r[1] : null;
if (option === 'i') {
option = "'";
}
return face + (option || '');
};
/**
* fixCubeString
*
* Le développeur de cube.js a implémenté une fonction Cube::asString()
* qui renvoie l'état du cube sous forme de chaîne
* Mais la chaîne ne respecte pas nos notations. On fixe ça ici.
*
* Notations de Cubejs:
*
+------------+
| U1 U2 U3 |
| |
| U4 U5 U6 |
| |
| U7 U8 U9 |
+------------+------------+------------+------------+
| L1 L2 L3 | F1 F2 F3 | R1 R2 F3 | B1 B2 B3 |
| | | | |
| L4 L5 L6 | F4 F5 F6 | R4 R5 R6 | B4 B5 B6 |
| | | | |
| L7 L8 L9 | F7 F8 F9 | R7 R8 R9 | B7 B8 B9 |
+------------+------------+------------+------------+
| D1 D2 D3 |
| |
| D4 D5 D6 |
| |
| D7 D8 D9 |
+------------+
*
* Sous forme de chaîne, le cube est représenté par un ordre super chelou :
*
UUUUUUUUUR...F...D...L...B...
*
* @see https://github.com/akheron/cubejs#cubefromstringstr
*
* @param {String} str Une chaîne renvoyée par Cube::asString()
* @return {String} Le fix
*/
var fixCubeString = function (str) {
var result = str.substr(0, 9) //on commence par la face UP
var lignes = ["", "", ""]; //les futures 3 lignes LFRB
//construction des 3 lignes du milieu (alternance R, F, L, B)
var faces = [4, 2, 1, 5]; //les faces dans l'ordre LFRB
for (var x=0; x < 4; x++) {
var i = faces[x]; //n° de la face
var xoffset = i * 9; //nombre de faces à passer
for (var j=0; j < 3; j++) { //pour les 3 lignes
var yoffset = j * 3; //nombre de ligne à descendre
lignes[j] += str.substr( //on prend 3 caractères
xoffset + yoffset,
3
);
}
}
//on ajoute les 3 lignes
result = result.concat(lignes[0], lignes[1], lignes[2]);
//on ajoute Down
result = result.concat(str.substr(27, 9))
return result; //done
};
/**
* convertColors
*
* Cubejs n'utilise pas les couleurs mais l'identifiant des faces
* (U, L, F, ...) pour nommer ses facettes
* On remplace par les couleurs (Y, O, B, ...)
*
* @param {String} str Un état de cube
*
* @return {String} Même état, mais avec les couleurs
*/
var convertColors = function (str) {
return str.split('').map(function (c) {
return CONVERT_COLORS[c];
}).join('');
};
/**
* buildRandom
*
* Génère un rubik's cube et applique dessus tous les mouvements.
* Retourne le cube random et les résultats de tous les mouvements.
*
* @param {Boolean} random Utiliser un cube random. Defaut True.
* Sinon, cube résolu.
* @param {Function} callback
*/
var build = function (random, callback) {
var moves = { //les 18 mouvements
"U" : null,
"Ui" : null,
"U2" : null,
"L" : null,
"Li" : null,
"L2" : null,
"F" : null,
"Fi" : null,
"F2" : null,
"R" : null,
"Ri" : null,
"R2" : null,
"B" : null,
"Bi" : null,
"B2" : null,
"D" : null,
"Di" : null,
"D2" : null,
};
random = random !== false;
var origin = random
? (new Cube()).randomize().asString()
: (new Cube()).asString(); //pas de random, cube résolu
//on applique le mouvement et on fix l'output
Object.keys(moves).forEach(function (m) {
//on crée un nouveau cube comme il faut
var str = Cube
.fromString(origin)
.move(translateRotation(m))
.asString();
moves[m] = convertColors(fixCubeString(str));
});
callback(convertColors(fixCubeString(origin)), moves);
};
/**
* buildSamples
*
* @param {int} n nombre de samples à générer
* @param {Function} callback
*/
var buildSamples = function (n, callback) {
var samples = {};
for (var i=0; i < n; i++) {
build(i > 0, function (origin, moves) { //le premier sur un cube résolu
samples[origin] = moves;
});
}
callback(samples)
};
//on demande 60 rubik's cube aléatoires (18 mouvements chacun)
buildSamples(60, function(result) {
//on save le résultat dans sample.json
var content = JSON.stringify(result);
//le nom du fichier
var file = argv.length > 2 && argv[2] == '-o' ? argv[3] : DEFAULT_FILE;
fs.writeFile(path.join(__dirname, file), content, function (err) {
if (err) {
return console.error(err);
}
console.log("Jeu sample sauvé dans", file);
});
});
}());
Subproject commit 89bd0dd9dc06883bfaa9d8472ece1c58aaf88944
This diff is collapsed.