How to test an object's private methods in Sca

2020-03-31 04:19发布

问题:

I have an example object:

object Foo {
  private def sayFoo = "Foo!"
}

And I want to test the private sayFoo method without the following workarounds: 1. Without defining it as package private 2. without defining it as protected and than inheriting it in the test class

I saw one possible solution which is to extend privateMethodTester but it doesn't seem to work for objects just classes.

Now, I know that some, if not many, say that you are not suppose to test private methods but rather only public ones (api). Nonetheless, I still do want to test them.

thx in advanced

回答1:

It cannot be tested by any standard means.

However, if you need to "cover" that section of code, then test the methods which use that private method in their implementation. It is indirect, granted, but it does effectively test the code.

For example, in your class you could have another method:

def dontSayBar = {
    sayFoo()
    println("Other processing...")
}

and this will "cover" the code in the private method if tested.

P.S. A good rule of thumb is that if you see code that isn't showing up in a coverage run, then you probably don't actually need that code.



回答2:

As other said, there is no way to directly test a private method, especially of an object. However you can cheat by using reflection in order to bypass the restriction.

I'm not sure if there is a cleaner way to do the same using Scala reflection API, however the following code using Java's Reflection does work:

val m = Foo.getClass.getDeclaredMethod("sayFoo")
val inst = Foo.getClass.getField("MODULE$").get()
m.setAccessible(true)
m.invoke(inst)

First you need to get a reference to the sayFoo method. Next you need to obtain the reference to the singleton instance of the Foo object which is saved in the MODULE$ static field.

Once you have both references you just need to tell the JVM to remove the access restriction from the method, making it accessible.

This is clearly an ugly hack which, but if used sparingly and only for testing purposes may be useful.



回答3:

Probably someone has done this already: How about an annotation macro @Testable that emits the object with a method that exposes the private members, but only when compiling for testing (and just the object otherwise).

scala> object X { private def f = 42 ; def g = 2 * f }
defined object X

scala> X.g
res1: Int = 84

scala> object X { private def f = 42 ; def g = 2 * f ; def testable = new { def f = X.this.f } }
defined object X

scala> X.testable.f
warning: there was one feature warning; re-run with -feature for details
res2: Int = 42

scala> object X { private def f = 42 ; def g = 2 * f ; def testable = new Dynamic { def selectDynamic(name: String) = X.this.f } }
defined object X

scala> X.testable.f
warning: there was one feature warning; re-run with -feature for details
res4: Int = 42

Alternatively, it could emit a different object TestableX.