Jake B
Jake B

Reputation: 27

How do I simulate multiple inputs from a JUnit test case into my program?

I have written a program which looks like this:

import java.util.Scanner;
import java.util.ArrayList;

public class Triangles {

    public static void main(String[] args) {

        Scanner user_input = new Scanner(System.in);

        ArrayList<String> triangleLengths = new ArrayList<String>();

        for (int i=0; i < 3; i++) {

            System.out.print("Triangle length #" + i + ": ");
            triangleLengths.add(i,user_input.next());   
        }

        if (triangleLengths.get(0) == triangleLengths.get(1) && triangleLengths.get(1) == triangleLengths.get(2)) {

            System.out.println("This triangle is an equilateral triangle");

        } else if (triangleLengths.get(0) == triangleLengths.get(1) || triangleLengths.get(0) == triangleLengths.get(2) || triangleLengths.get(1) == triangleLengths.get(2)) {

            System.out.println("This triangle is an isosceles triangle");

        } else if (triangleLengths.get(0) != triangleLengths.get(1) && triangleLengths.get(1) != triangleLengths.get(2)) {

            System.out.println("This triangle is a scalene triangle");

        } else {

            System.out.println("The input does not make a triangle!");

        }


    }

}

I have been tasked with writing a JUnit test case to essentially try and 'break' my program through testing with various inputs. I can't for the life of me figure out how to do this as a total Java newbie - could anyone point me in the right direction?

Upvotes: 0

Views: 2496

Answers (3)

Dawood ibn Kareem
Dawood ibn Kareem

Reputation: 79807

The best thing for you to do is to redesign your code, so that

  • the triangle logic is in its own class,
  • the user input logic is in its own method,
  • the main method is absolutely minimal.

Then, you can unit test the triangle logic and the user input logic separately. And if main is small enough, you can get away without writing a unit test for it.

However, that's not what you asked. If you want to keep your code exactly as you've written it, you can test it by setting System.in and System.out to streams where you control the input and output.

Your test class might look something like this. This redirects System.in to read from a String that you specify in each test, and System.out to write to a stream whose content you can verify afterwards.

public class TrianglesTest {
    @Test
    public void identifiesEquilateralTriangle() {
        ByteArrayOutputStream output = setStreams("5 5 5 ");
        Triangles.main(new String[0]);
        assertEquals("This triangle is an equilateral triangle", output.toString().trim());
    }

    // Plus a whole lot more tests for other cases

    private ByteArrayOutputStream setStreams(String input) {
        System.setIn(new ByteArrayInputStream(input.getBytes()));
        ByteArrayOutputStream toReturn = new ByteArrayOutputStream();
        System.setOut(new PrintStream(toReturn));
        return toReturn;
    }
}

Upvotes: 0

Wisienkas
Wisienkas

Reputation: 1751

So I made a suggestion on how to solve it.

you want to make it so that you can test with different parameters automatically without needing to enter it manually so i isolated the triangles part as seen below.

EDIT: I redid the code somewhat

The normal run class src/main.java

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

public class Main {

    public static void main(String[] args) {
        Scanner user_input = new Scanner(System.in);
        List<String> triangleLengths = new ArrayList<>();

        for (int i = 0; i < 3; i++) {
            System.out.print("Triangle length #" + i + ": ");
            triangleLengths.add(i, user_input.next());
        }

        // Result output will be here
        Triangle subject = new Triangle(triangleLengths);
        if (subject.getTriangleType() == Triangle.Type.INVALID) {
            System.out.println("Triangle is invalid");
        } else {
            System.out.println("Triangle is: " + subject.getTriangleType());
        }
    }
}

The JUnit class test/TrianglesTest.java

import org.junit.Test;

import java.util.Arrays;
import java.util.List;

import static org.junit.Assert.assertEquals;

public class TrianglesTest {

    /**
     * Testing with String inputs (what you'd enter manually)
     */
    @Test
    public void testWithStrings() {
        List<String> triangleLengths = Arrays.asList("len1", "len2", "len3");

        Triangle subject = new Triangle(triangleLengths);
        // Example of checking if expected type
        assertEquals(Triangle.Type.ISOSCELES, subject.getTriangleType());
    }

    /**
     * Testing with numbers as what I'd expect the triangle to be made of
     *
     * Here you test with a triangle object
     *
     * Haven't tested what the 3 types is sorry :O
     */
    @Test
    public void testWithNumbersAsObject() {
        Triangle subject = new Triangle(4, 5.32, 7);
        assertEquals(Triangle.Type.SCALENE, subject);
    }

    /**
     * This piece you check the static method but have no object
     */
    @Test
    public void testWithNumbersStaticMethod() {
        assertEquals(Triangle.Type.ISOSCELES, Triangle.getTriangleType(3.4d, 4d, 1.111d));
    }
}

And lastly the actual code you wanted to test src/Triangles.java

import java.util.List;

/**
 * I created so you can both have an object of the triangle or make the check purely static
 * maybe you need an object type for later?
 */
public class Triangle {

    final double side0;
    final double side1;
    final double side2;

    public Triangle(List<String> triangleLengths) {
        this(Double.parseDouble(triangleLengths.get(0)),
                Double.parseDouble(triangleLengths.get(1)),
                Double.parseDouble(triangleLengths.get(2)));
    }

    public Triangle(double side0, double side1, double side2) {
        this.side0 = side0;
        this.side1 = side1;
        this.side2 = side2;
    }

    public Triangle.Type getTriangleType() {
        return Triangle.getTriangleType(side0, side1, side2);
    }

    public static Triangle.Type getTriangleType(double side0, double side1, double side2) {
        if (isEquilateral(side0, side1, side2)) {
            return Type.EQUILATERAL;
        } else if (isIsosceles(side0, side1, side2)) {
            return Type.ISOSCELES;
        } else if (isScalene(side0, side1, side2)) {
            return Type.SCALENE;
        } else {
            return Type.INVALID;
        }
    }

    private static boolean isScalene(double side0, double side1, double side2) {
        return side0 != side1 && side1 != side2;
    }

    private static boolean isIsosceles(double side0, double side1, double side2) {
        return side0 == side1 || side0 == side2 || side1 == side2;
    }

    private static boolean isEquilateral(double side0, double side1, double side2) {
        return side0 == side1 && side1 == side2;
    }

    public enum Type {
        EQUILATERAL,
        ISOSCELES,
        SCALENE,
        INVALID
    }
}

Hope this helps.

Note that I return the answer from the Triangles class instead of writing it out immidiately. And only in the manual run I write it out from the main method.

Upvotes: 1

GhostCat
GhostCat

Reputation: 140417

The answer from Wisienkas is right on spot; but I think a bit more of conceptual background would be helpful.

You see, when you start coding, "user input" from stdin, using a Scanner, that's like "the big thing". You fetch some value, do some processing on that; and print some results.

The problem is: that is not how "real world" works; and therefore you ran into that problem that your code wasn't testable. The key thing to understand here is: you need just a little bit of abstraction, in order to make things better.

Those are:

  1. your "processing" code goes into its own classes and methods. And ideally, those methods except the data to work on as argument when invoked.
  2. your "processing" code should return its results. When you just print to System.out it is almost impossible to verify what some code is doing.

Upvotes: 0

Related Questions