2016-08-24 3 views
11

나는 예를 들어, 농축을 입력 부착 될 것스칼라 테스트

나는이

object Algorithm{ 
    def apply(rand: Random) = { 
    import MyImplicits._ 
    rand.nextInt.complexCalculation + 1 
    } 
} 

같은 코드에 사용하지만 지금은 분리 및 단위 테스트 알고리즘 방법

object MyImplicits{ 
    implicit class RichInt(i: Int){ 
    def complexCalculation: Int = i * 200 
    } 
} 

? 특히,이 같은 complexCalculation의 실현하는 것이, 무언가를 조롱하고 싶습니다 :

class MyAlgorithmTest extends FreeSpec with MockitoSugar{ 
    import org.mockito.Mockito.when 

    "MyApgorithm" { 
    "Delegates complex calculation" in { 
     val mockRandom = mock[Random] 
     when(mockRandom.nextInt()).thenReturn(1) 

     // This wouldn't work, but is the kind of thing I'm looking for 
     //when(1.complexCalculation).thenReturn(2) 
     val expected = 1 * 2 + 1 

     val result = MyAlgorithm(mockRandom) 
     assert(result === expected) 
    } 
    } 
} 
+0

귀하의 문제는 무엇입니까? 테스트하는 동안 임의의 int 객체가 RichInt 객체로 변환되는지 확인하려고합니까? 아니면 모르는 무작위 값에 대한 어설 션을 사용하는 방법을 모르는 것입니까? – Samar

+1

@Pengin은'complexCalculation' 메소드를 조롱하길 원하지만'RichInt' 오브젝트의 생성이 암묵적으로 이루어지기 때문에'RichInt' 오브젝트에'when' 절을 지정할 방법이 없습니다. – virsox

답변

2

Implicits를 사용하면 구성이 가능하며, 구성 할 때 테스트를 위해 구현을 대체 할 수 있으므로 일반적으로 mock이 필요하지 않습니다. 즉, 나는이 경우에 함축 된 팬이 아니며, 그들이 가져다주는 가치를 보지 못한다. (내 다른 의견에 암시로) 나는 오래된 학교 조성 그것을 해결 것 :

당신이 구성을 할 implicits를 사용하여 주장하는 경우
trait Calculation { 
    def calculation(i: Int): Int 
} 

trait ComplexCalculation extends Calculation { 
    def calculation(i: Int): Int = i * 200 
} 

trait MyAlgorithm { 
    self: Calculation => 

    def apply(rand: Random) = { 
    calculation(rand.nextInt) + 1 
    } 
} 

// somewehre in test package 

trait MockCalculation extends Calculation { 
    def calculation(i: Int): Int = i * 200 
} 

//test instance 
object MyAlgorithm extends MyAlgorithm with MockCalculation 

,이 작업을 수행 할 수 있습니다

trait Computation { 
    def compute(i: Int): Int 
} 

object prod { 
    implicit val comp = new Computation { 
    def compute(i: Int): Int = i * 200 
    } 
} 

object test { 
    implicit val comp = new Computation { 
    def compute(i: Int): Int = i + 2 
    } 
} 

object Algorithm { 
    def apply(rand: Random)(implicit comp: Computation) = { 
    comp.compute(i) + 1 
    } 
} 

// application site 
import prod._ 

Algorithm(scala.util.Random) // will run * 200 computation 

//test 

import test._ 

Algorithm(scala.util.Random) // will run + 2 computation 

이 원 ' 하지만 당신에게 계산을위한 도트 구문을주지 마라. 이 방법은 행동을 정의하는 매우 미묘한 방법이기 때문에 가져 오는 가져 오기와 실수를하기 쉽습니다.

+0

유형 풍부화의 이점을 의심하기 시작했습니다. 당신은 그것을위한 많은 유틸리티를 볼 수 있습니까, 아니면 상당히 간단한 작업을위한 설탕으로 유용할까요? – Pengin

+0

"유형 농축"은 공식 명칭이 "type class"- https://en.wikipedia.org/wiki/Type_class입니다. 이는 새로운 행동으로 제어하지 않는 클래스를 확장 할 수있는 'ad-hoc 다형성 (ad-hoc polymorphism)'을 가능하게하는 강력한 메커니즘입니다. 또한 행동을 정의하는 매우 일반적인 방법을 제공합니다. 스칼라 컬렉션 구현시주의하십시오 - 많은 경우에 형식 클래스가 동작을 제공하는 데 사용된다는 암시를주는 암시 적 증거 매개 변수 (일반적으로 'ev'라고 함)가 있습니다. 여기도보십시오 : http://stackoverflow.com/questions/5408861/what-are-type-classes-in-scala-useful-for – Tim

+0

및 여기 http://danielwestheide.com/blog/2013/02/06/ the-neophytes-guide-to-scala-part-12-type-classes.html – Tim

0

다음은 scalatest API를 사용합니다. 모의 테스트가 잘 실행되고 있으며 암시 적 클래스 변환이 제대로 수행되고 있습니다.

// Implicit.scala in src/main/scala 

package implicittesting 
import scala.util.Random 

object MyImplicits{ 
    implicit class RichInt(i: Int){ 
    def complexCalculation: Int = 200*i // make this complex :) 
    } 
} 

object Algorithm{ 
    var current = 1 
    def apply(rand: Random) = { 
    import MyImplicits._ 
    current = rand.nextInt 
    current.complexCalculation + 100 
    } 
} 


// ImplicitSuite.scala in src/main/test 

package implicittesting 

import org.scalatest.FunSuite 


import org.junit.runner.RunWith 
import org.scalatest.junit.JUnitRunner 

@RunWith(classOf[JUnitRunner]) 
class DeleteSuite extends FunSuite { 
    import MyImplicits._ 
    test("algorithm implicit class conversion test") { 
    assert(Algorithm(scala.util.Random) == Algorithm.current.complexCalculation + 200) 
    println(Algorithm.current) 
    } 
} 
+0

나는 var에 대해 걱정할 것이지만, 어쨌든, 나는 '복잡한 계산'논리를 조롱하고 싶었다. – Pengin

0

이것은 내가 생각해 낸 최고입니다. 나는 그것을 사기꾼이라고 인정하는 것을 기꺼이한다.

import org.scalatest.FreeSpec 
import org.scalatest.mockito.MockitoSugar 
import scala.util.Random 

trait MyImplicits { 
    implicit class RichInt(i: Int){ 
    def complexCalculation: Int = complexCalculationImpl(i) 
    } 

    def complexCalculationImpl(i: Int) = i * 200 
} 

trait MyAlgorithm extends MyImplicits { 
    def apply(rand: Random) = { 
    rand.nextInt.complexCalculation + 1 
    } 
} 

//Implementation for use 
object MyAlgorithm extends MyAlgorithm 

class MyAlgorithmTest extends FreeSpec with MockitoSugar{ 
    import org.mockito.Mockito.when 

    "MyApgorithm should" - { 
    "Delegate the complex calculation" in { 
     val mockRandom = mock[Random] 
     when(mockRandom.nextInt()).thenReturn(1) 

     val instance = new MyAlgorithm { 
     override def complexCalculationImpl(i: Int) = i * 2 
     } 

     val expected = 3 // Note we don't expect 201 

     assert(instance(mockRandom) === expected) 
    } 
    } 
} 
+0

이 경우 implicits를 사용해야 할 충분한 이유가 있습니까? 모듈에서 복잡한 계산을 성공적으로 분리 했으므로 다른 특성 구현을 테스트에서 혼합하지 않는 이유는 무엇입니까? – Tim

1

RichInt.scala

trait RichInt { 
    def complexCalculation: Int 
} 

class RichIntImpl(i: Int) extends RichInt { 
    def complexCalculation = i * 200 
} 

Algorithm.scala

import scala.util.Random 

class Algorithm(enrich: Int => RichInt) { 
    implicit val _enrich = enrich 
    def apply(rand: Random) = { 
    rand.nextInt.complexCalculation + 1 
    } 
} 

object Algorithm extends Algorithm(new RichIntImpl(_)) 

AlgorithmTest.scala

import org.scalatest.FreeSpec 
import scala.util.Random 
import org.mockito.Mockito._ 

class AlgorithmTest extends FreeSpec with MockSugar { 

    "MyApgorithm should" - { 
    "Delegate the complex calculation" in { 
     val mockRandom = mock[Random] 
     when(mockRandom.nextInt()) thenReturn 1 

     val algorithm = new Algorithm(
     enrich = mocking[Int => RichInt] { enrich => 
      when(enrich(1)).thenReturnMocking { richInt => 
      when(richInt.complexCalculation).thenReturn(2) 
      } 
     } 
    ) 

     val expected = 3 

     assert(algorithm(mockRandom) === expected) 
    } 
    } 
} 

MockSuger.scala

,
import org.scalatest.mockito.MockitoSugar 
import org.mockito.stubbing.OngoingStubbing 

// More sugars to make our tests look better. 
trait MockSugar extends MockitoSugar { 

    def mocking[T <: AnyRef : Manifest](behavior: T => Unit): T = { 
    val m = mock[T] 
    behavior(m) 
    m 
    } 

    implicit class RichOngoingStubbing[T <: AnyRef : Manifest](stub: OngoingStubbing[T]) { 
    def thenReturnMocking(behavior: T => Unit) = { 
     val m = mock[T] 
     val s = stub.thenReturn(m) 
     behavior(m) 
     s 
    } 
    } 
} 
+0

모든 모의 설탕이 내 뇌를 녹이고 있습니다. 이 접근법이 형질을 사용하는 대답에 대한 나의 시도보다 낫다는 이유를 알 수 있습니까? – Pengin

+0

당신의 솔루션의 주된 문제점은 조롱 라이브러리의 장점을 취하지 않는다는 것입니다. 테스트 케이스에 대한 실제 계산을 모방하는 싼 표현식을 찾을 수 있다면 괜찮은 것 같습니다. 그러나 좀 더 복잡한 시나리오의 경우 혼자서 조롱 유틸리티를 구현해야 할 수도 있습니다. – thirstycrow

+0

나는 그것을 지금 본다. 이것이 이것이 최선의 방법이라면, 그것은 형식 풍부함의 우아함에 의문을 갖게합니다. 시험은 읽기가 매우 어려워집니다. – Pengin