Skip to content

Commit

Permalink
Create exercises for lab08 and update config.yml (#133)
Browse files Browse the repository at this point in the history
* wip on BankAccount testing

* implement the initial tests

* add a death note

* implement a Death Note

* implement the tests for the Death Note

* feat: update testing exercises' solutions

* feat: update lab08 exercises

* untrack the gradle wrapper

* improve the instructions

* recover the wrapper

---------

Co-authored-by: Danilo Pianini <[email protected]>
Co-authored-by: Danilo Pianini <[email protected]>
  • Loading branch information
3 people authored Nov 11, 2023
1 parent ac9a15d commit dc42370
Show file tree
Hide file tree
Showing 15 changed files with 1,004 additions and 69 deletions.
32 changes: 18 additions & 14 deletions config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -204,30 +204,34 @@ packages:
destinations:
- unibo-oop/lab08
contents:
- from: java/testing/junit
to: 81-junit
- from: java/gui/simple-gui
to: 81-simple-gui
to: 82-simple-gui
- from: java/testing/tdd-deathnote
to: 83-tdd-deathnote
oop-lab09:
format:
- repo
destinations:
- unibo-oop/lab09
contents:
- from: java/gui/mvc-gui
to: 82-mvc-gui-reflection
to: 91-mvc-gui-reflection
- from: java/mvc-io/bad-gui-io
to: 83-bad-gui-io
to: 92-bad-gui-io
- from: java/mvc-io/simple-mvc-io
to: 84-simple-mvc-io
to: 93-simple-mvc-io
- from: java/mvc-io/mvc-io
to: 85-mvc-io
to: 94-mvc-io
- from: java/mvc-io/advanced-mvc
to: 86-advanced-mvc
to: 95-advanced-mvc
- from: git/instructions-clone.md
to: README.md
oop-lab09:
format:
- repo
destinations:
- unibo-oop/lab09
contents:
- from: java/concurrency/reactive-gui
to: 91-reactive-guis
to: 96-reactive-guis
- from: java/concurrency/workers
to: 92-workers
to: 97-workers
- from: git/instructions-clone.md
to: README.md
oop-lab10:
Expand Down
18 changes: 3 additions & 15 deletions java/testing/junit/README.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,8 @@
# Unit testing with JUnit 5

Consider the *Banking* domain of previous labs: `AccountHolder`s, `BankAccount`s, and their implementations.
Write unit tests to verify the correctness of these classes.

Guidelines:
1. Look at the class `TestSimpleBankAccount` that tests `SimpleBankAccount` implementation.
2. Write tests described in class `TestStrictBankAccount` that tests `StrictBankAccount`.

* For each class, test the construction of new instances (their initial state)
* Test each method/behaviour works as expected
* Consider "corner cases" - i.e. what might happen if the user passes "weird" inputs (e.g., a deposit of a negative value)
* Use what your "learn" by preparing/running tests to improve the soundness of your implementations

1. Write a test class `TestSimpleBankAccount` to test `SimpleBankAccount`
2. Write a test class `TestStrictBankAccount` to test `StrictBankAccount`

Notes:

* Consult the [JavaDoc of JUnit 5](https://junit.org/junit5/docs/5.0.1/api/org/junit/jupiter/api/package-summary.html)
* For asserting equality on floats/doubles, consider using the overloaded method `assertEquals(double expected, double actual, double delta)` which allows you to specify a `delta` (tolerance) for equality
* By the way, notice that the "standard" handling of floats/doubles (cf. IEEE 754) in programming languages is not a great choice for the management of bank accounts and financial transactions. It would be far better to use [`BigDecimal`](https://docs.oracle.com/javase/8/docs/api/java/math/BigDecimal.html).
Notes: Consult the [JavaDoc of JUnit 5](https://junit.org/junit5/docs/5.0.1/api/org/junit/jupiter/api/package-summary.html)
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@

public class SimpleBankAccount implements BankAccount {

protected static final double ATM_TRANSACTION_FEE = 1;
protected static final double MANAGEMENT_FEE = 5;
public static final double ATM_TRANSACTION_FEE = 1;
public static final double MANAGEMENT_FEE = 5;

private final AccountHolder holder;
private double balance;
Expand All @@ -19,31 +19,19 @@ public SimpleBankAccount(final AccountHolder accountHolder, final double balance
}

public void chargeManagementFees(final int id) {
/*
* Riduce il bilancio del conto di un ammontare pari alle spese di gestione
*/
if (checkUser(id)) {
this.balance -= SimpleBankAccount.MANAGEMENT_FEE;
resetTransactions();
} else {
throw new IllegalArgumentException("ID not corresponding: cannot charge management fees");
}
}

public void deposit(final int id, final double amount) {
/*
* Incrementa il numero di transazioni e aggiunge amount al totale del
* conto Nota: il deposito va a buon fine solo se l'id utente
* corrisponde
*/
this.transactionOp(id, amount);
}

public void depositFromATM(final int id, final double amount) {
/*
* Incrementa il numero di transazioni e aggiunge amount al totale del
* conto detraendo le spese (costante ATM_TRANSACTION_FEE) relative
* all'uso dell'ATM (bancomat) Nota: il deposito va a buon fine solo se
* l'id utente corrisponde
*/
this.deposit(id, amount - SimpleBankAccount.ATM_TRANSACTION_FEE);
}

Expand All @@ -65,22 +53,10 @@ public int getTransactionsCount() {
}

public void withdraw(final int id, final double amount) {
/*
* Incrementa il numero di transazioni e rimuove amount al totale del
* conto. Note: - Il conto puo' andare in rosso (ammontare negativo) -
* Il prelievo va a buon fine solo se l'id utente corrisponde
*/
this.transactionOp(id, -amount);
}

public void withdrawFromATM(final int id, final double amount) {
/*
* Incrementa il numero di transazioni e rimuove amount + le spese
* (costante ATM_TRANSACTION_FEE) relative all'uso dell'ATM (bancomat)
* al totale del conto. Note: - Il conto puo' andare in rosso (ammontare
* negativo) - Il prelievo va a buon fine solo se l'id utente
* corrisponde
*/
this.withdraw(id, amount + SimpleBankAccount.ATM_TRANSACTION_FEE);
}

Expand All @@ -100,6 +76,8 @@ private void transactionOp(final int id, final double amount) {
if (checkUser(id)) {
this.balance += amount;
this.incrementTransactions();
} else {
throw new IllegalArgumentException("ID not corresponding: cannot perform transaction");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

public class StrictBankAccount extends SimpleBankAccount {

private static final double TRANSACTION_FEE = 0.1;
public static final double TRANSACTION_FEE = 0.1;

public StrictBankAccount(final AccountHolder accountHolder, final double balance) {
super(accountHolder, balance);
Expand All @@ -15,17 +15,24 @@ public void chargeManagementFees(final int usrID) {
if (checkUser(usrID) && isWithdrawAllowed(feeAmount)) {
setBalance(getBalance() - feeAmount);
resetTransactions();
} else {
throw new IllegalArgumentException("ID not corresponding: cannot charge management fees");
}
}

public void withdraw(final int usrID, final double amount) {
if (amount < 0) {
throw new IllegalArgumentException("Cannot withdraw a negative amount");
}
if (isWithdrawAllowed(amount)) {
super.withdraw(usrID, amount);
} else {
throw new IllegalArgumentException("Insufficient balance");
}
}

protected boolean isWithdrawAllowed(final double amount) {
return getBalance() > amount;
return (amount > 0 && getBalance() > amount);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,61 @@
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Assertions;

import it.unibo.bank.api.AccountHolder;
import it.unibo.bank.api.BankAccount;

public class TestSimpleBankAccount {
private AccountHolder aRossi;
private AccountHolder mRossi;
private AccountHolder aBianchi;
private BankAccount bankAccount;

/**
* Configuration step: this is performed BEFORE each test.
*/
@BeforeEach
public void setUp() {
this.aRossi = new AccountHolder("Andrea", "Rossi", 1);
this.aBianchi = new AccountHolder("Alex", "Bianchi", 2);
this.mRossi = new AccountHolder("Mario", "Rossi", 1);
this.aBianchi = new AccountHolder("Andrea", "Bianchi", 2);
this.bankAccount = new SimpleBankAccount(mRossi, 0.0);
}

/**
* Check that the initialization of the BankAccount is created with the correct values.
*/
@Test
public void testNewSimpleBankAccount() {
BankAccount account = new SimpleBankAccount(aRossi, 0.0);
Assertions.assertEquals(0.0, account.getBalance());
Assertions.assertEquals(0, account.getTransactionsCount());
Assertions.assertSame(this.aRossi, account.getAccountHolder());
}
public void testBankAccountInitialization() {
Assertions.assertEquals(0.0, bankAccount.getBalance());
Assertions.assertEquals(0, bankAccount.getTransactionsCount());
Assertions.assertEquals(mRossi, bankAccount.getAccountHolder());
}

/**
* Check that the deposit is performed correctly on the Bank Account.
*/
@Test
public void testBankAccountDeposit() {
int expectedValue = 0;
Assertions.assertFalse(bankAccount.getTransactionsCount() > 0);
for(int i = 0; i < 10; i++) {
expectedValue += i * 100;
bankAccount.deposit(mRossi.getUserID(), i * 100);
}
Assertions.assertEquals(expectedValue, bankAccount.getBalance());
Assertions.assertTrue(bankAccount.getTransactionsCount() > 0);
}

/**
* Checks that if the wrong AccountHolder id is given, the deposit will return an IllegalArgumentException.
*/
@Test
public void testWrongBankAccountDeposit() {
try {
bankAccount.deposit(aBianchi.getUserID(), 10000);
Assertions.fail();
} catch (IllegalArgumentException e) {
Assertions.assertEquals("ID not corresponding: cannot perform transaction.", e.getMessage());
}
// Alternative (with reflection): Assertions.assertThrows
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package it.unibo.bank.impl;

import it.unibo.bank.api.AccountHolder;
import it.unibo.bank.api.BankAccount;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import static it.unibo.bank.impl.SimpleBankAccount.*;
import static it.unibo.bank.impl.SimpleBankAccount.ATM_TRANSACTION_FEE;
import static it.unibo.bank.impl.StrictBankAccount.TRANSACTION_FEE;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class TestStrictBankAccount {

private final static int INITIAL_AMOUNT = 100;

// 1. Create a new AccountHolder and a StrictBankAccount for it each time tests are executed.
private AccountHolder mRossi;
private BankAccount bankAccount;

@BeforeEach
public void setUp() {
mRossi = new AccountHolder("Mario", "Rossi", 1);
bankAccount = new StrictBankAccount(mRossi, 0.0);
}

// 2. Test the initial state of the StrictBankAccount
@Test
public void testInitialization() {
assertEquals(0.0, bankAccount.getBalance());
assertEquals(0, bankAccount.getTransactionsCount());
assertEquals(mRossi, bankAccount.getAccountHolder());
}


// 3. Perform a deposit of 100€, compute the management fees, and check that the balance is correctly reduced.
@Test
public void testManagementFees() {
bankAccount.deposit(mRossi.getUserID(), INITIAL_AMOUNT);
assertEquals(INITIAL_AMOUNT, bankAccount.getBalance());
bankAccount.chargeManagementFees(mRossi.getUserID());
assertEquals(INITIAL_AMOUNT - TRANSACTION_FEE - MANAGEMENT_FEE, bankAccount.getBalance());
}

// 4. Test the withdraw of a negative value
@Test
public void testNegativeWithdraw() {
try {
bankAccount.withdraw(mRossi.getUserID(), -INITIAL_AMOUNT);
} catch (IllegalArgumentException e) {
assertNotNull(e.getMessage());
assertTrue(e.getMessage().length() > 1);
}
}

// 4. Test withdrawing more money than it is in the account
@Test
public void testWithdrawingTooMuch() {
try {
bankAccount.withdraw(mRossi.getUserID(), INITIAL_AMOUNT);
} catch (IllegalArgumentException e) {
assertNotNull(e.getMessage());
assertTrue(e.getMessage().length() > 1);
}
}
}
44 changes: 44 additions & 0 deletions java/testing/tdd-deathnote/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Test-first

Use the *Test-Driven Development (TDD)* methodology to develop the following.

1. Observe the `DeathNote` interface, understand how it is supposed to work
2. Create an implementation of `DeathNote` in which each method throws an Exception
3. Write a test for the `DeathNote` implementation (the test will fail and that is okay) testing the following:
1. Rule number 0 and negative rules do not exist in the DeathNote rules.
* check that the exceptions are thrown correctly, that their type is the expected one, and that the message is not null, empty, or blank.
2. No rule is empty or null in the DeathNote rules.
* for all the valid rules, check that none is null or blank
3. The human whose name is written in the DeathNote will eventually die.
* verify that the human has not been written in the notebook yet
* write the human in the notebook
* verify that the human has been written in the notebook
* verify that another human has not been written in the notebook
* verify that the empty string has not been written in the notebook
4. Only if the cause of death is written within the next 40 milliseconds of writing the person's name, it will happen.
* check that writing a cause of death before writing a name throws the correct exception
* write the name of a human in the notebook
* verify that the cause of death is a heart attack
* write the name of another human in the notebook
* set the cause of death to "karting accident"
* verify that the cause of death has been set correctly (returned true, and the cause is indeed "karting accident")
* sleep for 100ms
* try to change the cause of death
* verify that the cause of death has not been changed
5. Only if the cause of death is written within the next 6 seconds and 40 milliseconds of writing the death's details, it will happen.
* check that writing the death details before writing a name throws the correct exception
* write the name of a human in the notebook
* verify that the details of the death are currently empty
* set the details of the death to "ran for too long"
* verify that death details have been set correctly (returned true, and the details are indeed "ran for too long")
* write the name of another human in the notebook
* sleep for 6100ms
* try to change the details
* verify that the details have not been changed
4. Ask for a correction of the tests
5. Verify that all tests fail
6. Modify the implementation of the `DeathNote` in such a way that all tests work

**Important notes**:
* the current time measured as seconds since the Unix epoch is available as `System.currentTimeMillis()`.
* the execution of a program can be paused for a given number of milliseconds using `Thread.sleep(long millis)`.
Loading

0 comments on commit dc42370

Please sign in to comment.