0

I have a case class defined as below

case class Person(name:String = null)

Below is the test case for this case class.

import org.scalatest.FunSuite
class PersonTest extends FunSuite{

  test("Person test"){
    val personName=Person("Bill")
    assert(personName.name == s"Bill")
}

The test case is passing, however in sonarqube code coverage only 1 out of 20 cases are covered or partially covered. How do I know which are the 19 remaining conditions which are not covered with this case class?

enter image description here

Dileep Dominic
  • 499
  • 11
  • 23
  • Unrelated: don't use `null` values! Use `Option`s to represent possibility of no value. – Gaël J Oct 29 '22 at 06:23
  • Can you show the output of Sonarqube maybe? Is that the only code you have in the whole project? – Gaël J Oct 29 '22 at 06:24
  • 1
    I assume you are doing this to comply with some internal code coverage requirement, but in general testing that a `case class` works as expected is probably effort not particularly well spent. – stefanobaghino Oct 29 '22 at 06:57
  • @GaëlJ, I have attached the screenshot for a similar case class coverage from sonarqube. There are other codes also in the project. – Dileep Dominic Oct 29 '22 at 14:56
  • Your screenshot doesn't show much but look's like there are more than the code you've shown: is all the code tested in the same manner? Doesn't Sonarqube gives you more details about the conditions? – Gaël J Oct 29 '22 at 15:11
  • @GaëlJ , No, I have not seen any option in sonarqube which which show which all conditions are not covered. Please help if there is a way to see those. – Dileep Dominic Oct 29 '22 at 17:20

1 Answers1

1

From the jacoco tag, it looks like you're using Jacoco to generate coverage data for sonarqube to report.

Jacoco doesn't understand Scala, so it just sees your case class as a JVM class. A Scala case class has a lot of functionality that's synthesized by the compiler: to jacoco, that functionality counts for test coverage.

Most notably, the compiler synthesizes implementations for

  • equals
  • toString
  • hashCode
  • copy

These implementations are battle-tested (and tested in CI by the Scala compiler team, a large portion of which is employed by my employer), so there's probably not much value in you testing them. Accordingly, it's generally not recommended to use jacoco with Scala codebases, at least if you're using lots of case classes and/or have a coverage requirement that would force you to test the synthetic functionality (something like scoverage will be aware of what a case class is).

If for whatever reason you absolutely have to get the coverage number for your case classes up, a test like this should help:

// test equals method
val bill = Person("Bill")
val ted = Person("Ted")
assert(bill == bill)
assert(bill != ted)
assert(bill != new AnyRef)

// test hashCode
assert(bill.hashCode == Person("Bill").hashCode)
// might be very unlucky if "Bill" and "Ted" hash the same, in which case this adventure is kinda bogus
assert(bill.hashCode != ted.hashCode)

// alternatively (only works since this is a single-field case class
assert((bill.name.hashCode == ted.name.hashCode) || (bill.hashCode != ted.hashCode))

// test toString
assert(bill.toString.startsWith("Person("))

// test copy (repeat this for all permutations of fields)
assert(bill.copy().name == bill.name)
assert(bill.copy(name = "Rufus").name == "Rufus")

You might have to look at the generated bytecode to find other methods you can test; likewise, the synthetic methods should be able to be decompiled into Java so you can get an idea of what the branches are. It's possible that things like modular shifts in hashCode get interpreted as conditions, in which case you may need to experiment with the magic values required to satisfy the coverage metric.

As mentioned above, this effort to satisfy the broken coverage metric is basically wasted. I would recommend including a comment in these tests like:

// included solely to satisfy code coverage: these methods are synthesized by the compiler

(you can make them as passive-aggressive as you like :) )

For branch coverage (which is what I think is meant by condition?), it is possible that some branches cannot be triggered in idiomatic Scala.

Levi Ramsey
  • 18,884
  • 1
  • 16
  • 30