Java & Groovy & Scala & Kotlin - 31.Test

Overview

測試分類

測試過程通常有很多種分類方法,一般常說的有那么幾種: UT,IT(CT),ST,MT。

UT 單元測試屬于白盒測試,是測試中的最小單元,一般用于測試單獨的方法,檢測方法的正確性。

IT 集成測試(又可稱為 CT 結合測試),屬于灰盒測試,在單元測試之后進行,一般用于測試各個接口之間的數(shù)據(jù)傳遞功能,由數(shù)個單元測試的對象組成。

ST 系統(tǒng)測試,屬于黑盒測試,在集成測試之后進行,用于測試系統(tǒng)與原定于的需求的契合度。

MT 隨機測試,屬于黑盒測試,測試要求隨機,可以無需知道任何業(yè)務邏輯,主要用于測試系統(tǒng)的穩(wěn)定性。

測試相關技術

TDD

TDD 為測試驅動開發(fā)。步驟一般是先按照預期的調用方式編寫調用測試用例,然后做最小修改使測試用例可以跑通,然后繼續(xù)寫下一個測試用例,所有測試用例跑通后一個可用的類便編寫完成。

TDD 的最大優(yōu)點就是編寫的代碼是可測試的,容易調用的,解決了空想造成的代碼耦合度高,不可測試,很難調用的問題。有不少人提倡所有開發(fā)都應該使用 TDD 技術,但是 TDD 本身會加大工作量,有時會為了不必要的解耦增加系統(tǒng)的復雜性。

BDD

BDD 為行為驅動開發(fā)。其最大的特色是編寫測試用例之前先使用自然語言來編寫該段代碼的目的,本質上是對原來的測試驅動開發(fā)做了擴展。由于使用了自然語言來進行描述,因此非程序員也能進行一定程度的閱讀,而不用等程序員來進行翻譯。

Java

概述

Java 中 Unit Test 主要有這么三個概念:Test Runner,Test Suit,Test Case。

  • Test Runner 主要用于運行測試用例。
  • Test Suit 用于收集 Test Case,是 Test Case 的集合。
  • Test Case 即測試用例,默認的 Test Case 訪問權限為 public 且方法名開頭為 test。

實現(xiàn) UnitTest

第一種方式是繼承 TestCase ,該類有兩個特殊方法 setUp()tearDown(),前者在每個測試用例執(zhí)行前執(zhí)行,后者在每個測試用例執(zhí)行完執(zhí)行。所有以 test 開頭的方法都會被認為是測試用例。

例:

public class Test01 extends TestCase {

    private Calculator calculator;

    @Override
    public void setUp() throws Exception {
        super.setUp();
        calculator = new Calculator();
    }

    @Override
    public void tearDown() throws Exception {
        super.tearDown();
        System.out.println("End test...");
    }

    public void testSum() {
        assert 3 == calculator.sum(1, 2);
        assert 3 != calculator.sum(2, 5);
    }

    public void testMock() {
        Calculator calculator = Mockito.mock(Calculator.class);
        Mockito.when(calculator.sum(1, 2)).thenReturn(10);
        assert 10 == calculator.sum(1, 2);
        assert 0 == calculator.sum(1, 20);
    }

    public void testStory() {
        Animal elephant = new Animal("Elephant");
        Animal giraffa = new Animal("Giraffa");
        Refrigerator refrigerator = new Refrigerator();

        refrigerator.putInto(giraffa);
        assert !refrigerator.isEmpty();

        refrigerator.takeOut();
        refrigerator.putInto(elephant);
        assert !refrigerator.isEmpty();

        Animal animal = refrigerator.takeOut();
        assert refrigerator.isEmpty();
        assertEquals(animal, elephant);
    }

    private class Calculator {
        public int sum(int x, int y) {
            return x + y;
        }
    }

    private class Refrigerator {
        private Animal animal;

        public boolean putInto(Animal animal) {
            if (isEmpty()) {
                this.animal = animal;
                return true;
            }
            return false;
        }

        public Animal takeOut() {
            if (isEmpty()) return null;
            Animal temp = animal;
            animal = null;
            return temp;
        }

        public boolean isEmpty() {
            return animal == null;
        }
    }

    private class Animal {
        public String type;

        public Animal(String type) {
            this.type = type;
        }
    }
}

第二種方式是使用注解,適合那些無法繼承 TestCase 的類。標為 @Test 的方法會被收集為測試用例,標為 @Before 的方法會在每個測試用例執(zhí)行前執(zhí)行,標為 @After 的方法會在每個測試用例后執(zhí)行。

例:

public class Test02 {

    private Calculator calculator;

    @Before
    public void setUp() {
        calculator = new Calculator();
    }

    @After
    public void tearDown() {
        System.out.println("End test...");
    }

    @Test
    public void testSum() {
        assert 3 == calculator.sum(1, 2);
        assert 3 != calculator.sum(2, 5);
    }

    @Test
    public void testMock() {
        Calculator calculator = Mockito.mock(Calculator.class);
        Mockito.when(calculator.sum(1, 2)).thenReturn(10);
        assert 10 == calculator.sum(1, 2);
        assert 0 == calculator.sum(1, 20);
    }

    private class Calculator {
        public int sum(int x, int y) {
            return x + y;
        }
    }
}

Groovy

實現(xiàn) UnitTest

繼承 GroovyTestCase

例:

class Test01 extends GroovyTestCase {

    private Calculator calculator

    @Override
    protected void setUp() throws Exception {
        super.setUp()
        calculator = new Calculator()
    }

    @Override
    protected void tearDown() throws Exception {
        super.tearDown()
        println("End test...")
    }

    def testSum() {
        assert 3 == calculator.sum(1, 2)
        assert 3 != calculator.sum(2, 5)
    }

    def testMock() {
        Calculator calculator = Mockito.mock(Calculator.class)
        Mockito.when(calculator.sum(1, 2)).thenReturn(10)
        assert 10 == calculator.sum(1, 2)
        assert 0 == calculator.sum(1, 20)
    }

    def testStory() {
        Animal elephant = new Animal(type: "Elephant")
        Animal giraffa = new Animal(type: "Giraffa")
        Refrigerator refrigerator = new Refrigerator()

        refrigerator.putInto(giraffa)
        assert !refrigerator.isEmpty()

        refrigerator.takeOut()
        refrigerator.putInto(elephant)
        assert !refrigerator.isEmpty()

        Animal animal = refrigerator.takeOut()
        assert refrigerator.isEmpty()
        assertEquals(animal, elephant)
    }

    class Calculator {
        def sum(x, y) {
            x + y
        }
    }

    class Refrigerator {
        private Animal animal

        def putInto(Animal animal) {
            if (isEmpty()) {
                this.animal = animal
                true
            }
            false
        }

        def Animal takeOut() {
            if (isEmpty()) return null
            def temp = animal
            animal = null
            temp
        }

        def isEmpty() {
            animal == null
        }
    }

    class Animal {
        public String type
    }
}

Groovy 也能使用注解實現(xiàn)測試用例,但是使用注解時必須使用靜態(tài)聲明,沒有繼承方便。

Scala

實現(xiàn) UnitTest

例:

class Test01 extends TestCase {
  private var calculator: Calculator = null

  override def setUp() {
    calculator = new Calculator()
  }

  override def tearDown() {
    println("End test...")
  }

  def testSum() {
    assert(3 == calculator.sum(1, 2))
    assert(3 != calculator.sum(2, 5))
  }

  def testMock() {
    val calculator: Calculator = Mockito.mock(classOf[Calculator])
    Mockito.when(calculator.sum(1, 2)).thenReturn(10)
    assert(10 == calculator.sum(1, 2))
    assert(0 == calculator.sum(1, 20))
  }

  def testStory() {
    val elephant: Animal = new Animal("Elephant")
    val giraffa: Animal = new Animal("Giraffa")
    val refrigerator: Refrigerator = new Refrigerator
    refrigerator.putInto(giraffa)
    assert(!refrigerator.isEmpty)
    refrigerator.takeOut
    refrigerator.putInto(elephant)
    assert(!refrigerator.isEmpty)
    val animal: Animal = refrigerator.takeOut
    assert(refrigerator.isEmpty)
    assert(animal == elephant)
  }
}

class Calculator {
  def sum(x: Int, y: Int): Int = x + y
}

class Refrigerator {
  private var animal: Animal = null

  def putInto(animal: Animal): Boolean = {
    if (isEmpty) {
      this.animal = animal
      true
    } else {
      false
    }
  }

  def takeOut: Animal = {
    if (isEmpty) {
      null
    } else {
      val temp = animal
      animal = null
      temp
    }
  }

  def isEmpty: Boolean = {
    animal == null
  }
}

class Animal(val `type`: String)

實現(xiàn) ScalaTest

Scala Test 是 Scala 的一個測試框架,比起直接使用 JUnit 能夠寫出更優(yōu)美的代碼。

例:

@RunWith(classOf[JUnitRunner])
class ScalaTest01 extends FunSuite with BeforeAndAfter {

  private var calculator: Calculator = null

  before {
    calculator = new Calculator
  }

  after {
    println("End test...")
  }

  test("Sum") {
    assert(3 == calculator.sum(1, 2))
    assert(3 != calculator.sum(2, 5))
  }

  test("Story") {
    val elephant: Animal = new Animal("Elephant")
    val giraffa: Animal = new Animal("Giraffa")
    val refrigerator: Refrigerator = new Refrigerator
    refrigerator.putInto(giraffa)
    assert(!refrigerator.isEmpty)
    refrigerator.takeOut
    refrigerator.putInto(elephant)
    assert(!refrigerator.isEmpty)
    val animal: Animal = refrigerator.takeOut
    assert(refrigerator.isEmpty)
    assert(animal == elephant)
  }
}

實現(xiàn) Spec2

Spec2 是另一個 Scala 的測試框架,使用它可以簡單的完成 BDD 的測試用例的編寫。

例:

class SpecTest extends Specification with BeforeAfterEach with Mockito {

    private var calculator: Calculator = null
    override protected def after: Any = {
        println("End test...")
    }
    override protected def before: Any = {
        calculator = new Calculator
    }

    "Arithmetic" should {
        "sum" in {
            "two numbers" in {
                calculator.sum(1, 2) mustEqual 3
                calculator.sum(2, 5) mustNotEqual 3
            }
        }
    }

    "TestMock" should {
        "mock sum method" in {
            "two numbers" in {
                val calculator = mock[Calculator]
                calculator.sum(1, 2) returns 10
                calculator.sum(1, 2) mustEqual 10
                calculator.sum(1, 20) mustEqual 0
            }
        }
    }

    "Story" should {
        val elephant: Animal = new Animal("Elephant")
        val giraffa: Animal = new Animal("Giraffa")
        val refrigerator: Refrigerator = new Refrigerator

        """
           1. put giraffa
           2. takeout giraffa
           3. put elephant
           4. takeout elephant
           the refrigerator should be empty""" in {
            refrigerator.putInto(giraffa)
            refrigerator.isEmpty mustEqual false

            refrigerator.takeOut
            refrigerator.isEmpty mustEqual true

            refrigerator.putInto(elephant)
            val animal: Animal = refrigerator.takeOut
            refrigerator.isEmpty mustEqual true
            animal mustEqual elephant
        }

    }
}

Kotlin

實現(xiàn) UnitTest

第一種方式,繼承 TestCase

例:

class Test01 : TestCase() {

    private var calculator: Calculator? = null

    override fun setUp() {
        super.setUp()
        calculator = Calculator()
    }

    override fun tearDown() {
        super.tearDown()
        println("End test...")
    }

    fun testSum() {
        assert(3 == calculator?.sum(1, 2))
        assert(3 != calculator?.sum(2, 5))
    }

    fun testMock() {
        val calculator = Mockito.mock<Calculator>(Calculator::class.java)
        Mockito.`when`<Int>(calculator.sum(1, 2)).thenReturn(10)
        assert(10 == calculator.sum(1, 2))
        assert(0 == calculator.sum(1, 20))
    }

    fun testStory() {
        val elephant = Animal("Elephant")
        val giraffa = Animal("Giraffa")
        val refrigerator: Refrigerator = Refrigerator()

        refrigerator.putInto(giraffa)
        assert(!refrigerator.isEmpty())

        refrigerator.takeOut()
        refrigerator.putInto(elephant)
        assert(!refrigerator.isEmpty())

        val animal = refrigerator.takeOut()
        assert(refrigerator.isEmpty())
        assertEquals(animal, elephant)
    }

}

//  Declared open when using mock
open class Calculator {
    open fun sum(x: Int, y: Int): Int {
        return x + y
    }
}

class Refrigerator {
    private var animal: Animal? = null

    fun putInto(animal: Animal): Boolean {
        if (isEmpty()) {
            this.animal = animal
            return true
        }
        return false
    }

    fun takeOut(): Animal? {
        if (isEmpty()) return null
        val temp = animal
        animal = null
        return temp
    }

    fun isEmpty(): Boolean {
        return animal == null
    }
}

class Animal(type: String)

第二種方式,使用注解

例:

class Test02 {

    private var calculator: Calculator? = null

    @Before
    fun setUp() {
        calculator = Calculator()
    }

    @After
    fun tearDown() {
        println("End test...")
    }

    @Test
    fun testSum() {
        assert(3 == calculator?.sum(1, 2))
        assert(3 != calculator?.sum(2, 5))
    }

    @Test
    fun testMock() {
        val calculator = Mockito.mock<Calculator>(Calculator::class.java)
        Mockito.`when`<Int>(calculator.sum(1, 2)).thenReturn(10)
        assert(10 == calculator.sum(1, 2))
        assert(0 == calculator.sum(1, 20))
    }

    @Test
    fun testStory() {
        val elephant = Animal("Elephant")
        val giraffa = Animal("Giraffa")
        val refrigerator: Refrigerator = Refrigerator()

        refrigerator.putInto(giraffa)
        assert(!refrigerator.isEmpty())

        refrigerator.takeOut()
        refrigerator.putInto(elephant)
        assert(!refrigerator.isEmpty())

        val animal = refrigerator.takeOut()
        assert(refrigerator.isEmpty())
        assertEquals(animal, elephant)
    }

}

其它測試框架

Kotlin 還有一種測試框架名為 Spek,同 Scala 的 Specs2 一樣,也可以用于進行 BDD 測試,不過目前其還只是 0.1.x 版本,還沒有達到可以放心用在生成環(huán)境的時候,這里就不介紹了,有興趣可以訪問 官網(wǎng) 了解詳情。

Summary

  • JUnit 是所有測試的核心框架。
  • 一般編寫測試用例可以使用繼承和注解兩種方式,繼承的代碼量更少,注解更加靈活。
  • Scala 除了可以直接使用 JUnit,也可以使用 scalaTest 和 Specs2 進行測試。

文章源碼見 https://github.com/SidneyXu/JGSK 倉庫的 _31_test 小節(jié)

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容