我在F#中编写了一个大型模块,碰巧有一个简单的界面。该模块包含大约1000行代码,50个单元测试,并只导出一个易于理解的功能。
接下来要做的就是写一个小的fsi文件。这有许多优点,包括防止命名空间污染,为文档提供一个明显的位置,确保如果有人决定重用内部,他们将有动力干净地将它们分解出来,毫无疑问,许多其他人。我相信我在这里向合唱团讲道,但仍然觉得值得解释为什么我认为有fsi文件是有帮助的。
现在问题。 NUnit将不再运行单元测试,谴责声称它们不公开。那么,那将是因为它们不以任何方式成为界面的一部分。我并不特别希望将它们添加到界面中,尽管如此,因为它意味着每次添加另一个测试时都会更新它,并且它会使fsi文件膨胀一个数量级。
我认为一个简单的解决方法是将代码移动到其他地方,将其导入到一个很小的.fs文件中,然后转发一个函数。幸运的是,每个人都会认为这只是令人反感。请问有更好的方法吗?
编辑:非常感谢所有回复的人。我赞同这两个答案。我本来想分开赏金,但是因为这似乎不可能(我有点随意)接受Tomas的回答。
答案 0 :(得分:6)
如果您要添加fsi
文件以指定源中模块和函数的可见性,那么您需要包含应该可公开访问的所有函数的声明。这意味着如果NUnit要求测试是公共函数,则需要将它们包含在fsi
文件中。
但是,还有另一种在F#中指定可见性的方法 - 您可以在声明中添加适当的可见性修饰符,而不是使用fsi
文件。这样,您可以隐藏所有实现细节并仅导出主函数和测试:
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)
与使用fsi
文件相比,这样做的缺点是您没有单独的文件,只能很好地描述API的重要部分。但是,您将获得所需内容 - 隐藏实现细节并仅显示单个函数和测试。
答案 1 :(得分:2)
您可以使用反射来调用您的私有测试方法:您将拥有一个公共NUnit测试方法,该方法循环调用程序集中的所有私有方法,并调用具有Test属性的方法。这种方法的一个重要缺点是,你一次只能看到一种失败的测试方法(但也许你可以研究一些创造性的东西,比如使用参数化测试来解决这个问题)。
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(我们的“方法”的实现)
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)
请注意,我们从未进入testImplMethod4,因为它在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).