JeffGrigg icon

Untitled

JeffGrigg | PRO | 05/12/23 01:54:49 PM UTC | 0 ⭐ | 1062 👁️ | Never ⏰ | []
Java 5 |

0 B

|

None

|

0 👍

/

0 👎

/*
GuessTheNumber.java = simple “guess the number” game
RandomNumberGenerator.java = wrapper for random number generation
 
GuessTheNumberTest.java = JUnit test for the game
MockRandomNumberGenerator.java = mock implementation for single-use random number
 
SystemInputOutputTester.java = Builder for console I/O testing
MockStandardOutputStream.java = Capture Standard Output (or Error)
MockInputStream.java = Provide input when needed. Calls back to SystemInputOutputTester to validate intervening output and to provide the input data.
ExpectedInputOutputStepBase.java = common base class for input/output expectations
ExpectedTextOutputStep.java = Expect that the given text output is written to Standard Output (or Error).
ProvideLineInputStep.java = Provide line of Console Input at that point in the expectations list.
*/
 
//
// File Name:   GuessTheNumber.java
//
 
import java.util.Scanner;
 
public class GuessTheNumber {
 
    protected static RandomNumberGenerator _randomNumberGenerator = new RandomNumberGenerator();
 
    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 int numberToGuess = _randomNumberGenerator.nextInt(100);
        final var scanner = new Scanner(System.in);
 
        while (true) {
            System.out.println("What is your guess?");
            final var userGuess = scanner.nextInt();
            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!");
                break;
            }
        }
    }
 
}
 
//
// File Name:   RandomNumberGenerator.java
//
 
import java.util.Random;
 
public class RandomNumberGenerator {
 
    private final Random _randomNumberGenerator = new Random();
 
    public int nextInt(final int bound) {
        return _randomNumberGenerator.nextInt(bound);
    }
 
}
 
//
// File Name:   GuessTheNumberTest.java
//
 
import org.junit.Test;
 
public class GuessTheNumberTest {
 
    @Test
    public void test() {
        final var oldRandomNumberGenerator = GuessTheNumber._randomNumberGenerator;
        GuessTheNumber._randomNumberGenerator = new MockRandomNumberGenerator(33);
        try {
            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!")
                    //
                    .assertThatTheMethodReturnsHere();
        } finally {
            GuessTheNumber._randomNumberGenerator = oldRandomNumberGenerator;
        }
    }
 
}
 
//
// File Name:   MockRandomNumberGenerator.java
//
 
public class MockRandomNumberGenerator extends RandomNumberGenerator {
 
    private int _theFixedRandomNumberValue;
 
    public MockRandomNumberGenerator(final int theFixedRandomNumberValue) {
        _theFixedRandomNumberValue = theFixedRandomNumberValue;
    }
 
    @Override
    public int nextInt(final int bound) {
        final var returnValue = _theFixedRandomNumberValue;
        _theFixedRandomNumberValue = -1;    // Clear value, as it's only good for one use.
        return returnValue;
    }
 
}
 
//
// File Name:   SystemInputOutputTester.java
//
 
import junit.framework.AssertionFailedError;
 
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
 
public class SystemInputOutputTester {
 
    private final Runnable _callback;
    private final List<ExpectedInputOutputStepBase> _expectedInputOutputSteps = new ArrayList<ExpectedInputOutputStepBase>();
    private Iterator<ExpectedInputOutputStepBase> _expectedInputOutputStepIterator = null;
    protected final MockInputStream _mockInputStream = new MockInputStream(this);
    protected final MockStandardOutputStream _mockStandardOutputStream = new MockStandardOutputStream();
    //protected final MockStandardOutputStream _mockStandardErrorStream = new MockStandardOutputStream(this);
 
    public SystemInputOutputTester(final Runnable callback) {
        _callback = callback;
    }
 
    public static SystemInputOutputTester whenCallingThisMethod(final Runnable callback) {
        return new SystemInputOutputTester(callback);
    }
 
    public SystemInputOutputTester assertTextOutput(final String expectedOutputMessage) {
        _expectedInputOutputSteps.add(new ExpectedTextOutputStep(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();
 
            _callback.run();
 
            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) {
                    throw new AssertionFailedError("Program has exited, but the tests say that we should further input: <" + ((ProvideLineInputStep) step)._lineOfInputText + ">.");
                }
            }
 
        } 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;
            }
        }
    }
 
}
 
//
// File Name:   MockStandardOutputStream.java
//
 
import org.jetbrains.annotations.NotNull;
import org.junit.Assert;
 
import java.io.IOException;
import java.io.OutputStream;
 
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();
        Assert.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:   MockInputStream.java
//
 
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:   ExpectedInputOutputStepBase.java
//
 
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);
 
}
 
//
// File Name:   ExpectedTextOutputStep.java
//
 
import junit.framework.AssertionFailedError;
 
public class ExpectedTextOutputStep extends ExpectedInputOutputStepBase {
 
    private final String _expectedOutputMessage;
 
    public ExpectedTextOutputStep(final String expectedOutputMessage) {
        _expectedOutputMessage = expectedOutputMessage;
    }
 
    @Override
    public boolean validate(final SystemInputOutputTester systemInputOutputTester) {
        final var stringBuilder = systemInputOutputTester._mockStandardOutputStream.getUpdatedStringBuilder();
        final var currentStringValue = stringBuilder.toString();
        final var foundAtIndex = currentStringValue.indexOf(_expectedOutputMessage);
        if (foundAtIndex >= 0) {
            stringBuilder.delete(0, foundAtIndex + _expectedOutputMessage.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:   ProvideLineInputStep.java
//
 
public class ProvideLineInputStep extends ExpectedInputOutputStepBase {
 
    protected final String _lineOfInputText;
 
    public ProvideLineInputStep(final String lineOfInputText) {
        _lineOfInputText = lineOfInputText;
    }
 
    @Override
    public boolean validate(final SystemInputOutputTester systemInputOutputTester) {
        systemInputOutputTester._mockInputStream.provideLineOfInput(_lineOfInputText);
        return true;   // *DID* provide new *input* data.
    }
}
 

Comments

  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎