Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- /*
- GuessTheNumber.java = simple “guess the number” game
- tests/GuessTheNumberTest.java = JUnit test for the game
- tests/SystemInputOutputTesterTest.java = Test the main JUnit testing factory.
- tests/RandomSourceTest.java = Test the "Random" class related functionality and mocks used by the "GuessTheNumber" class.
- tests/ExpectedException.java = Independent exception (child of Error) to throw when the "code under test" was expected to throw an exception (typically an assertion error), but did not.
- library/RandomSource.java = creates "Random" instances (or mock) for the Program Under Test
- library/RandomCreatorInterface.java = interface to create "Random" instances (enables lambda expressions)
- library/AbstractMockRandomSwapper.java = swaps mock Random subclasses in on construction and out on close
- library/MockRandomSingleValue.java = mock implementation for single-use random number
- library/SystemInputOutputTester.java = Builder for console I/O testing
- library/CallbackThrowsException.java = interface for call back to "main" method -- to handle checked exceptions
- library/MockStandardOutputStream.java = Capture Standard Output (or Error)
- library/MockInputStream.java = Provide input when needed. Calls back to SystemInputOutputTester to validate intervening output and to provide the input data.
- library/ExpectedInputOutputStepBase.java = common base class for input/output expectations
- library/ExpectedTextOutputStep.java = Expect that the given text output is written to Standard Output (or Error).
- library/ProvideLineInputStep.java = Provide line of Console Input at that point in the expectations list.
- */
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- //
- // File Name: GuessTheNumber.java
- //
- package p20230510_TestConsoleIO;
- import p20230510_TestConsoleIO.library.RandomSource;
- import java.util.Scanner;
- public class GuessTheNumber {
- public static void main(final String[] args) {
- System.out.println("Welcome to the number guessing program.");
- System.out.println("I am thinking of a number in the range of 1 to 100, inclusive.");
- System.out.println("You need to guess this number, with a minimum number of guesses.");
- System.out.println("Each time you guess, I will tell you if my number is HIGHER or LOWER than your guess.");
- final var random = RandomSource.create();
- final int numberToGuess = random.nextInt(100) + 1;
- final var scanner = new Scanner(System.in);
- var numberOfGuesses = 0;
- while (true) {
- System.out.println("What is your guess?");
- final var userGuess = scanner.nextInt();
- ++numberOfGuesses;
- if (userGuess < numberToGuess) {
- System.out.println("Your guess of " + userGuess + " is TOO LOW.");
- } else if (userGuess > numberToGuess) {
- System.out.println("Your guess of " + userGuess + " is TOO HIGH.");
- } else {
- System.out.println("Your guess of " + userGuess + " is CORRECT in "
- + (numberOfGuesses == 1 ? "one guess!!!" : (numberOfGuesses + " guesses!")));
- break;
- }
- }
- }
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- //
- // File Name: tests/GuessTheNumberTest.java
- //
- package p20230510_TestConsoleIO.tests;
- // https://mastodon.social/@GeePawHill/110341886456025504 = Hard to TDD "Hello World" and simple console I/O "games"
- // https://mastodon.social/@JeffGrigg/110347321176183414 = Jeff "Grigg says that "doing TDD through a User Interface is Always Difficult."
- // https://mastodon.social/@GeePawHill/110351924530710192 = GeePawHill proposes hiding complexity in a library to protect students
- // https://mastodon.social/@JeffGrigg/110356052836313274 = posting my implementation
- // https://mastodon.social/@JeffGrigg/110358610496829363 = my implementation, cleaned and improved -- "Cleaned it up a bit. Better interfaces and reduced coupling. Separate "library" and "tests" packages. Added tests."
- import org.junit.Test;
- import p20230510_TestConsoleIO.GuessTheNumber;
- import p20230510_TestConsoleIO.library.MockRandomSingleValue;
- import p20230510_TestConsoleIO.library.SystemInputOutputTester;
- import java.io.IOException;
- public class GuessTheNumberTest {
- @Test
- public void testHighsAndLowsTo33() throws IOException {
- final var systemNumberWeAreTryingToGuess = 33;
- try (final var mockRandomNumberGenerator = new MockRandomSingleValue(systemNumberWeAreTryingToGuess)) {
- SystemInputOutputTester
- .whenCallingThisMethod(() -> {
- GuessTheNumber.main(null);
- })
- .assertTextOutput("Welcome to the number guessing program.")
- //
- .assertTextOutput("I am thinking of a number in the range of 1 to 100, inclusive.")
- .assertTextOutput("You need to guess this number, with a minimum number of guesses.")
- .assertTextOutput("Each time you guess, I will tell you if my number is HIGHER or LOWER than your guess.")
- //
- .assertTextOutput("What is your guess?")
- .provideLineInput("50")
- .assertTextOutput("Your guess of 50 is TOO HIGH.")
- //
- .assertTextOutput("What is your guess?")
- .provideLineInput("25")
- .assertTextOutput("Your guess of 25 is TOO LOW.")
- //
- .assertTextOutput("What is your guess?")
- .provideLineInput("37")
- .assertTextOutput("Your guess of 37 is TOO HIGH.")
- //
- .assertTextOutput("What is your guess?")
- .provideLineInput("31")
- .assertTextOutput("Your guess of 31 is TOO LOW.")
- //
- .assertTextOutput("What is your guess?")
- .provideLineInput("34")
- .assertTextOutput("Your guess of 34 is TOO HIGH.")
- //
- .assertTextOutput("What is your guess?")
- .provideLineInput("32")
- .assertTextOutput("Your guess of 32 is TOO LOW.")
- //
- .assertTextOutput("What is your guess?")
- .provideLineInput("33")
- .assertTextOutput("Your guess of 33 is CORRECT in 7 guesses!")
- //
- .assertThatTheMethodReturnsHere();
- }
- }
- @Test
- public void testMaximumGuesses() throws IOException {
- final var systemNumberWeAreTryingToGuess = 100;
- try (final var mockRandomNumberGenerator = new MockRandomSingleValue(systemNumberWeAreTryingToGuess)) {
- SystemInputOutputTester
- .whenCallingThisMethod(() -> {
- GuessTheNumber.main(null);
- })
- .assertTextOutput("Welcome to the number guessing program.")
- //
- .assertTextOutput("I am thinking of a number in the range of 1 to 100, inclusive.")
- .assertTextOutput("You need to guess this number, with a minimum number of guesses.")
- .assertTextOutput("Each time you guess, I will tell you if my number is HIGHER or LOWER than your guess.")
- //
- .assertTextOutput("What is your guess?")
- .provideLineInput("50")
- .assertTextOutput("Your guess of 50 is TOO LOW.")
- //
- .assertTextOutput("What is your guess?")
- .provideLineInput("75")
- .assertTextOutput("Your guess of 75 is TOO LOW.")
- //
- .assertTextOutput("What is your guess?")
- .provideLineInput("88")
- .assertTextOutput("Your guess of 88 is TOO LOW.")
- //
- .assertTextOutput("What is your guess?")
- .provideLineInput("94")
- .assertTextOutput("Your guess of 94 is TOO LOW.")
- //
- .assertTextOutput("What is your guess?")
- .provideLineInput("97")
- .assertTextOutput("Your guess of 97 is TOO LOW.")
- //
- .assertTextOutput("What is your guess?")
- .provideLineInput("99")
- .assertTextOutput("Your guess of 99 is TOO LOW.")
- //
- .assertTextOutput("What is your guess?")
- .provideLineInput("100")
- .assertTextOutput("Your guess of 100 is CORRECT in 7 guesses!")
- //
- .assertThatTheMethodReturnsHere();
- }
- }
- @Test
- public void testFirstGuessIsCorrect() throws IOException {
- final var systemNumberWeAreTryingToGuess = 24;
- try (final var mockRandomNumberGenerator = new MockRandomSingleValue(systemNumberWeAreTryingToGuess)) {
- SystemInputOutputTester
- .whenCallingThisMethod(() -> {
- GuessTheNumber.main(null);
- })
- .assertTextOutput("Welcome to the number guessing program.")
- //
- .assertTextOutput("I am thinking of a number in the range of 1 to 100, inclusive.")
- .assertTextOutput("You need to guess this number, with a minimum number of guesses.")
- .assertTextOutput("Each time you guess, I will tell you if my number is HIGHER or LOWER than your guess.")
- //
- .assertTextOutput("What is your guess?")
- .provideLineInput("24")
- .assertTextOutput("Your guess of 24 is CORRECT in one guess!!!")
- //
- .assertThatTheMethodReturnsHere();
- }
- }
- @Test
- public void testSecondGuessIsCorrect() throws IOException {
- final var systemNumberWeAreTryingToGuess = 33;
- try (final var mockRandomNumberGenerator = new MockRandomSingleValue(systemNumberWeAreTryingToGuess)) {
- SystemInputOutputTester
- .whenCallingThisMethod(() -> {
- GuessTheNumber.main(null);
- })
- .assertTextOutput("Welcome to the number guessing program.")
- //
- .assertTextOutput("I am thinking of a number in the range of 1 to 100, inclusive.")
- .assertTextOutput("You need to guess this number, with a minimum number of guesses.")
- .assertTextOutput("Each time you guess, I will tell you if my number is HIGHER or LOWER than your guess.")
- //
- .assertTextOutput("What is your guess?")
- .provideLineInput("86")
- .assertTextOutput("Your guess of 86 is TOO HIGH.")
- //
- .assertTextOutput("What is your guess?")
- .provideLineInput("33")
- .assertTextOutput("Your guess of 33 is CORRECT in 2 guesses!")
- //
- .assertThatTheMethodReturnsHere();
- }
- }
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- //
- // File Name: tests/SystemInputOutputTesterTest.java
- //
- package p20230510_TestConsoleIO.tests;
- import junit.framework.AssertionFailedError;
- import org.junit.Test;
- import p20230510_TestConsoleIO.library.SystemInputOutputTester;
- import java.io.BufferedReader;
- import java.io.IOException;
- import java.io.InputStreamReader;
- import static org.junit.Assert.assertEquals;
- public class SystemInputOutputTesterTest {
- private static class DoNothingProgram {
- public static void main(String[] args) {
- // Intentionally do NOTHING here.
- }
- }
- @Test
- public void testDoNothingProgram() {
- SystemInputOutputTester
- .whenCallingThisMethod(() -> {
- DoNothingProgram.main(null);
- })
- .assertThatTheMethodReturnsHere();
- }
- @Test
- public void testMissingMessage() {
- try {
- SystemInputOutputTester
- .whenCallingThisMethod(() -> {
- DoNothingProgram.main(null);
- })
- .assertTextOutput("This program does not and should not produce this output.")
- .assertThatTheMethodReturnsHere();
- throw new ExpectedException("Expected AssertionFailedError to be thrown by the code above.");
- } catch (final AssertionFailedError ex) {
- assertEquals("Failed to find <This program does not and should not produce this output.> in <>", ex.getMessage());
- }
- }
- @Test
- public void testProgramTerminatesWithMoreInputToProcess() {
- try {
- SystemInputOutputTester
- .whenCallingThisMethod(() -> {
- DoNothingProgram.main(null);
- })
- .provideLineInput("Unused Input Line")
- .assertThatTheMethodReturnsHere();
- throw new ExpectedException("Expected AssertionFailedError to be thrown by the code above.");
- } catch (final AssertionFailedError ex) {
- assertEquals("Program has exited, but the tests say that we should provide further input."
- + System.lineSeparator() + " ProvideLineInputStep('Unused Input Line')", ex.getMessage());
- }
- }
- @Test
- public void testHelloWorldProgram() {
- SystemInputOutputTester
- .whenCallingThisMethod(() -> {
- HelloWorldProgram.main(null);
- })
- .assertTextOutput("Hello world!")
- .assertThatTheMethodReturnsHere();
- }
- private static class HelloWorldProgram {
- public static void main(String[] args) {
- System.out.println("Hello world!");
- }
- }
- @Test
- public void testHelloNameProgram() {
- SystemInputOutputTester
- .whenCallingThisMethod(() -> {
- HelloNameProgram.main(null);
- })
- .assertTextOutput("What is your name? ")
- .provideLineInput("John")
- .assertTextOutput("Hello John!")
- .assertThatTheMethodReturnsHere();
- }
- private static class HelloNameProgram {
- public static void main(String[] args) throws IOException {
- System.out.println("What is your name? ");
- final var in = new BufferedReader(new InputStreamReader(System.in));
- final var name = in.readLine();
- System.out.println("Hello " + name + "!");
- }
- }
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- //
- // File Name: tests/RandomSourceTest.java
- //
- package p20230510_TestConsoleIO.tests;
- import org.junit.Test;
- import p20230510_TestConsoleIO.library.MockRandomSingleValue;
- import p20230510_TestConsoleIO.library.RandomSource;
- import java.io.IOException;
- import java.rmi.server.ExportException;
- import java.util.Random;
- import java.util.TreeSet;
- import static org.junit.Assert.*;
- public class RandomSourceTest {
- private static final double ODDS_OF_FALSE_TEST_FAILURE = 0.000001; // -- PER number we're looking for.
- private static final String NL = System.lineSeparator();
- private final Random _random = RandomSource.create();
- private double _lowestConfidence = 1.0;
- @Test
- public void testCreateRandomNumberGenerator() throws IOException {
- // Initial (production) state:
- assertSame("Initial Java Random number generator class;", Random.class, _random.getClass());
- // Creating mock Random number generator swaps in a factory for it as a side-effect:
- try (final var mockRandomNumberGenerator = new MockRandomSingleValue(13)) {
- assertSame(mockRandomNumberGenerator, RandomSource.create());
- assertEquals("Mock Random number, as configured above;",
- 13, mockRandomNumberGenerator.nextInt(100) + 1);
- try {
- mockRandomNumberGenerator.nextInt(100);
- throw new ExportException("Expected AssertionError when MockRandomSingleValue instance is used more than once.");
- } catch (final AssertionError ex) {
- assertEquals("'Random' value must be in the range of 0 <= value < bound (of 100). The value -1 is outside this range in class " + MockRandomSingleValue.class.getName() + ".",
- ex.getMessage());
- }
- }
- // Restored to initial (production) state by ".close()" call at the end of the "try (with resource)" block, above.
- assertSame("Initial Random generator restored on 'close();", Random.class, _random.getClass());
- }
- @Test
- public void testD1() {
- assertEquals("1st;", 0, _random.nextInt(1));
- assertEquals("2nd;", 0, _random.nextInt(1));
- assertEquals("3rd;", 0, _random.nextInt(1));
- assertEquals("4th;", 0, _random.nextInt(1));
- assertEquals("5th;", 0, _random.nextInt(1));
- }
- @Test
- public void test2toN() {
- _lowestConfidence = 1.0;
- var increment = 1;
- for (var bound = 2; bound <= 200; bound += increment) {
- if (bound >= 10) {
- increment = 5;
- }
- final var firstAndLastToFind = new TreeSet<Integer>();
- firstAndLastToFind.add(0);
- firstAndLastToFind.add(bound - 1);
- verifyThatBoundryValueAreFound(bound, firstAndLastToFind);
- }
- System.out.println("Lowest Confidence Level = " + _lowestConfidence);
- }
- @Test
- public void testNegativeOneIsOutOfBounds() {
- final var bound = 5;
- final var firstAndLastToFind = new TreeSet<Integer>();
- firstAndLastToFind.add(-1);
- try {
- verifyThatBoundryValueAreFound(bound, firstAndLastToFind);
- throw new ExpectedException("Expected AssertionFailedError to be thrown by the code above.");
- } catch (final AssertionError ex) {
- final var exceptionMessage = ex.getMessage();
- final var expectedMessagePrefix = "Unable to find value(s) [-1] after ";
- final var expectedMessageSuffix = " tries." + NL
- + " Values found = [0, 1, 2, 3, 4]" + NL
- + " Giving up because the count of unique values found (5) is (at least) equal to the bound value of 5.";
- assertTrue("Expected message" + NL
- + " <" + exceptionMessage + ">" + NL
- + " to start with" + NL
- + " <" + expectedMessagePrefix + ">.",
- exceptionMessage.startsWith(expectedMessagePrefix));
- assertTrue("Expected message" + NL
- + " <" + exceptionMessage + ">" + NL
- + " to end with" + NL
- + " <" + expectedMessageSuffix + ">.",
- exceptionMessage.endsWith(expectedMessageSuffix));
- }
- }
- @Test
- public void testValueEqualsBoundNeverHappens() {
- final var bound = 20;
- final var firstAndLastToFind = new TreeSet<Integer>();
- firstAndLastToFind.add(bound);
- try {
- verifyThatBoundryValueAreFound(bound, firstAndLastToFind);
- throw new ExpectedException("Expected AssertionFailedError to be thrown by the code above.");
- } catch (final AssertionError ex) {
- final var exceptionMessage = ex.getMessage();
- final var expectedMessagePrefix = "Unable to find value(s) [20] after ";
- final var expectedMessageSuffix = " tries." + NL
- + " Values found = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]" + NL
- + " Giving up because the count of unique values found (20) is (at least) equal to the bound value of 20.";
- assertTrue("Expected message" + NL
- + " <" + exceptionMessage + ">" + NL
- + " to start with" + NL
- + " <" + expectedMessagePrefix + ">.",
- exceptionMessage.startsWith(expectedMessagePrefix));
- assertTrue("Expected message" + NL
- + " <" + exceptionMessage + ">" + NL
- + " to end with" + NL
- + " <" + expectedMessageSuffix + ">.",
- exceptionMessage.endsWith(expectedMessageSuffix));
- }
- }
- private void verifyThatBoundryValueAreFound(final int bound, final TreeSet<Integer> firstAndLastToFind) {
- final var valuesFound = new TreeSet<Integer>();
- int numberOfTries = 0;
- double oddsOfGettingThisFarWithNoMatch = 1.0; // (100% = always gets here!)
- while (oddsOfGettingThisFarWithNoMatch > ODDS_OF_FALSE_TEST_FAILURE) {
- final var randomValue = _random.nextInt(bound);
- ++numberOfTries;
- final double oddsOfNotGettingAMatchThisTime = ((double) (bound - firstAndLastToFind.size())) / bound;
- oddsOfGettingThisFarWithNoMatch *= oddsOfNotGettingAMatchThisTime;
- if (oddsOfGettingThisFarWithNoMatch > 0.0 && oddsOfGettingThisFarWithNoMatch < _lowestConfidence) {
- _lowestConfidence = oddsOfGettingThisFarWithNoMatch;
- }
- final var isAUniqueNewNumber = valuesFound.add(randomValue);
- if (firstAndLastToFind.remove(randomValue)) { // true if we did find and remove a value
- if (firstAndLastToFind.isEmpty()) {
- break; // Successfully hit first and last expected value. Test is successful.
- } else {
- oddsOfGettingThisFarWithNoMatch = 1.0; // Reset = odds of finding 2nd match.
- }
- }
- if (isAUniqueNewNumber) {
- oddsOfGettingThisFarWithNoMatch = 1.0; // Restore optimisim, as long as we're seeing new numbers.
- final var numberOfValuesFound = valuesFound.size();
- if (numberOfValuesFound >= bound) {
- fail("Unable to find value(s) " + firstAndLastToFind.toString() + " after " + numberOfTries + " tries."
- + NL + " Values found = " + valuesFound.toString()
- + NL + " Giving up because the count of unique values found (" + numberOfValuesFound
- + ") is (at least) equal to the bound value of " + bound + ".");
- }
- }
- }
- // Failure case: [NEVER happens when "break;" is taken from loop, above.]
- if (!firstAndLastToFind.isEmpty()) {
- fail("Unable to find value(s) " + firstAndLastToFind.toString() + " after " + numberOfTries + " tries."
- + NL + " Values found = " + valuesFound.toString()
- + NL + " [There's a small chance that this test failure may be in error, as it is testing randomness!]");
- }
- }
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- //
- // File Name: tests/ExpectedException.java
- //
- package p20230510_TestConsoleIO.tests;
- class ExpectedException extends Error {
- public ExpectedException(final String message) {
- super(message);
- }
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- //
- // File Name: library/RandomSource.java
- //
- package p20230510_TestConsoleIO.library;
- import java.util.Random;
- abstract public class RandomSource {
- private RandomSource() {
- throw new IllegalStateException("Not expecting to create instances of this class.");
- }
- private static RandomCreatorInterface _creator = () -> {
- return new Random();
- };
- public static Random create() {
- return _creator.create();
- }
- public static RandomCreatorInterface swapRandomConstructor(final RandomCreatorInterface newRandomCreator) {
- final var oldRandomCreator = _creator;
- _creator = newRandomCreator;
- return oldRandomCreator;
- }
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- //
- // File Name: library/RandomCreatorInterface.java
- //
- package p20230510_TestConsoleIO.library;
- import java.util.Random;
- public interface RandomCreatorInterface {
- Random create();
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- //
- // File Name: library/AbstractMockRandomSwapper.java
- //
- package p20230510_TestConsoleIO.library;
- import java.io.Closeable;
- import java.io.IOException;
- import java.util.Random;
- import static org.junit.Assert.fail;
- abstract public class AbstractMockRandomSwapper extends Random implements Closeable {
- private final RandomCreatorInterface _oldRandomConstructor;
- protected AbstractMockRandomSwapper() {
- _oldRandomConstructor = RandomSource.swapRandomConstructor(() -> {
- return this;
- });
- }
- @Override
- public void close() throws IOException {
- final var randomConstructorSwappedOut = RandomSource.swapRandomConstructor(_oldRandomConstructor);
- }
- @Override
- final public int nextInt(final int bound) {
- final var returnValue = nextIntImpl(bound);
- if (returnValue < 0 || returnValue >= bound) {
- fail("'Random' value must be in the range of 0 <= value < bound (of " + bound + ")."
- + " The value " + returnValue + " is outside this range in class " + this.getClass().getName() + ".");
- }
- return returnValue;
- }
- abstract public int nextIntImpl(final int bound);
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- //
- // File Name: library/MockRandomSingleValue.java
- //
- package p20230510_TestConsoleIO.library;
- public class MockRandomSingleValue extends AbstractMockRandomSwapper {
- private int _targetValueToGuess;
- public MockRandomSingleValue(final int targetValueToGuess) {
- _targetValueToGuess = targetValueToGuess;
- }
- @Override
- public int nextIntImpl(int bound) {
- final var returnValue = _targetValueToGuess - 1;
- _targetValueToGuess = 0; // invalid value -- for any subsequent calls
- return returnValue;
- }
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- //
- // File Name: library/SystemInputOutputTester.java
- //
- package p20230510_TestConsoleIO.library;
- import java.io.PrintStream;
- import java.util.ArrayList;
- import java.util.Iterator;
- import java.util.List;
- public class SystemInputOutputTester {
- private final CallbackThrowsException _callback;
- private final List<ExpectedInputOutputStepBase> _expectedInputOutputSteps = new ArrayList<ExpectedInputOutputStepBase>();
- private Iterator<ExpectedInputOutputStepBase> _expectedInputOutputStepIterator = null;
- private final MockInputStream _mockInputStream = new MockInputStream(this);
- private final MockStandardOutputStream _mockStandardOutputStream = new MockStandardOutputStream();
- //protected final MockStandardOutputStream _mockStandardErrorStream = new MockStandardOutputStream(this);
- public SystemInputOutputTester(final CallbackThrowsException callback) {
- _callback = callback;
- }
- public static SystemInputOutputTester whenCallingThisMethod(final CallbackThrowsException callback) {
- return new SystemInputOutputTester(callback);
- }
- public SystemInputOutputTester assertTextOutput(final String expectedOutputMessage) {
- _expectedInputOutputSteps.add(new ExpectedTextOutputStep(_mockStandardOutputStream, expectedOutputMessage));
- return this;
- }
- public SystemInputOutputTester provideLineInput(final String lineOfInputText) {
- _expectedInputOutputSteps.add(new ProvideLineInputStep(lineOfInputText));
- return this;
- }
- public void assertThatTheMethodReturnsHere() {
- final var oldInput = System.in;
- final var oldOutput = System.out;
- //final var oldError = System.err;
- try {
- System.setIn(_mockInputStream);
- System.setOut(new PrintStream(_mockStandardOutputStream));
- //System.setErr(new PrintStream(_mockStandardErrorStream));
- _expectedInputOutputStepIterator = _expectedInputOutputSteps.iterator();
- try {
- _callback.callback();
- } catch (final RuntimeException ex) {
- throw ex;
- } catch (final Exception ex) {
- throw new RuntimeException("Nested Exception " + ex.getClass().getName() + ": " + ex.getMessage(), ex);
- }
- System.out.flush();
- System.err.flush();
- while (_expectedInputOutputStepIterator.hasNext()) {
- final var step = _expectedInputOutputStepIterator.next();
- final var isAfterExit = true;
- final var didProvideNewUserInput = step.validate(this);
- if (didProvideNewUserInput) {
- step.throwException("Program has exited, but the tests say that we should provide further input.");
- }
- }
- } finally {
- System.setIn(oldInput);
- System.setOut(oldOutput);
- //System.setErr(oldError);
- }
- }
- public void validateToAndIncludingNextInput() {
- System.out.flush();
- System.err.flush();
- while (_expectedInputOutputStepIterator.hasNext()) {
- final var step = _expectedInputOutputStepIterator.next();
- final var isAfterExit = false;
- final var didProvideNewUserInput = step.validate(this);
- if (didProvideNewUserInput) {
- break;
- }
- }
- }
- protected void provideLineOfInput(final String lineOfInputText) {
- _mockInputStream.provideLineOfInput(lineOfInputText);
- }
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- //
- // File Name: library/CallbackThrowsException.java
- //
- package p20230510_TestConsoleIO.library;
- public interface CallbackThrowsException {
- public void callback() throws Exception;
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- //
- // File Name: library/MockStandardOutputStream.java
- //
- package p20230510_TestConsoleIO.library;
- import org.jetbrains.annotations.NotNull;
- import java.io.IOException;
- import java.io.OutputStream;
- import static org.junit.Assert.assertEquals;
- public class MockStandardOutputStream extends OutputStream {
- private final StringBuilder _sequenceOfByteValues = new StringBuilder();
- private final StringBuilder _accumulatedOutput = new StringBuilder();
- @Override
- public void write(final int byteValue) throws IOException {
- _sequenceOfByteValues.append((char) byteValue);
- }
- public StringBuilder getUpdatedStringBuilder() {
- if (_sequenceOfByteValues.length() > 0) {
- final String stringValue = getStringValue();
- _accumulatedOutput.append(stringValue);
- }
- return _accumulatedOutput;
- }
- @NotNull
- private String getStringValue() {
- final var bytes = new byte[_sequenceOfByteValues.length()];
- final var charArray = _sequenceOfByteValues.toString().toCharArray();
- assertEquals(bytes.length, charArray.length);
- for (int idx = 0; idx < bytes.length; ++idx) {
- bytes[idx] = (byte) charArray[idx];
- }
- _sequenceOfByteValues.setLength(0);
- final var stringValue = new String(bytes);
- return stringValue;
- }
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- //
- // File Name: library/MockInputStream.java
- //
- package p20230510_TestConsoleIO.library;
- import java.io.IOException;
- import java.io.InputStream;
- public class MockInputStream extends InputStream {
- private final SystemInputOutputTester _systemInputOutputTester;
- private final StringBuilder _inputToProvide = new StringBuilder();
- public MockInputStream(final SystemInputOutputTester systemInputOutputTester) {
- _systemInputOutputTester = systemInputOutputTester;
- }
- @Override
- public int read() throws IOException {
- if (_inputToProvide.length() == 0) {
- _systemInputOutputTester.validateToAndIncludingNextInput();
- }
- if (_inputToProvide.length() == 0) {
- return -1; // = End Of File
- } else {
- final var firstCharacter = _inputToProvide.charAt(0);
- _inputToProvide.delete(0, 1);
- return firstCharacter;
- }
- }
- @Override
- public int read(final byte byteArray[], final int offset, final int length) throws IOException {
- if (_inputToProvide.length() == 0) {
- _systemInputOutputTester.validateToAndIncludingNextInput();
- }
- if (_inputToProvide.length() == 0) {
- return -1; // = End Of File
- } else {
- int charsRead = 0;
- for (; charsRead < length && _inputToProvide.length() > 0; ++charsRead) {
- final var firstCharacter = _inputToProvide.charAt(0);
- _inputToProvide.delete(0, 1);
- byteArray[offset + charsRead] = (byte) firstCharacter;
- }
- return charsRead;
- }
- }
- public void provideLineOfInput(final String lineOfInputText) {
- _inputToProvide.append(lineOfInputText);
- _inputToProvide.append('\n');
- }
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- //
- // File Name: library/ExpectedInputOutputStepBase.java
- //
- package p20230510_TestConsoleIO.library;
- import junit.framework.AssertionFailedError;
- abstract public class ExpectedInputOutputStepBase {
- protected final AssertionFailedError _locationOfAssertionInitialization = new AssertionFailedError(
- "Location of assertion initialization. [Look up in the Test's chained method calls.]");
- protected ExpectedInputOutputStepBase() {
- }
- public abstract boolean validate(final SystemInputOutputTester systemInputOutputTester);
- public void throwException(final String message) {
- final var ex = new AssertionFailedError(message + System.lineSeparator() + " " + this.toString());
- ex.initCause(_locationOfAssertionInitialization);
- throw ex;
- }
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- //
- // File Name: library/ExpectedTextOutputStep.java
- //
- package p20230510_TestConsoleIO.library;
- import junit.framework.AssertionFailedError;
- public class ExpectedTextOutputStep extends ExpectedInputOutputStepBase {
- private final MockStandardOutputStream _mockOutputStream;
- private final String _expectedOutputMessage;
- public ExpectedTextOutputStep(final MockStandardOutputStream mockOutputStream, final String expectedOutputMessage) {
- _mockOutputStream = mockOutputStream;
- _expectedOutputMessage = expectedOutputMessage;
- }
- @Override
- public boolean validate(final SystemInputOutputTester systemInputOutputTester) {
- final var stringBuilder = _mockOutputStream.getUpdatedStringBuilder();
- final var currentStringValue = stringBuilder.toString();
- final var foundAtIndex = currentStringValue.indexOf(_expectedOutputMessage);
- if (foundAtIndex >= 0) {
- // Remove the string value we just found:
- stringBuilder.delete(0, foundAtIndex + _expectedOutputMessage.length());
- // If followed immediately by a new line, remove that too:
- final var newlineCharacterSequence = System.lineSeparator();
- if (stringBuilder.toString().startsWith(newlineCharacterSequence)) {
- stringBuilder.delete(0, newlineCharacterSequence.length());
- }
- return false; // Did NOT provide new *input* data.
- } else {
- final var remainingOutputWithNewlinesVisible = currentStringValue
- .replace("\r\n", "[NL]")
- .replace("\r", "\\r")
- .replace("\n", "\\n");
- final var failEx = new AssertionFailedError("Failed to find <" + _expectedOutputMessage + "> in <" + remainingOutputWithNewlinesVisible + ">");
- failEx.initCause(super._locationOfAssertionInitialization);
- throw failEx;
- }
- }
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- //
- // File Name: library/ProvideLineInputStep.java
- //
- package p20230510_TestConsoleIO.library;
- public class ProvideLineInputStep extends ExpectedInputOutputStepBase {
- private final String _lineOfInputText;
- public ProvideLineInputStep(final String lineOfInputText) {
- _lineOfInputText = lineOfInputText;
- }
- @Override
- public boolean validate(final SystemInputOutputTester systemInputOutputTester) {
- systemInputOutputTester.provideLineOfInput(_lineOfInputText);
- return true; // *DID* provide new *input* data.
- }
- @Override
- public String toString() {
- return "ProvideLineInputStep('" + _lineOfInputText + "')";
- }
- }
Add Comment
Please, Sign In to add comment