2017-04-15 4 views
2

J'ai écrit mini-dames qui représente la carte dans deux formats: long positionID et byte[][] board. L'ancien est moins cher à stocker, ce dernier est plus facile à représenter/manipuler. La conversion elle-même est simple (voir le code ci-dessous).Développement piloté par les tests. Comment écrire un test unitaire pour cette conversion avant que les méthodes ne soient créées?

Le TDD indique «écrire un test défaillant, puis écrire le code de production». Comment cela devrait-il être fait avec la conversion de la représentation? Test unitaire comme
assertEquals(0L, toIndex(new byte[6][6])) ne fournit pas beaucoup de couverture. Test pour Long myPosID = 42L; assertEquals(myPosID, toIndex(toBoard(myPosID)) n'ajoute pas beaucoup de valeur. Tester toute la gamme prendra une éternité. Exécuter un test unitaire pour plusieurs valeurs aléatoires myPosID (simulation de Monte-Carlo) semble mieux, mais même en passant le test ne signifie pas grand-chose.

Comment devrait-il être fait dans TDD?

/* 
    This class manipulates checkers board representation. Position is stored as long and represented as byte[height][width] board. 
    For board representation white = 0, black = 1, empty = 2. 

    Long positionID to byte[][] board: 
    get ternary numeral system representation of positionID, place positional values to corresponding squares. 

    For conversion from byte[][] board to long positionID: 
    long positionID = 0; for each (byte playableSquare : board){playable square positionID = positionID*3. positionID+= playableSquare} 
    */ 

    public final int boardHeight = 6; 
    public final int boardWidth = 6; 



    public long toIndex(byte[][] board) { 
     byte coords[] = new byte[boardHeight * boardWidth/2]; 
     int totalSquares = boardHeight * boardWidth/2; 
     byte k = 0; 
     for (int i = 0; i < boardHeight; i++) { 
      for (int j = 0; j < boardWidth/2; j++) { 
       byte makeItCheckers = (byte) ((i + 1) % 2); 
       coords[k] = board[i][j * 2 + makeItCheckers]; 
       k++; 
      } 
     } 
     long positionID = 0; 
     for (int i = totalSquares - 1; i >= 0; i--) { 
      positionID = positionID * 3 + coords[i]; 
     } 
     return positionID; 
    } 



    public byte[][] toBoard(long positionID) { 
     int totalSquares = boardHeight * boardWidth/2; 

     int[] coords = new int[totalSquares]; 

     for (int i = 0; i < totalSquares; i++) { 

      coords[i] = (int) (positionID % 3L); 
      positionID = positionID/3L; 
     } 
     byte[][] board = new byte[boardHeight][boardWidth]; 
     Arrays.fill(board, 2); 
     byte k = 0; 
     for (int i = 0; i < boardHeight; i++) { 
      for (int j = 0; j < boardWidth/2; j++) { 
       byte makeItCheckers = (byte) ((i + 1) % 2); 
       board[i][j * 2 + makeItCheckers] = (byte) coords[k]; 
       k++; 
      } 
     } 
     return board; 
    } 

Répondre

2

TDD écrit des tests avant d'écrire l'implémentation.
Vous semblez faire le contraire. Pour écrire des TDD et plus généralement des tests unitaires pour votre traitement de conversion, vous devez penser en termes de tests d'acceptation.
Vous devez identifier les scénarios possibles du traitement de conversion.
Ce que vous avez en entrée et ce que vous attendez en sortie.


gamme d'essai prendra toujours

En effet, si vous avez des centaines, voire des milliers de scénarios, vous ne devriez pas tester tous car ceux-ci deviendront de temps pour mettre en œuvre et en outre tests unitaires peuvent devenirtrop long pour exécuter.
Il est contraire aux principes de test unitaire.
Les tests unitaires doivent être exécutés rapidement car ils sont exécutés très souvent.

Exécution des tests unitaires pour plusieurs valeurs de myPosID aléatoire (simulation de Monte-Carlo ) semble mieux, mais même alors essai passage ne signifie pas beaucoup.

test avec des valeurs aléatoires comme vous le suggérez ne devrait pas utiliser des séries aléatoires qui sont générés différemment à chaque fois que les tests sont exécutés, car ceux-ci peuvent ne pas être reproductible.
Il est également contraire aux principes de test unitaire.
Un test unitaire doit produire le même résultat dans n'importe quel environnement et à n'importe quel moment.
Sinon, cela signifie que le test n'est pas fiable.


Ainsi, l'idée de créer des tests unitaires est en train d'écrire TDD façon est autant de tests unitaires comme type de cas à traiter.

Par exemple: vous avez 3 façons de représenter une cellule:

blanc = 0, noir = 1, vide = 2.

Ceux-ci peuvent être la création de 3 tests d'acceptation pour la conversion de Long à byte[][] et inversement.

1) Lorsque je en tant que valeur Long, seules les cellules vides, je suis en attente d'une représentation de tableau d'octets comme ...

2) Lorsque je en tant que valeur Long, 1 cellule blanche et vide les cellules pour le reste, j'attends une représentation de tableau d'octets comme ...

3) Quand j'ai une valeur Long, 1 cellule noire et des valeurs vides pour le reste, j'attends une représentation de tableau d'octets comme ...

Vous pouvez alors aller plus loin. Pourquoi ne pas créer un test d'acceptation qui mélange des cellules blanches et noires pour vérifier que leur mélange ne crée pas d'effets secondaires.

4) Quand j'ai une valeur Long, 3 globules blancs, 4 cellules noires et les cellules vides pour le reste, je suis en attente d'une représentation de tableau d'octets comme ...

Enfin, A propos de votre question de savoir si vous devriez tester tous les cas, je pense que vous devriez plutôt essayer de vous concentrer sur les «grandes affaires» comme celles montrées ci-dessus.
Ça devrait aller.

+0

Oui, dans TDD, vous devez d'abord écrire un test défaillant, puis écrire du code de production qui réussit un test. J'ai écrit mon code en premier. Maintenant, je pense, à quoi ressemblerait un TDD. Ils n'auraient pas de fonctions de conversion ou de tests au début. Ils écriraient un test. A quoi ressemblerait le test? – Stepan

+0

Lorsque vous effectuez un TDD, cela ne signifie pas que vous sachiez à l'avance tous les traitements dont vous aurez besoin. Pour votre application, les exigences métier seront d'abord codées dans TDD. Normal: vous commencez à partir des besoins métier initiaux et vous commencez à écrire des tests puis à les implémenter. À la fois, pendant le développement, un besoin plus fin ou une exigence technique vient (ici convertir les données d'un format à un autre) et vous devez l'obtenir pour compléter les besoins d'affaires initiaux. Donc, vous les spécifiez dans TDD. – davidxxx

+0

Permet d'appliquer cette logique à ma question. J'ai besoin d'un TEST et, plus tard, d'une méthode pour passer de la représentation décimale de LongIDID à un système de numération ternaire à "pour chaque valeur d'assignation carrée de la position correspondante dans la représentation numérique ternaire de" positionID ". Comment écrire ce test?!. Il ne couvre rien (vérifiez 0L, 1L, 2L et dites "nous passons! Hourra!" - nous avons simulé un test et trompé les collèges) ou il fonctionne pour toujours. Une méthode en elle-même est triviale. Mais si le test est si difficile à écrire, quel est le point de TDD? – Stepan

1

Il existe un problème similaire dans la programmation de la concurrence: lorsque vous soumettez un code, le système ne peut pas vérifier l'exactitude à 100% du code, car il n'a pas réussi toutes les entrées possibles. Au lieu de cela, le système fonctionne de nombreux tests, qui se divise en trois catégories:

  • cas d'angle: entrée vide, par exemple
  • certains cas généraux
  • cas extrêmement grand pour tester perfomance

Vous On peut aussi suivre cette technique, mais à l'échelle, ça vous va.

Aussi, je devrais mentionner, que "TDD canonique" ne fonctionne pas pour les méthodes de type formule, puisque vous pouvez toujours passer le test avec un autre if. Au lieu de cela, nous devrions nous concentrer sur les faits, que les tests nous donnent non seulement la bonne implémentation de l'algorithme, mais aussi la bonne conception.

1

La vérification des formules avec TDD est difficile. Vous pouvez utiliser une variante de Monte-Carlo. Générer 1000 (ou 100 000) nombres aléatoires Long testID. Sauvez-les quelque part. Vérifiez toujours la conversion en arrière et en avant avec cette liste. Les ID seront aléatoires, mais ne changeront pas d'un test à l'autre. De cette façon, vous suivez "les tests doivent produire les mêmes résultats".

TDD semble bien fonctionner lorsque l'entreprise compte beaucoup d'employés bon marché mais médiocres.Le gestionnaire peut alors appliquer des tests d'écriture (il est facile de vérifier si les tests manquent) et il est plus difficile pour les autres codeurs de soumettre un patch qui casse le code existant (votre commit ne passe pas le test JUnit - allez et recommencez !!). Les tests ralentiront les travailleurs, mais cela n'a pas d'importance, tant que les tests ne ralentissent pas le manager. Il suffit d'embaucher plus de codeurs.

Ceci fonctionne particulièrement bien lorsque le projet part de zéro.

Si les codeurs sont décents, leur travail est coûteux et un projet est maturé, alors vous êtes mieux avec des tests axés sur le comportement.