單元測試描述了獨立於它們所屬系統的各個代碼單元的測試過程。構成單位的內容因係統而異,從單個方法到一組密切相關的類或模塊。
在必要時使用測試雙精度將單元與其依賴關係隔離並設置為已知狀態。然後針對預期行為測試其對刺激(方法調用,事件,模擬數據)的反應行為。
整個系統的單元測試可以使用自定義編寫的測試工具來完成,但是已經編寫了許多測試框架來幫助簡化流程並處理大量的管道,重複和平凡的任務。這允許開發人員專注於他們想要測試的內容。
當項目有足夠的單元測試時,通過最終驗證一切都像以前一樣工作,可以輕鬆完成添加新功能或執行代碼重構的任何修改。
代碼覆蓋率 ,通常表示為百分比,是用於顯示單元測試涵蓋系統中代碼量的典型度量標準;請注意,關於這應該有多高,並沒有嚴格的規則,但人們普遍認為越高越好。
測試驅動開發(TDD)是一個原則,規定開發人員應該通過編寫失敗的單元測試來開始編碼,然後才能編寫使測試通過的生產代碼。在練習TDD時,可以說測試本身是所創建代碼的第一個消費者;因此,它們有助於審計和驅動代碼的設計,使其盡可能簡單易用。
單元測試是一個沒有版本號的概念。
using Xunit;
public class SimpleCalculatorTests
{
[Theory]
[InlineData(0, 0, 0, true)]
[InlineData(1, 1, 2, true)]
[InlineData(1, 1, 3, false)]
public void Add_PassMultipleParameters_VerifyExpected(
int inputX, int inputY, int expected, bool isExpectedCorrect)
{
// Arrange
var sut = new SimpleCalculator();
// Act
var actual = sut.Add(inputX, inputY);
// Assert
if (isExpectedCorrect)
{
Assert.Equal(expected, actual);
}
else
{
Assert.NotEqual(expected, actual);
}
}
}
public class SimpleCalculator
{
public int Add(int x, int y)
{
return x + y;
}
}
import unittest
def addition(*args):
""" add two or more summands and return the sum """
if len(args) < 2:
raise ValueError, 'at least two summands are needed'
for ii in args:
if not isinstance(ii, (int, long, float, complex )):
raise TypeError
# use build in function to do the job
return sum(args)
現在測試部分:
class Test_SystemUnderTest(unittest.TestCase):
def test_addition(self):
"""test addition function"""
# use only one summand - raise an error
with self.assertRaisesRegexp(ValueError, 'at least two summands'):
addition(1)
# use None - raise an error
with self.assertRaises(TypeError):
addition(1, None)
# use ints and floats
self.assertEqual(addition(1, 1.), 2)
# use complex numbers
self.assertEqual(addition(1, 1., 1+2j), 3+2j)
if __name__ == '__main__':
unittest.main()
最簡單的是,單元測試包括三個階段:
這三個階段通常被稱為'Arrange-Act-Assert',或'Given-When-Then'。
下面是使用NUnit框架的C#中的示例。
[TestFixture]
public CalculatorTest
{
[Test]
public void Add_PassSevenAndThree_ExpectTen()
{
// Arrange - setup environment
var systemUnderTest = new Calculator();
// Act - Call system under test
var calculatedSum = systemUnderTest.Add(7, 3);
// Assert - Validate expected result
Assert.AreEqual(10, calculatedSum);
}
}
必要時,可選的第四個清理階段進行整理。
經典單元測試測試狀態 ,但是通過狀態正確測試行為依賴於其他類的方法是不可能的。我們通過交互測試來測試這些方法,這些測試驗證被測系統正確調用其協作者。由於合作者有自己的單元測試,這就足夠了,實際上是對測試方法的實際責任的更好測試。我們不測試此方法在給定輸入的情況下返回特定結果,而是測試其正確調用其協作者。
// Test that squareOfDouble invokes square() with the doubled value
let systemUnderTest = new Calculator() // Arrange - setup environment
let square = spy()
systemUnderTest.setSquare(square) // inject a spy
let actual = systemUnderTest.squareOfDouble(3) // Act - Call system under test
assert(square.calledWith(6)) // Assert - Validate expected interaction
好的單元測試是獨立的,但代碼通常具有依賴性。我們使用各種測試雙精度來刪除測試的依賴性。最簡單的測試雙打之一是存根。這是一個帶有硬編碼返回值的函數,代替現實世界的依賴。
// Test that oneDayFromNow returns a value 24*60*60 seconds later than current time
let systemUnderTest = new FortuneTeller() // Arrange - setup environment
systemUnderTest.setNow(() => {return 10000}) // inject a stub which will
// return 10000 as the result
let actual = systemUnderTest.oneDayFromNow() // Act - Call system under test
assert.equals(actual, 10000 + 24 * 60 * 60) // Assert - Validate expected result
在生產代碼中, oneDayFromNow
會調用Date.now(),但這會導致不一致和不可靠的測試。所以在這裡我們將其刪除。
JUnit是用於測試Java代碼的領先測試框架。
被測試的課程模擬了一個簡單的銀行賬戶,當您透支時收取罰款。
public class BankAccount {
private int balance;
public BankAccount(int i){
balance = i;
}
public BankAccount(){
balance = 0;
}
public int getBalance(){
return balance;
}
public void deposit(int i){
balance += i;
}
public void withdraw(int i){
balance -= i;
if (balance < 0){
balance -= 10; // penalty if overdrawn
}
}
}
此測試類驗證某些BankAccount
公共方法的行為。
import org.junit.Test;
import static org.junit.Assert.*;
// Class that tests
public class BankAccountTest{
BankAccount acc;
@Before // This will run **before** EACH @Test
public void setUptestDepositUpdatesBalance(){
acc = new BankAccount(100);
}
@After // This Will run **after** EACH @Test
public void tearDown(){
// clean up code
}
@Test
public void testDeposit(){
// no need to instantiate a new BankAccount(), @Before does it for us
acc.deposit(100);
assertEquals(acc.getBalance(),200);
}
@Test
public void testWithdrawUpdatesBalance(){
acc.withdraw(30);
assertEquals(acc.getBalance(),70); // pass
}
@Test
public void testWithdrawAppliesPenaltyWhenOverdrawn(){
acc.withdraw(120);
assertEquals(acc.getBalance(),-30);
}
}
using NUnit.Framework;
namespace MyModuleTests
{
[TestFixture]
public class MyClassTests
{
[TestCase(1, "Hello", true)]
[TestCase(2, "bye", false)]
public void MyMethod_WhenCalledWithParameters_ReturnsExpected(int param1, string param2, bool expected)
{
//Arrange
var foo = new MyClass(param1);
//Act
var result = foo.MyMethod(param2);
//Assert
Assert.AreEqual(expected, result);
}
}
}