Come tracciare i tratti della classe C ++?

5

Come fan di tipi regolari e semantica dei valori , mi piace che le classi diventino più regolari e non siano polimorfiche. Come fan delle operazioni di non-lancio, sono appassionato di operazioni che sono noexcept . Apprezzo anche la conferma di quali funzioni membro speciali vengono effettivamente generate dal compilatore. E mi piacerebbe tenere traccia di queste cose insieme al codice a cui sono associate.

C ++ offre il <type_traits> file di intestazione che offre classi per il controllo di qualità come queste e I ' mi sono divertito a usarli in codice di test unitario come il seguente:

TEST(MyClass, Traits)
{      
    EXPECT_TRUE(std::is_constructible< MyClass >::value);
    EXPECT_TRUE(std::is_nothrow_constructible< MyClass >::value);
    EXPECT_FALSE(std::is_trivially_constructible< MyClass >::value);

    EXPECT_TRUE(std::is_copy_constructible< MyClass >::value);
    EXPECT_TRUE(std::is_nothrow_copy_constructible< MyClass >::value);
    EXPECT_FALSE(std::is_trivially_copy_constructible< MyClass >::value);

    EXPECT_TRUE(std::is_move_constructible< MyClass >::value);
    EXPECT_TRUE(std::is_nothrow_move_constructible< MyClass >::value);
    EXPECT_FALSE(std::is_trivially_move_constructible< MyClass >::value);

    EXPECT_TRUE(std::is_copy_assignable< MyClass >::value);
    EXPECT_TRUE(std::is_nothrow_copy_assignable< MyClass >::value);
    EXPECT_FALSE(std::is_trivially_copy_assignable< MyClass >::value);

    EXPECT_TRUE(std::is_move_assignable< MyClass >::value);
    EXPECT_TRUE(std::is_nothrow_move_assignable< MyClass >::value);
    EXPECT_FALSE(std::is_trivially_move_assignable< MyClass >::value);

    EXPECT_TRUE(std::is_destructible< MyClass >::value);
    EXPECT_TRUE(std::is_nothrow_destructible< MyClass >::value);
    EXPECT_FALSE(std::is_trivially_destructible< MyClass >::value);

    EXPECT_TRUE(!std::is_polymorphic< MyClass >::value);
}

Come codice di test unitario che sta entrando anche nel mio sistema di revisione del codice sorgente, questo ha il vantaggio di tenere traccia di queste qualità insieme al codice impegnato per MyClass . Mi piace anche ricevere un gamification kick di vedere le classi assumere più di queste qualità - cosa che faccio usando un codice come questo.

Mi chiedo tuttavia: ci sono altri modi per ottenere ciò che forse hanno più benefici senza tanti trabocchetti?

Ecco alcune preoccupazioni che ho su questa tecnica:

  1. Sono accettabili come test unitari? Sono per me ma lo sono per gli altri? Mentre la definizione di test unitari che Wikipedia fornisce sembra abbastanza soggettiva da consentire a questo di qualificarsi, sono preoccupato qui quanto ampiamente altri d'accordo.
  2. Sarebbe meglio rintracciare queste qualità altrove come nel codice della classe stessa usando per esempio static_assert ? Questo è diverso dall'ultima preoccupazione in ora concentrandosi sul fatto che il loro sia un posto migliore (non se altri sviluppatori di software lo accetterebbero come test unitario). Tracciare queste qualità nel codice della classe tramite static_assert anziché in codice test unitario come mostrato, sembra un'elevazione di queste qualità. Per le classi più performanti, questa direzione ha un senso. Riconosco anche una prospettiva che questi dovrebbero invece essere ridotti in importanza forse per essere solo avvisi o messaggi (invece di fallimenti di test o fallimenti di build).
  3. Mancanza di scalabilità. È più un codice standard di quello che deve essere? È un'operazione di copia e incolla semplice per creare test come questo ma non si presta agli aggiornamenti globali. Preferirei astrarre più questo, se possibile, quindi c'era un posto in cui poteva essere aggiornato. Ho preso in considerazione la conversione di questo in una funzione modello che combina questi controlli e ottiene solo il tipo come parametro del modello. Come farebbe a tenere traccia dei valori veri e falsi? Potrebbe generare un report di output, ma sembra complicato analizzare in EXPECT_* contesti con numeri di linea locali. Nel frattempo, una funzione macro verrà valutata nel contesto del test unitario, ma una funzione macro a più istruzioni viene comunque visualizzata come tutto su un'unica riga di codice sorgente: la linea da cui viene chiamata la macro. Ho anche considerato di combinare le forme normali, nothrow e trivially di questi controlli, in controlli a linea singola che prendono un parametro per indicare quale di questi dovrebbe applicarsi in positivo. Tuttavia, non ho visto nulla di più soddisfacente del modello mostrato.
posta Louis Langholtz 24.12.2018 - 07:55
fonte

2 risposte

3
  1. Sono accettabili come test unitari?

No. Questi non sono test unitari accettabili. I test unitari dovrebbero controllare comportamento piuttosto che implementazione . Il test unitario che hai non sta controllando alcun comportamento di MyClass .

Supponiamo che io sia un altro sviluppatore che non si preoccupa veramente dei tuoi normali tipi nella tua suite di software, e io per qualche ragione voglio rimuovere il costruttore di copie. Il mio codice viene compilato ma all'improvviso questo test dell'unità fallisce. Cosa faccio ora? Probabilmente rimuovi il test unitario e vai avanti. E se scrivo una nuova classe, non prenderei nemmeno in considerazione un secondo per aggiungere questi test.

  1. Sarebbe meglio tracciare queste qualità altrove come nel codice della classe stessa usando static_assert per esempio?

Di sicuro, il static_assert è una soluzione migliore di un test di unità (se è persino possibile implementarlo). Ma continuo a pensare che sia inutile. Parzialmente per la stessa ragione del test unitario. Un altro sviluppatore del tuo team li aggiungerebbe? Probabilmente finirai col copiare tutte le tue affermazioni ogni volta, o creando una brutta macro generandole.

  1. Mancanza di scalabilità. È più un codice standard di quello che deve essere?

Hai identificato il problema più serio. E supponiamo di voler cambiare qualcosa nei tuoi standard di codice, quindi devi aggiornare non solo tutte le tue classi, ma anche tutti i test o asserire che hai copiato. Aspetta, ho menzionato gli standard del codice? Sì, penso che questo dovrebbe far parte degli standard del tuo codice. In genere un team definisce gli standard del codice su come codificare. Questo non è solo uno stile di codice (su cui ovviamente devi essere d'accordo), ma anche cose come obbedire alla regola dello zero, o non usare variabili membro protette, o fare in modo che le tue classi si comportino come tipi regolari. Successivamente, si dispone di un sistema di revisione in cui altri sviluppatori verificano la conformità con questi standard di codice. Sì, si può passare attraverso, ma con il test e asserisce senza revisione non avete alcuna garanzia neanche.

    
risposta data 25.12.2018 - 08:44
fonte
2

Se questi tratti fanno parte dell'interfaccia del tipo, dovrebbero idealmente essere fatti valere vicino alla dichiarazione di quel tipo. Un static_assert è il modo ideale per farlo, dato che la compilazione fallirà (piuttosto che solo i tuoi test).

In generale, i test sono utili per verificare alcune proprietà del programma con l'esempio. In genere non possono mostrare che un programma è privo di errori, ma può essere utile per trovare bug se sono presenti. Al contrario, i sistemi di tipo forniscono una prova automatica delle proprietà di correttezza. Poiché il sistema di tipi è una prova (piuttosto che un semplice esempio di scenario), è generalmente preferibile utilizzare controlli di livello del sistema di tipo, laddove possibile. Qui i tratti del tipo sono proprietà a livello di sistema di tipo, non proprietà di runtime. Controllarli come parte di una suite di test di unità non è sbagliato, ma trovo che sia fonte di confusione.

    
risposta data 24.12.2018 - 15:18
fonte

Leggi altre domande sui tag