Task complete - #1338
Task complete#1338Hubris0 wants to merge 8 commits into
Conversation
… for supplied transactions.
slade13
left a comment
There was a problem hiding this comment.
You have already written several useful scenarios and you are thinking about invalid input, insufficient fruit quantity, and the complete application flow, which is a good start. However, the most important requirement of this task is currently missing: unit tests must be isolated.
Right now almost the whole application is tested through one MainTest, with multiple services, strategies, file operations, and handlers participating in the same tests. Split these tests into separate classes and test every component directly. Prepare Storage directly when testing transactions or report creation instead of reaching the tested functionality through other services.
Also improve test naming so that the first part contains the actual method under test, remove assertions that only verify the manually prepared input, and move test files to src/test/resources.
| import org.junit.jupiter.api.BeforeEach; | ||
| import org.junit.jupiter.api.Test; | ||
|
|
||
| class MainTest { |
There was a problem hiding this comment.
Tests should be separated by responsibility, currently everything is placed inside:
class MainTest
This is directly against the task requirements. You should create separate test classes for the components you want to verify, for example:
ReaderImplTest
WriterImplTest
ReportCreatorTest
BalanceTransactionTest
SupplyTransactionTest
PurchaseTransactionTest
ReturnTransactionTest
OperationStrategyImplTest
TransactionHandlerTest
Creating MainTest still suggests that you're testing the application's complete flow rather than individual units.
…r in test variables. Fixed import order.
slade13
left a comment
There was a problem hiding this comment.
Please review my comments, additionally fields like:
private static final String fruitName
private static final int fruitQuantity
are constants and should normally use uppercase naming:
private static final String FRUIT_NAME = "testfruit";
private static final int FRUIT_QUANTITY = 123;
Please fix things mentioned by my comments and request another review.
| class SupplyTransactionTest { | ||
| private static final String fruitName = "testfruit"; | ||
| private static final int fruitQuantity = 123; | ||
| private final ReturnTransaction returnTransaction = new ReturnTransaction(); |
There was a problem hiding this comment.
You have:
private final ReturnTransaction returnTransaction = new ReturnTransaction();
and later:
returnTransaction.process(fruitName, suppliedQuantity);
So despite the class being named SupplyTransactionTest, it actually tests ReturnTransaction.
| } | ||
|
|
||
| @Test | ||
| void process_inputAllTransactions_ok() { |
There was a problem hiding this comment.
This test still combines:
ReaderImplTransactionHandlerOperationStrategyImpl- all transaction implementations
ReportCreatorWriterImpl- another
ReaderImpl
This recreates almost the entire program flow.
The task explicitly says:
Keep your strategy, handler, and service tests separate from each other.
For TransactionHandlerTest, it is enough to create a strategy containing only the transaction needed for the particular test.
There was a problem hiding this comment.
The task explicitly says that the test resources should be placed in:
src/test/resources/[your-files.csv]
but the project still contains test files such as:
src/main/resources/testOk.csv
src/main/resources/testEmpty.csv
src/main/resources/testIncorrectAmount.csv
src/main/resources/testMissingElement.csv
These should be moved to:
src/test/resources
| import org.junit.jupiter.api.AfterEach; | ||
| import org.junit.jupiter.api.Test; | ||
|
|
||
| class WriterImplTest { |
There was a problem hiding this comment.
This test file currently checks only:
assertTrue(Files.exists(Path.of(path)));
That verifies that a file exists, but not whether WriterImpl actually wrote the correct data.
A stronger test should also read the file and compare its contents.
| } | ||
|
|
||
| @Test | ||
| void process_purchaseMoreThanAvailable_ok() { |
There was a problem hiding this comment.
The test expects:
assertThrows(...)
So this is clearly a negative scenario and should be:
| void process_purchaseMoreThanAvailable_ok() { | |
| void process_purchaseMoreThanAvailable_notOk() { |
|
|
||
| @Test | ||
| @Tag("SkipSetup") | ||
| void process_fruitNotExist_ok() { |
There was a problem hiding this comment.
Same here:
| void process_fruitNotExist_ok() { | |
| void process_fruitNotExist_notOk() { |
| import org.junit.jupiter.api.BeforeEach; | ||
| import org.junit.jupiter.api.Test; | ||
|
|
||
| class OperationStrategyImplTest { |
There was a problem hiding this comment.
OperationStrategyImplTest can be simpler and more isolated, currently you create all real transaction implementations:
Map.of(
"b", new BalanceTransaction(),
"s", new SupplyTransaction(),
"p", new PurchaseTransaction(),
"r", new ReturnTransaction()
)
To test OperationStrategyImpl, you don't really need to verify every concrete transaction class.
The important behavior is:
- existing operation returns the corresponding handler;
- unsupported operation throws an exception
A map containing one transaction is enough for this unit test and follows the task hint:
It's enough to create a map with only one handler.
So something like:
Map.of("b", new BalanceTransaction())
would be sufficient.
…y test transactions. Fixed naming issues in tests. Fixed variable naming issues.
mateuszwojtkowiak
left a comment
There was a problem hiding this comment.
Good job! Please read my comments and apply changes.
|
|
||
| class ReaderImplTest { | ||
| private final Reader reader = new ReaderImpl(); | ||
| private String path; |
There was a problem hiding this comment.
Tests should be isolated and path is not constant, so it would be better to use local variables:
void readFromFile_noFile_notOk() {
String path = "";
| void readFromFile_noFile_notOk() { | ||
| path = ""; | ||
| assertThrows(RuntimeException.class, () -> reader.readFromFile(path), | ||
| "File to read should not have been found"); |
There was a problem hiding this comment.
The message "File to read should not have been found" is grammatically incorrect and unnatural.
It suggests that the file was actively searched for and should not have been found, which is not the intended meaning. In this test, we simply expect that the file does not exist.
A clearer and idiomatic message would be: "File to read could not be found" or "Expected the file to be missing".
| void readFromFile_fileExists_ok() { | ||
| path = "src/test/resources/testOk.csv"; | ||
| assertFalse(reader.readFromFile(path).isEmpty(), | ||
| "The provided file: " + path + " is empty"); |
There was a problem hiding this comment.
If file exists, then this message should not say that it is empty. It should look like that:
Expected file should contain data.
| void readFromFile_fileEmpty_notOk() { | ||
| path = "src/test/resources/testEmpty.csv"; | ||
| assertThrows(IllegalArgumentException.class, () -> reader.readFromFile(path), | ||
| "The provided file: " + path + " is not empty"); |
There was a problem hiding this comment.
The test expects that file will be empty. But the message says that it is not empty.
It should look like that:
Expected file to be empty.
| @Tag("SkipCleanup") | ||
| void createReport_storageEmpty_notOk() { | ||
| assertThrows(IllegalArgumentException.class, ReportCreator::createReport, | ||
| "Storage is not empty"); |
There was a problem hiding this comment.
You expect that storage should not be empty, but the message says that it is not. The message should look like that:
Expected storage to be empty.
| .stream() | ||
| .filter(f -> f.getName().equals(FRUIT_NAME)) | ||
| .findFirst() | ||
| .get(); |
There was a problem hiding this comment.
get() on Optional can give you NoSuchElementException so it's better to use AssertionError with descriptive message:
Fruit fruit = fruits.stream()
.filter(f -> f.getName().equals(FRUIT_NAME))
.findFirst()
.orElseThrow(() -> new AssertionError("Fruit was not added to storage"));
| void process_inputMissingElement_notOk() { | ||
| List<String> lines = List.of("b,banana"); | ||
| assertThrows(ArrayIndexOutOfBoundsException.class, () -> { | ||
| for (String line : lines) { |
There was a problem hiding this comment.
You don't need loop because you test only one line:
assertThrows(ArrayIndexOutOfBoundsException.class,
() -> transactionHandler.process("b,banana"));
| void process_tooManyElements_notOk() { | ||
| List<String> lines = List.of("b,banana,10,extra"); | ||
| assertThrows(ArrayIndexOutOfBoundsException.class, () -> { | ||
| for (String line : lines) { |
There was a problem hiding this comment.
You don't need loop because you test only one line:
assertThrows(ArrayIndexOutOfBoundsException.class,
() -> transactionHandler.process("b,banana,10,extra"));
| List<String> lines = List.of("b,banana,-2"); | ||
| assertThrows(IllegalArgumentException.class, () -> { | ||
| for (String line : lines) { | ||
| transactionHandler.process(line); |
There was a problem hiding this comment.
You don't need loop because you test only one line:
assertThrows(ArrayIndexOutOfBoundsException.class,
() -> transactionHandler.process("b,banana,-2")));
| Storage.getFruits().clear(); | ||
| } | ||
|
|
||
| private void seedFruit(String name, int quantity) { |
There was a problem hiding this comment.
For tests that use this method, it's worth adding assertion that will check if Storage has exactly one fruit.
assertEquals(1, Storage.getFruits().size());
…. Fixed constants naming.
slade13
left a comment
There was a problem hiding this comment.
Good job, your code is much closer to proper unit testing and the major structural problems from the previous submissions have been fixed.
The main remaining improvement is isolation: WriterImplTest should not use ReaderImpl, and TransactionHandlerTest doesn't need to retest all four transaction implementations because those already have dedicated tests.
Please review my comments and update your code.
|
|
||
| @BeforeEach | ||
| void setUp() { | ||
| Map<String, Transaction> transactionMap = Map.of( |
There was a problem hiding this comment.
Currently setUp() creates the whole strategy:
Map<String, Transaction> transactionMap = Map.of(
"b", new BalanceTransaction(),
"s", new SupplyTransaction(),
"p", new PurchaseTransaction(),
"r", new ReturnTransaction()
);
and then tests all four transactions through TransactionHandler.
The task specifically recommends keeping handler and strategy tests isolated and says that a map with one handler is enough.
Since BalanceTransaction, PurchaseTransaction, etc. already have their own test classes, TransactionHandlerTest doesn't need to verify their business logic again.
| @Test | ||
| void process_inputMissingElement_notOk() { | ||
| String line = "b,banana"; | ||
| assertThrows(ArrayIndexOutOfBoundsException.class, | ||
| () -> transactionHandler.process(line)); | ||
| } | ||
|
|
||
| @Test | ||
| void process_tooManyElements_notOk() { | ||
| String line = "b,banana,10,extra"; | ||
| assertThrows(ArrayIndexOutOfBoundsException.class, | ||
| () -> transactionHandler.process(line)); | ||
| } | ||
|
|
||
| @Test | ||
| void process_incorrectAmount_notOk() { | ||
| String line = "b,banana,-2"; | ||
| assertThrows(IllegalArgumentException.class, | ||
| () -> transactionHandler.process(line)); | ||
| } |
There was a problem hiding this comment.
Avoid testing implementation-specific exceptions:
assertThrows(ArrayIndexOutOfBoundsException.class,
() -> transactionHandler.process(line));
ArrayIndexOutOfBoundsException looks more like an accidental consequence of the current implementation than part of the application's contract.
A better implementation would probably validate the transaction format and throw something intentional such as:
IllegalArgumentException
Then the tests would verify business behavior rather than a side effect of array indexing.
This is partially an issue with production code, not only tests.
| import org.junit.jupiter.api.Test; | ||
|
|
||
| class WriterImplTest { | ||
| private final Reader reader = new ReaderImpl(); |
There was a problem hiding this comment.
You improved this test class nicely, but WriterImplTest should ideally not use ReaderImpl.
WriterImplTest depends now on ReaderImpl, the task explicitly emphasizes isolated testing. Use Java directly:
List<String> reportLines = Files.readAllLines(Path.of(path));
After that WriterImplTest tests only WriterImpl and ReaderImpl already has its own test class.
| void createReport_reportCreated_ok() { | ||
| String expectedReport = REPORT_HEADER + System.lineSeparator() | ||
| + FRUIT_NAME + "," + FRUIT_QUANTITY + System.lineSeparator(); | ||
| assertEquals(expectedReport,createReport(), |
There was a problem hiding this comment.
Fix formatting issue:
| assertEquals(expectedReport,createReport(), | |
| assertEquals(expectedReport, createReport(), |
| @Test | ||
| void getOperationType_balanceInput_ok() { | ||
| String input = "b"; | ||
| assertInstanceOf(BalanceTransaction.class,operationStrategy.getOperationType(input), |
There was a problem hiding this comment.
Reformat this fragment of code:
| assertInstanceOf(BalanceTransaction.class,operationStrategy.getOperationType(input), | |
| assertInstanceOf( | |
| BalanceTransaction.class, | |
| operationStrategy.getOperationType(input), |
| int startBalance = 50; | ||
| int purchase = 30; | ||
| int expectedBalance = startBalance - purchase; | ||
| seedFruit(expectedFruit,startBalance); |
There was a problem hiding this comment.
Missing space:
| seedFruit(expectedFruit,startBalance); | |
| seedFruit(expectedFruit, startBalance); |
| int startBalance = 50; | ||
| int returned = 30; | ||
| int expectedBalance = startBalance + returned; | ||
| seedFruit(expectedFruit,startBalance); |
There was a problem hiding this comment.
Missing space:
| seedFruit(expectedFruit,startBalance); | |
| seedFruit(expectedFruit, startBalance); |
| int startBalance = 50; | ||
| int supplied = 30; | ||
| int expectedBalance = startBalance + supplied; | ||
| seedFruit(expectedFruit,startBalance); |
There was a problem hiding this comment.
Missing space:
| seedFruit(expectedFruit,startBalance); | |
| seedFruit(expectedFruit, startBalance); |
…andler logic to better validate input. Removed obsolete test cases. General code formatting.
Prepared test coverage for creating transaction report for supplied transactions.