0

Code is in Scala, but the question is relevant to other lanugages also.

Let say we have some class:

class SomeClass(inputString: String) {
  private val name: String = readName()
  private val method: Method = readMethod()

  def sayHello(): String = { 
    if (method == USE_XXX) {
      s"Hello, my name is $name"
    } else "I don't know"
  }

  private def readName(): String = {
    val nameRegex ="""(?mi)^NAME\s*:\s*(.*)$""".r
    val name = for (m <- nameRegex.findFirstMatchIn(inputString)) yield m.group(1)
    name.getOrElse(throw new CustomException("No name in input"))
  }

  private def readMethod(): Method = {
    val methodRegex = """(?mi)^METHOD\s*:\s*(.*)$""".r
    val method = for (m <- methodRegex.findFirstMatchIn(inputString)) yield m.group(1)
    method.getOrElse("") match {
      case "XXX" => USE_XXX
      case _ => UNKNOWN
    }
  }

  sealed trait Method
  case object USE_XXX extends Method
  case object UNKOWN extends Method
}

As you can see it's public interface is only the method sayHello(). However, when the object of that class is instantiated readName can throw an exception as the name field is required.

The question is, how to unit test this class. I would like to write unit tests that will test both private functions against input specific for the given function (isolation), e.g.:

tests for readName() with texts:
  1) "NAME: ok"
  2) "NAME: first occurance\nNAME: second occurance"
  3) "name: keyword lowercase"
  4) "NAME :  whitespaces around semicolon"
  5) AssertThrows[CustomException](SomeClass("NONAME"))

test for readMethod() with texts:
  1) "NO METHOD KEYWORD" -> should have be UNKOWN
  2) "METHOD: XXX"
  3) "method: xxx"
etc.

The problem is that input texts for testing readMethod() should also contain the NAME: keyword to prevent throwing an exception. If there are also another fields and validation rules that in each test it must be used input with fulfills all validation rules but the one under the test and this makes test suit hard to maintain.

How to deal with such a problem?

The easiest way is to move the exception throw to sayHello() method but this has a drawback - it may be called after some expensive calculations are performed in the other part of the system, while it should warn the user as early as possible ("Hello dear user, data you provided is wrong, the name is missing. Do you really want to continue?"- or something similar).

Koikos
  • 162
  • 9
  • @JoelBerkeley I've read that and I lean toward the idea of testing only public. However, in this case, this approach leads to unreadable tests as input text grows with every required field added to the class. In case it's a configuration class it may many, many possible properties, each that has to be validated. So maybe validation should be delegated to some other class? This, however, increases class number while the validation rules apply to the only this given one. – Koikos Aug 05 '18 at 14:53
  • The sites @JoelBerkeley mentioned are: - https://stackoverflow.com/questions/21369093/how-can-a-private-class-method-be-tested-in-scala. - https://stackoverflow.com/questions/34571/how-do-i-test-a-private-function-or-a-class-that-has-private-methods-fields-or?noredirect=1&lq=1 – Koikos Aug 05 '18 at 15:17

1 Answers1

0

The external interface to this class is the constructor (new SomeClass(string)) and the sayHello method. This is the behaviour that should be tested in any Unit Tests, and it is fairly straightforward to do this.

readName and readMethod are internal implementation functions and could be implemented in other ways (e.g (name, method) = readNameAndMethod()). Therefore it does not make sense to test readName or readMethod because they are part of the implementation.

If you want to test readName and readMethod as separate functions them make them public methods on a separate testable object. Then they are a public interface and can have their own Unit Test suite.

Tim
  • 26,753
  • 2
  • 16
  • 29
  • I've totally missed that constructor is also a public interface to that class, while the essential problem is, that the private methods are called during object instantiation! Having the method united in one goes against clean code, where we want functions doing one and the only thing (or I misunderstand it). – Koikos Aug 05 '18 at 15:11
  • The real problem here is doing validation in the constructor, rather than doing it a separate object (Factory model). Turn `SomeClass` into a trait with an abstract `sayHello` method. Then create a class object with an `apply` method that validates the `inputString` and then returns an instance of a private class that inherits from the trait. Or have a `createClass` method that returns `Either[Exception, SomeClass]` so that the caller does the error handling directly. – Tim Aug 05 '18 at 15:28