1. Basic Unit Test Structure
Java (JUnit 4):
import org.junit.Test;
import org.junit.Before;
import org.junit.After;
import static org.junit.Assert.*;
public class CalculatorTest {
private Calculator calculator;
@Before // Runs before each test
public void setUp() {
calculator = new Calculator();
}
@After // Runs after each test
public void tearDown() {
calculator = null;
}
@Test
public void testAddition() {
int result = calculator.add(5, 3);
assertEquals(8, result);
}
@Test
public void testSubtraction() {
int result = calculator.subtract(10, 3);
assertEquals(7, result);
}
}
Python (unittest):
import unittest
class Calculator:
def add(self, a, b):
return a + b
def subtract(self, a, b):
return a - b
class CalculatorTest(unittest.TestCase):
def setUp(self): # Runs before each test
self.calculator = Calculator()
def tearDown(self): # Runs after each test
self.calculator = None
def test_addition(self):
result = self.calculator.add(5, 3)
self.assertEqual(result, 8)
def test_subtraction(self):
result = self.calculator.subtract(10, 3)
self.assertEqual(result, 7)
# Run tests
if __name__ == "__main__":
unittest.main()
Python (pytest - Modern, Recommended):
import pytest
class Calculator:
def add(self, a, b):
return a + b
def subtract(self, a, b):
return a - b
class TestCalculator:
@pytest.fixture(autouse=True)
def setup(self):
self.calculator = Calculator()
yield
self.calculator = None
def test_addition(self):
result = self.calculator.add(5, 3)
assert result == 8
def test_subtraction(self):
result = self.calculator.subtract(10, 3)
assert result == 7
# Run: pytest test_calculator.py
Key Differences:
- Java uses
@Test,@Before,@Afterannotations. - Python's unittest uses methods like
setUp(),tearDown(). - pytest is more concise with simpler assertion syntax.
- pytest is generally preferred for Python projects.
2. Assertions Comparison
-
Equal
- Java (JUnit):
assertEquals(expected, actual) - Python (unittest):
self.assertEqual(actual, expected) - Python (pytest):
assert actual == expected
- Java (JUnit):
-
Not Equal
- Java (JUnit):
assertNotEquals(a, b) - Python (unittest):
self.assertNotEqual(a, b) - Python (pytest):
assert a != b
- Java (JUnit):
-
True
- Java (JUnit):
assertTrue(condition) - Python (unittest):
self.assertTrue(condition) - Python (pytest):
assert condition
- Java (JUnit):
-
False
- Java (JUnit):
assertFalse(condition) - Python (unittest):
self.assertFalse(condition) - Python (pytest):
assert not condition
- Java (JUnit):
-
Null
- Java (JUnit):
assertNull(obj) - Python (unittest):
self.assertIsNone(obj) - Python (pytest):
assert obj is None
- Java (JUnit):
-
Not Null
- Java (JUnit):
assertNotNull(obj) - Python (unittest):
self.assertIsNotNone(obj) - Python (pytest):
assert obj is not None
- Java (JUnit):
-
Contains
- Java (JUnit):
assertTrue(list.contains(item)) - Python (unittest):
self.assertIn(item, list) - Python (pytest):
assert item in list
- Java (JUnit):
-
Exception
- Java (JUnit):
@Test(expected=Exception.class) - Python (unittest):
self.assertRaises(Exception, ...) - Python (pytest):
with pytest.raises(Exception):
- Java (JUnit):
3. Testing Exceptions
Java:
@Test(expected = IllegalArgumentException.class)
public void testInvalidInput() {
calculator.divide(10, 0);
}
// Or using ExpectedException rule
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void testDivisionByZero() {
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage("Cannot divide by zero");
calculator.divide(10, 0);
}
Python (unittest):
def test_division_by_zero(self):
with self.assertRaises(ValueError):
calculator.divide(10, 0)
def test_exception_message(self):
with self.assertRaises(ValueError) as context:
calculator.divide(10, 0)
self.assertIn("Cannot divide by zero", str(context.exception))
Python (pytest):
def test_division_by_zero():
with pytest.raises(ValueError):
calculator.divide(10, 0)
def test_exception_message():
with pytest.raises(ValueError, match="Cannot divide by zero"):
calculator.divide(10, 0)
4. Parametrized Tests
Java (JUnit 4):
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
public class CalculatorParameterizedTest {
private int a;
private int b;
private int expected;
public CalculatorParameterizedTest(int a, int b, int expected) {
this.a = a;
this.b = b;
this.expected = expected;
}
@Parameters
public static Collection<Object[]> data() {
return Arrays.asList(new Object[][] {
{ 5, 3, 8 },
{ 10, 20, 30 },
{ -5, 5, 0 }
});
}
@Test
public void testAddition() {
Calculator calc = new Calculator();
assertEquals(expected, calc.add(a, b));
}
}
Python (pytest):
import pytest
class TestCalculator:
@pytest.mark.parametrize("a,b,expected", [
(5, 3, 8),
(10, 20, 30),
(-5, 5, 0)
])
def test_addition(self, a, b, expected):
calculator = Calculator()
assert calculator.add(a, b) == expected
Python (unittest):
import unittest
from parameterized import parameterized
class TestCalculator(unittest.TestCase):
@parameterized.expand([
(5, 3, 8),
(10, 20, 30),
(-5, 5, 0)
])
def test_addition(self, a, b, expected):
calculator = Calculator()
self.assertEqual(calculator.add(a, b), expected)
5. Mocking Basics
Java (Mockito):
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import static org.mockito.Mockito.*;
public class OrderServiceTest {
@Mock
private PaymentService paymentService;
private OrderService orderService;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
orderService = new OrderService(paymentService);
}
@Test
public void testOrderCreation() {
// Setup mock
when(paymentService.charge(100.0)).thenReturn(true);
// Execute
boolean result = orderService.createOrder(100.0);
// Verify
assertTrue(result);
verify(paymentService).charge(100.0);
}
}
Python (unittest.mock):
from unittest.mock import Mock, patch, MagicMock
class PaymentService:
def charge(self, amount):
pass
class OrderService:
def __init__(self, payment_service):
self.payment_service = payment_service
def create_order(self, amount):
return self.payment_service.charge(amount)
class TestOrderService(unittest.TestCase):
def setUp(self):
self.payment_service = Mock()
self.order_service = OrderService(self.payment_service)
def test_order_creation(self):
# Setup mock
self.payment_service.charge.return_value = True
# Execute
result = self.order_service.create_order(100.0)
# Verify
self.assertTrue(result)
self.payment_service.charge.assert_called_once_with(100.0)
Python (pytest with unittest.mock):
from unittest.mock import Mock, patch
def test_order_creation():
# Setup mock
payment_service = Mock()
payment_service.charge.return_value = True
order_service = OrderService(payment_service)
# Execute
result = order_service.create_order(100.0)
# Verify
assert result is True
payment_service.charge.assert_called_once_with(100.0)
6. Mock Verification
Java (Mockito):
@Test
public void testMockVerification() {
UserService userService = mock(UserService.class);
// Setup
when(userService.getUser("alice")).thenReturn(new User("alice", "Alice"));
// Execute
User user = userService.getUser("alice");
// Verify method was called
verify(userService).getUser("alice");
// Verify called exactly once
verify(userService, times(1)).getUser("alice");
// Verify never called
verify(userService, never()).getUser("bob");
// Verify called at least once
verify(userService, atLeastOnce()).getUser("alice");
}
Python (unittest.mock):
def test_mock_verification():
user_service = Mock()
# Setup
user_service.get_user.return_value = {"name": "alice"}
# Execute
user = user_service.get_user("alice")
# Verify method was called
user_service.get_user.assert_called_with("alice")
# Verify called exactly once
user_service.get_user.assert_called_once_with("alice")
# Verify called N times
assert user_service.get_user.call_count == 1
# Verify never called
user_service.get_other_user.assert_not_called()
7. Patching and Spying
Java (Mockito with Spy):
@Test
public void testSpying() {
List<String> realList = new ArrayList<>();
List<String> spyList = spy(realList);
// Real behavior
spyList.add("item1");
assertEquals(1, spyList.size());
// Mock specific method
when(spyList.get(0)).thenReturn("mocked");
assertEquals("mocked", spyList.get(0));
// Verify was called
verify(spyList).add("item1");
}
Python (unittest.mock.patch):
from unittest.mock import patch, MagicMock
# Patch a module function
@patch("module.external_api")
def test_with_patch(mock_api):
mock_api.return_value = "mocked result"
result = my_function()
assert result == "mocked result"
mock_api.assert_called_once()
# Or using context manager
def test_with_patch_context():
with patch("module.external_api") as mock_api:
mock_api.return_value = "mocked result"
result = my_function()
assert result == "mocked result"
# Patch class/object attribute
def test_patch_attribute():
with patch.object(MyClass, "method", return_value="mocked"):
obj = MyClass()
result = obj.method()
assert result == "mocked"
8. Fixtures and Test Setup
Java (JUnit 4):
public class UserServiceTest {
@Rule
public final ExpectedException exception = ExpectedException.none();
private UserService userService;
private UserRepository userRepository;
@Before
public void setUp() {
userRepository = mock(UserRepository.class);
userService = new UserService(userRepository);
}
@Test
public void testGetUser() {
User user = new User("alice", "Alice");
when(userRepository.findById(1)).thenReturn(user);
User result = userService.getUser(1);
assertEquals("alice", result.getUsername());
}
}
Python (pytest fixtures):
import pytest
from unittest.mock import Mock
class UserService:
def __init__(self, user_repository):
self.user_repository = user_repository
def get_user(self, user_id):
return self.user_repository.find_by_id(user_id)
@pytest.fixture
def user_repository():
return Mock()
@pytest.fixture
def user_service(user_repository):
return UserService(user_repository)
def test_get_user(user_service, user_repository):
user = {"id": 1, "username": "alice"}
user_repository.find_by_id.return_value = user
result = user_service.get_user(1)
assert result["username"] == "alice"
user_repository.find_by_id.assert_called_once_with(1)
9. Practical Example: Service Testing
Java:
public class PaymentService {
private PaymentGateway gateway;
private TransactionLogger logger;
public PaymentService(PaymentGateway gateway, TransactionLogger logger) {
this.gateway = gateway;
this.logger = logger;
}
public boolean processPayment(double amount, String cardToken) {
if (amount <= 0) {
throw new IllegalArgumentException("Amount must be positive");
}
try {
boolean success = gateway.charge(cardToken, amount);
logger.log("Payment processed: " + amount + " - " + (success ? "SUCCESS" : "FAILED"));
return success;
} catch (Exception e) {
logger.log("Payment error: " + e.getMessage());
return false;
}
}
}
public class PaymentServiceTest {
@Mock
private PaymentGateway gateway;
@Mock
private TransactionLogger logger;
private PaymentService paymentService;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
paymentService = new PaymentService(gateway, logger);
}
@Test
public void testSuccessfulPayment() {
when(gateway.charge("token123", 100.0)).thenReturn(true);
boolean result = paymentService.processPayment(100.0, "token123");
assertTrue(result);
verify(gateway).charge("token123", 100.0);
verify(logger).log(contains("SUCCESS"));
}
@Test
public void testFailedPayment() {
when(gateway.charge("token123", 100.0)).thenReturn(false);
boolean result = paymentService.processPayment(100.0, "token123");
assertFalse(result);
verify(logger).log(contains("FAILED"));
}
@Test(expected = IllegalArgumentException.class)
public void testNegativeAmount() {
paymentService.processPayment(-50.0, "token123");
}
}
Python:
from unittest.mock import Mock, patch, call
class PaymentService:
def __init__(self, gateway, logger):
self.gateway = gateway
self.logger = logger
def process_payment(self, amount, card_token):
if amount <= 0:
raise ValueError("Amount must be positive")
try:
success = self.gateway.charge(card_token, amount)
status = "SUCCESS" if success else "FAILED"
self.logger.log(f"Payment processed: {amount} - {status}")
return success
except Exception as e:
self.logger.log(f"Payment error: {str(e)}")
return False
class TestPaymentService:
def setup_method(self):
self.gateway = Mock()
self.logger = Mock()
self.payment_service = PaymentService(self.gateway, self.logger)
def test_successful_payment(self):
self.gateway.charge.return_value = True
result = self.payment_service.process_payment(100.0, "token123")
assert result is True
self.gateway.charge.assert_called_once_with("token123", 100.0)
self.logger.log.assert_called_once()
assert "SUCCESS" in self.logger.log.call_args[0][0]
def test_failed_payment(self):
self.gateway.charge.return_value = False
result = self.payment_service.process_payment(100.0, "token123")
assert result is False
assert "FAILED" in self.logger.log.call_args[0][0]
def test_negative_amount(self):
with pytest.raises(ValueError):
self.payment_service.process_payment(-50.0, "token123")
Python (pytest version):
import pytest
from unittest.mock import Mock
def test_successful_payment():
gateway = Mock()
logger = Mock()
gateway.charge.return_value = True
payment_service = PaymentService(gateway, logger)
result = payment_service.process_payment(100.0, "token123")
assert result is True
gateway.charge.assert_called_once_with("token123", 100.0)
assert "SUCCESS" in logger.log.call_args[0][0]
def test_failed_payment():
gateway = Mock()
logger = Mock()
gateway.charge.return_value = False
payment_service = PaymentService(gateway, logger)
result = payment_service.process_payment(100.0, "token123")
assert result is False
assert "FAILED" in logger.log.call_args[0][0]
def test_negative_amount():
gateway = Mock()
logger = Mock()
payment_service = PaymentService(gateway, logger)
with pytest.raises(ValueError):
payment_service.process_payment(-50.0, "token123")
10. Test Coverage
Java (JaCoCo):
<!-- In pom.xml -->
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.8</version>
</plugin>
<!-- Run: mvn clean jacoco:report -->
Python (coverage.py):
# Install
pip install coverage
# Run tests with coverage
coverage run -m pytest
# Generate report
coverage report
coverage html # Creates htmlcov/index.html
Summary Table
-
Test Framework
- Java (JUnit + Mockito): JUnit
- Python (unittest): unittest
- Python (pytest): pytest
-
Basic Test
- Java (JUnit + Mockito):
@Testannotation - Python (unittest):
test_method - Python (pytest):
test_function
- Java (JUnit + Mockito):
-
Setup
- Java (JUnit + Mockito):
@Before - Python (unittest):
setUp()method - Python (pytest):
@pytest.fixture
- Java (JUnit + Mockito):
-
Teardown
- Java (JUnit + Mockito):
@After - Python (unittest):
tearDown()method - Python (pytest): fixture cleanup
- Java (JUnit + Mockito):
-
Assertions
- Java (JUnit + Mockito):
assertEquals(), etc. - Python (unittest):
self.assertEqual(), etc. - Python (pytest):
assertstatement
- Java (JUnit + Mockito):
-
Exception Testing
- Java (JUnit + Mockito):
@Test(expected=...) - Python (unittest):
assertRaises() - Python (pytest):
pytest.raises()
- Java (JUnit + Mockito):
-
Mocking
- Java (JUnit + Mockito):
mock()/spy() - Python (unittest):
Mock() - Python (pytest):
Mock()from unittest.mock
- Java (JUnit + Mockito):
-
Mock Setup
- Java (JUnit + Mockito):
when(...).thenReturn(...) - Python (unittest):
.return_value = - Python (pytest):
.return_value =
- Java (JUnit + Mockito):
-
Verification
- Java (JUnit + Mockito):
verify() - Python (unittest):
assert_called_with() - Python (pytest):
assert_called_with()
- Java (JUnit + Mockito):
-
Parametrization
- Java (JUnit + Mockito):
@Parameterized - Python (unittest):
parameterizedlibrary - Python (pytest):
@pytest.mark.parametrize
- Java (JUnit + Mockito):
-
Coverage
- Java (JUnit + Mockito): JaCoCo
- Python (unittest): coverage.py
- Python (pytest): coverage.py