- Scala Design Patterns
- Ivan Nikolov
- 802字
- 2021-07-16 12:57:27
Testing traits
Testing is a really important part of software development. It ensures that changes to a certain piece of code do not end up producing errors either in the methods that were changed, or somewhere else.
There are different testing frameworks that one can use, and it really is a matter of personal preference. In this book, we have used ScalaTest (http://www.scalatest.org), as this is the one I use in my projects; it is understandable, readable, and easy to use.
In some cases, if a trait is mixed into a class, we could end up testing the class. However, we might want to test only a specific trait. It does not make much sense to test a trait that doesn't have all its methods implemented, so here we will look into the ones that have their code written (mixins). Also, the unit tests that we will show here are quite simple but they are just for illustration purposes. We will be looking into more complex and meaningful tests in the following chapters of this book.
Using a class
Let's have a look at how DoubledMultiplierIdentity
, which we saw previously, would be tested. One would try to simply mix the trait into a test class and test the methods:
class DoubledMultiplierIdentityTest extends FlatSpec with ShouldMatchers with DoubledMultiplierIdentity
This, however, won't compile and will lead to the following error:
Error:(5, 79) illegal inheritance; superclass FlatSpec is not a subclass of the superclass MultiplierIdentity of the mixin trait DoubledMultiplierIdentity class DoubledMultiplierIdentityTest extends FlatSpec with ShouldMatchers with DoubledMultiplierIdentity { ^
We already talked about this before and the fact that a trait can only be mixed in a class that has the same super class as itself. This means that in order to test the trait, we should create a dummy class inside our test class and then use it:
package com.ivan.nikolov.linearization import org.scalatest.{ShouldMatchers, FlatSpec} class DoubledMultiplierIdentityTest extends FlatSpec with ShouldMatchers { class DoubledMultiplierIdentityClass extends DoubledMultiplierIdentity val instance = new DoubledMultiplierIdentityClass "identity" should "return 2 * 1" in { instance.identity should equal(2) } }
Mixing the trait in
We can test a trait by mixing it in. There are a few places where we can do this: into a test class or into separate test cases.
Mixing into the test class
Mixing in a trait into a test class is only possible if the trait does not extend any other class explicitly, hence the super class of the trait and the test will be the same. Other than this, everything else is absolutely the same as done previously.
Let's test the A
trait from earlier in this chapter, which says hello
. We've also added an extra pass
method, and now the trait looks like the following:
trait A { def hello(): String = "Hello, I am trait A!" def pass(a: Int): String = s"Trait A said: 'You passed $a.'" }
Here, is how the unit test will look like:
package com.ivan.nikolov.composition import org.scalatest.{ShouldMatchers, FlatSpec} class TraitATest extends FlatSpec with ShouldMatchers with A { "hello" should "greet properly." in { hello() should equal("Hello, I am trait A!") } "pass" should "return the right string with the number." in { pass(10) should equal("Trait A said: 'You passed 10.'") } it should "be correct also for negative values." in { pass(-10) should equal("Trait A said: 'You passed -10.'") } }
Mixing into the test cases
We can also mix traits into the inpidual test cases separately. This could allow us to apply customizations specific to those test cases only. The following is just a different representation of the preceding unit test:
package com.ivan.nikolov.composition import org.scalatest.{ShouldMatchers, FlatSpec} class TraitACaseScopeTest extends FlatSpec with ShouldMatchers { "hello" should "greet properly." in new A { hello() should equal("Hello, I am trait A!") } "pass" should "return the right string with the number." in new A { pass(10) should equal("Trait A said: 'You passed 10.'") } it should "be correct also for negative values." in new A { pass(-10) should equal("Trait A said: 'You passed -10.'") } }
As you can see in the preceding code, the test cases are identical to the previous ones. They, however, inpidually mix A
in. This would allow us to apply different customizations in the cases where a trait requires an implementation of a method or a variable initialization. This way we can also focus specifically on the trait being tested, rather than creating actual instances of it.
Running the tests
After the tests are written, it is useful to run them in order to see whether everything works as expected. Just run the following command from the root of your project and it will execute all the tests:
mvn clean test
If you want, the project can be converted to SBT instead of Maven and then the tests can be triggered using an sbt test
command.