2011-12-04 11 views
7

Ho scritto un modulo di grandi dimensioni in F # che ha un'interfaccia banale. Il modulo contiene circa 1000 righe di codice, 50 test di unità ed esporta solo una funzione facilmente comprensibile.Come faccio a far funzionare NUnit test F # non esportati da un modulo

La cosa naturale da fare è scrivere un file FSI minuscolo. Questo ha numerosi vantaggi, tra cui la prevenzione dell'inquinamento dello spazio dei nomi, fornendo un posto ovvio per la documentazione, assicurandosi che se qualcuno decidesse di riutilizzare gli interni, essi avranno un incentivo a ripulirli in modo pulito, e senza dubbio molti altri. Sono certo che sto predicando al coro qui, ma sento comunque che valga la pena spiegare perché ritengo sia utile avere il file fsi.

Ora il problema. NUnit non eseguirà più i test unitari, recalcitrantemente affermando di non essere pubblici. Bene, sarebbe perché non sono in alcun modo una parte dell'interfaccia. Non voglio particolarmente aggiungerli all'interfaccia nonostante, visto che significherebbe aggiornarlo ogni volta che aggiungo un altro test, e anche che gonfierà il file fsi di un ordine di grandezza.

Suppongo che una soluzione banale sia spostare il codice da qualche altra parte, importarlo in un piccolo file .fs e inoltrare semplicemente una funzione. Con un po 'di fortuna tutti saranno d'accordo che è semplicemente disgustoso. C'è un modo migliore per favore?

Modifica: molte grazie a tutti coloro che hanno risposto. Ho svalutato entrambe le risposte. Mi sarebbe piaciuto dividere la taglia, tuttavia, dato che non sembra possibile, accetterò (un po 'arbitrariamente) la risposta di Tomas.

+1

Hai provato a usare InternalsVisibleTo? http://devlicio.us/blogs/derik_whittaker/archive/2007/04/09/internalsvisibleto-testing-internal-methods-in-net-2-0.aspx –

+0

Penso che il mio problema sia un po 'diverso. Posso già compilare i test bene. Tuttavia, non posso eseguirli se non li esporto dal modulo. Suppongo che l'attributo sarebbe di aiuto se potessi arrivare a dire che tutti gli interni sono accessibili a NUnit. Ma non ho idea di dove cominciare, e nessuno dei due - sembra - fa Google. Ovviamente potrei usare l'attributo dopo aver spostato i test su un file diverso, ma quello è un male peggiore. Li uso come documentazione eseguibile, quindi hanno davvero bisogno di essere vicini al codice. Molte grazie per la risposta. – user1002059

+0

Se è compilato, dovrebbe essere eseguito. A meno che tu non abbia i test nello stesso assembly del codice reale o qualcosa del genere. –

risposta

6

Se si aggiunge un file fsi per specificare la visibilità dei moduli e delle funzioni nella propria origine, è necessario includere dichiarazioni di tutte le funzioni che dovrebbero essere accessibili al pubblico. Ciò significa che se NUnit richiede che i test siano funzioni pubbliche, è necessario includerli nel file fsi.

Tuttavia, c'è anche un altro modo per specificare la visibilità in F # - invece di utilizzare il file fsi, è sufficiente aggiungere i modificatori di visibilità appropriati alle dichiarazioni. In questo modo, è possibile nascondere tutti i dettagli di implementazione e l'esportazione solo la funzione principale e test:

namespace MyLibrary 
open NUnit.Framework 

// Implementation details can be in this module 
// (which will not be visible outside of the library) 
module private Internal = 
    let foo n = n * 2 
    let bar n = n + 1 

// A public module can contain the public API (and use internal implementation)  
module public MyModule = 
    open Internal 
    let doWork n = foo (bar n) 

// To make the tests visible to NUnit, these can be placed in a public module 
// (but they can still access all functions from 'Internal') 
module public Tests = 
    open MyModule 

    [<Test>] 
    let ``does work for n = 1``() = 
    Assert.Equals(doWork 1, 4) 

Rispetto utilizzando fsi file, questo ha lo svantaggio che non si dispone di un file separato che ben descrive solo la parti importanti della tua API. Tuttavia, otterrai ciò di cui hai bisogno: nascondi i dettagli di implementazione ed esporti solo una singola funzione e i test.

+0

Molte grazie per la risposta. Non è presumibilmente possibile intercalare i test e il codice quando viene utilizzato questo approccio? – user1002059

+0

@ user1002059 Sarebbe anche possibile, ma dovresti contrassegnare ogni singola funzione usando 'let private foo() = ...' o usando 'let public test_for_foo() = ...' e rendi pubblici tutti i moduli che contengono alcune funzioni pubbliche. Ciò significa più annotazioni, ma è una possibilità. –

2

approccio

Si potrebbe ricorrere all'utilizzo di riflessione per invocare i metodi di prova privati: si sarebbe avere un unico metodo di prova NUnit pubblico che loop su tutti i metodi privati ​​nella dell'assemblea invocando quelli con l'attributo di prova. Il lato negativo di questo approccio è che si può vedere un solo metodo di test in errore alla volta (ma forse si potrebbe esaminare qualcosa di creativo come l'uso di test parametrizzati per risolvere questo problema).

Esempio

Program.fsi

namespace MyNs 

module Program = 
    val visibleMethod: int -> int 

Program.fs

namespace MyNs 

open NUnit.Framework 

module Program = 
    let implMethod1 x y = 
     x + y 

    [<Test>] 
    let testImpleMethod1() = 
     Assert.AreEqual(implMethod1 1 1, 2) 

    let implMethod2 x y z = 
     x + y + z 

    [<Test>] 
    let testImpleMethod2() = 
     Assert.AreEqual(implMethod2 1 1 1, 3) 

    let implMethod3 x y z r = 
     x + y + z + r 

    [<Test>] 
    let testImpleMethod3() = 
     Assert.AreEqual(implMethod3 1 1 1 1, -1) 

    let implMethod4 x y z r s = 
     x + y + z + r + s 

    [<Test>] 
    let testImpleMethod4() = 
     Assert.AreEqual(implMethod4 1 1 1 1 1, 5) 

    let visibleMethod x = 
     implMethod1 x x 
     + implMethod2 x x x 
     + implMethod3 x x x x 

TestProxy.fs (implementazione del nostro "Approach")

module TestProxy 

open NUnit.Framework 

[<Test>] 
let run() = 
    ///we only want static (i.e. let bound functions of a module), 
    ///non-public methods (exclude any public methods, including this method, 
    ///since those will not be skipped by nunit) 
    let bindingFlags = System.Reflection.BindingFlags.Static ||| System.Reflection.BindingFlags.NonPublic 

    ///returns true if the given obj is of type TestAttribute, the attribute used for marking nunit test methods 
    let isTestAttr (attr:obj) = 
     match attr with 
     | :? NUnit.Framework.TestAttribute -> true 
     | _ -> false 

    let assm = System.Reflection.Assembly.GetExecutingAssembly() 
    let tys = assm.GetTypes() 
    let mutable count = 0 
    for ty in tys do 
     let methods = ty.GetMethods(bindingFlags) 
     for mi in methods do 
      let attrs = mi.GetCustomAttributes(false) 
      if attrs |> Array.exists isTestAttr then 
       //using stdout w/ flush instead of printf to ensure messages printed to screen in sequence 
       stdout.Write(sprintf "running test `%s`..." mi.Name) 
       stdout.Flush() 
       mi.Invoke(null,null) |> ignore 
       stdout.WriteLine("passed") 
       count <- count + 1 
    stdout.WriteLine(sprintf "All %i tests passed." count) 

Esempio di output (tramite TestDriven.NET)

Privacy non abbiamo mai arrivare a testImplMethod4 poiché non sulla testImpleMethod3:

running test `testImpleMethod1`...passed 
running test `testImpleMethod2`...passed 
running test `testImpleMethod3`...Test 'TestProxy.run' failed: System.Reflection.TargetInvocationException : Exception has been thrown by the target of an invocation. 
    ----> NUnit.Framework.AssertionException : Expected: 4 
    But was: -1 
    at System.RuntimeMethodHandle._InvokeMethodFast(IRuntimeMethodInfo method, Object target, Object[] arguments, SignatureStruct& sig, MethodAttributes methodAttributes, RuntimeType typeOwner) 
    at System.RuntimeMethodHandle.InvokeMethodFast(IRuntimeMethodInfo method, Object target, Object[] arguments, Signature sig, MethodAttributes methodAttributes, RuntimeType typeOwner) 
    at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture, Boolean skipVisibilityChecks) 
    at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture) 
    at System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters) 
    C:\Users\Stephen\Documents\Visual Studio 2010\Projects\FsOverflow\FsOverflow\TestProxy.fs(29,0): at TestProxy.run() 
    --AssertionException 
    C:\Users\Stephen\Documents\Visual Studio 2010\Projects\FsOverflow\FsOverflow\Program.fs(25,0): at MyNs.Program.testImpleMethod3() 

0 passed, 1 failed, 4 skipped (see 'Task List'), took 0.41 seconds (NUnit 2.5.10). 
+0

Mille grazie. Questa è una soluzione interessante a cui non ho pensato. Tuttavia, mi chiedo se sta andando un po 'troppo in là nella direzione di reimplementare NUnit. Ad esempio, se qualcuno dei test si aspettasse un'eccezione, usasse cose come [] o persino l'installazione e il teardown, ci sarebbe stato un sacco di lavoro da fare? Grazie ancora. – user1002059

+0

Prego :) Inizialmente avevo creato qualcosa di simile a questa soluzione per poter eseguire i miei test xUnit.net/Unquote con Silverlight: http://stackoverflow.com/questions/7053245/how-to-run-f -silverlight-biblioteca-progetto-holding-xUnit-net-contrib-based-unità-t. Posso immaginare che supportare pienamente tutti i tipi di asserzioni che NUnit può sollevare potrebbe mai espandere la complessità qui (si sta effettivamente fornendo una sorta di reimplementation di runner di test NUnit), ma non è stato un problema per me dal momento che Unquote utilizza solo 'NUnit. Framework.AssertionException' e fornisce la propria API di asserzione su questo. –

+0

Un'altra opzione dal momento che NUnit è open source è l'hacking di una build personalizzata per te che rimuove la logica di NUnit per saltare i metodi di test privati ​​(li vede, e potrebbe eseguirli, ma per qualsiasi ragione sciocca sceglie deliberatamente di non supportarli) - o , come ho suggerito nel mio commento sulla tua domanda, passa a xUnit.net poiché non ha scrupoli sull'esecuzione di metodi di test privati, e la conversione dovrebbe essere abbastanza indolore (ovviamente, potresti avere qualche requisito che ti lega a NUnit) . –

Problemi correlati