नकली उपयोगकर्ता इनपुट के साथ JUnit परीक्षण


82

मैं एक विधि के लिए कुछ JUnit परीक्षण बनाने की कोशिश कर रहा हूं जिसमें उपयोगकर्ता इनपुट की आवश्यकता होती है। परीक्षण के तहत विधि कुछ इस प्रकार है:

public static int testUserInput() {
    Scanner keyboard = new Scanner(System.in);
    System.out.println("Give a number between 1 and 10");
    int input = keyboard.nextInt();

    while (input < 1 || input > 10) {
        System.out.println("Wrong number, try again.");
        input = keyboard.nextInt();
    }

    return input;
}

क्या मेरे या मेरे बजाय किसी अन्य व्यक्ति को प्रोग्राम को स्वचालित रूप से पास करने का एक संभावित तरीका है जो मैन्युअल रूप से JUnit परीक्षण विधि में कर रहा है? उपयोगकर्ता इनपुट अनुकरण?

अग्रिम में धन्यवाद।


7
क्या आप परीक्षण कर रहे हैं, बिल्कुल? चित्रान्वीक्षक? एक परीक्षण विधि आमतौर पर उपयोगी होने के लिए कुछ जोर देना चाहिए।
मार्कस

2
आपको जावा के स्कैनर वर्ग का परीक्षण नहीं करना चाहिए। आप मैन्युअल रूप से अपना इनपुट सेट कर सकते हैं और बस अपने तर्क का परीक्षण कर सकते हैं। int input = -1 या 5 या 11 आपके तर्क को कवर करेगा
blong824

3
छह साल बाद ... और यह अभी भी एक अच्छा सवाल है। कम से कम नहीं क्योंकि जब कोई ऐप विकसित करना शुरू करते हैं तो आप आमतौर पर JavaFX की सभी घंटियाँ और सीटी नहीं बजा सकते हैं, लेकिन इसके बजाय बस थोड़ी बहुत बुनियादी बातचीत के लिए विनम्र कमांड लाइन का उपयोग करना शुरू करें। शर्म की बात है कि JUnit इसे काफी आसान नहीं बनाता है। मेरे लिए उमर एल्शाल का उत्तर बहुत अच्छा है, जिसमें न्यूनतम "कंट्रोल्ड" या "विकृत" ऐप कोडिंग शामिल है ...
माइक कृंतक

जवाबों:


105

System.setIn (InputStream) में कॉल करके आप System.in को अपनी स्ट्रीम से बदल सकते हैं । InputStream एक बाइट सरणी हो सकता है:

InputStream sysInBackup = System.in; // backup System.in to restore it later
ByteArrayInputStream in = new ByteArrayInputStream("My string".getBytes());
System.setIn(in);

// do your thing

// optionally, reset System.in to its original
System.setIn(sysInBackup);

अलग-अलग दृष्टिकोण इस पद्धति को पैरामीटर के रूप में IN और OUT पास करके अधिक परीक्षण योग्य बना सकते हैं:

public static int testUserInput(InputStream in,PrintStream out) {
    Scanner keyboard = new Scanner(in);
    out.println("Give a number between 1 and 10");
    int input = keyboard.nextInt();

    while (input < 1 || input > 10) {
        out.println("Wrong number, try again.");
        input = keyboard.nextInt();
    }

    return input;
}

6
@KrzyH मैं इस बारे में कई इनपुट के साथ कैसे जाऊंगा? कहो कि // में आपकी बात मैं उपयोगकर्ता इनपुट के लिए तीन संकेत हैं। मैं उसके बारे में कैसे जाऊंगा?
स्टुपिड.फैट.कैट

1
@ Stupid.Fat.Cat मैंने समस्या के लिए दूसरा दृष्टिकोण जोड़ा है, और अधिक सुंदर और सुंदर
KrzyH

1
@KrzyH दूसरे दृष्टिकोण के लिए, आपको विधि परिभाषा में एक पैरामीटर के रूप में इनपुट स्ट्रीम और आउटपुट स्ट्रीम में पास करना होगा, जो कि मैं नहीं देख रहा हूं। क्या आप किसी भी बेहतर तरीके से जानते हैं?
चिराग

6
मैं प्रवेश कुंजी दबाने पर कैसे अनुकरण करूं? अभी, कार्यक्रम सिर्फ एक शॉट में सभी इनपुट को पढ़ता है, जो अच्छा नहीं है। मैंने कोशिश की, \nलेकिन इससे कोई फर्क नहीं
पड़ा

3
@CodyBugstein ByteArrayInputStream in = new ByteArrayInputStream(("1" + System.lineSeparator() + "2").getBytes());विभिन्न keyboard.nextLine()कॉल के लिए कई इनपुट प्राप्त करने के लिए उपयोग करें ।
को क्ले

18

अपने कोड को चलाने के लिए, आपको सिस्टम इनपुट / आउटपुट फ़ंक्शन के लिए एक आवरण बनाना चाहिए। आप यह निर्भरता इंजेक्शन का उपयोग करके कर सकते हैं, हमें एक वर्ग दे सकते हैं जो नए पूर्णांक के लिए पूछ सकते हैं:

public static class IntegerAsker {
    private final Scanner scanner;
    private final PrintStream out;

    public IntegerAsker(InputStream in, PrintStream out) {
        scanner = new Scanner(in);
        this.out = out;
    }

    public int ask(String message) {
        out.println(message);
        return scanner.nextInt();
    }
}

तब आप अपने कार्य के लिए परीक्षण बना सकते हैं, मॉक फ्रेमवर्क का उपयोग करके (मैं मॉकिटो का उपयोग करता हूं):

@Test
public void getsIntegerWhenWithinBoundsOfOneToTen() throws Exception {
    IntegerAsker asker = mock(IntegerAsker.class);
    when(asker.ask(anyString())).thenReturn(3);

    assertEquals(getBoundIntegerFromUser(asker), 3);
}

@Test
public void asksForNewIntegerWhenOutsideBoundsOfOneToTen() throws Exception {
    IntegerAsker asker = mock(IntegerAsker.class);
    when(asker.ask("Give a number between 1 and 10")).thenReturn(99);
    when(asker.ask("Wrong number, try again.")).thenReturn(3);

    getBoundIntegerFromUser(asker);

    verify(asker).ask("Wrong number, try again.");
}

फिर अपने फ़ंक्शन को लिखें जो परीक्षण पास करता है। फ़ंक्शन बहुत क्लीनर है क्योंकि आप पूछ / पूर्णांक दोहराव प्राप्त कर सकते हैं और वास्तविक सिस्टम कॉल को एनकैप्सुलेट किया जाता है।

public static void main(String[] args) {
    getBoundIntegerFromUser(new IntegerAsker(System.in, System.out));
}

public static int getBoundIntegerFromUser(IntegerAsker asker) {
    int input = asker.ask("Give a number between 1 and 10");
    while (input < 1 || input > 10)
        input = asker.ask("Wrong number, try again.");
    return input;
}

यह आपके छोटे से उदाहरण के लिए ओवरकिल की तरह लग सकता है, लेकिन यदि आप एक बड़ा एप्लिकेशन बना रहे हैं, तो यह इस तरह से विकसित हो सकता है कि आप जल्दी से भुगतान कर सकें।


6

इसी तरह के कोड का परीक्षण करने का एक सामान्य तरीका यह होगा कि इस StackOverflow उत्तर के समान स्कैनर और PrintWriter में एक विधि निकाली जाए , और यह परीक्षण किया जाए:

public void processUserInput() {
  processUserInput(new Scanner(System.in), System.out);
}

/** For testing. Package-private if possible. */
public void processUserInput(Scanner scanner, PrintWriter output) {
  output.println("Give a number between 1 and 10");
  int input = scanner.nextInt();

  while (input < 1 || input > 10) {
    output.println("Wrong number, try again.");
    input = scanner.nextInt();
  }

  return input;
}

ध्यान दें कि आप अंत तक अपना आउटपुट नहीं पढ़ पाएंगे, और आपको अपने सभी इनपुट को सामने रखना होगा:

@Test
public void shouldProcessUserInput() {
  StringWriter output = new StringWriter();
  String input = "11\n"       // "Wrong number, try again."
               + "10\n";

  assertEquals(10, systemUnderTest.processUserInput(
      new Scanner(input), new PrintWriter(output)));

  assertThat(output.toString(), contains("Wrong number, try again.")););
}

बेशक, एक अधिभार विधि बनाने के बजाय, आप परीक्षण के तहत "स्कैनर" और "आउटपुट" को अपने सिस्टम के परस्पर क्षेत्रों के रूप में रख सकते हैं। मैं जितना संभव हो उतने वर्गों को स्टेटलेस रखना पसंद करता हूं, लेकिन यह बहुत बड़ी रियायत नहीं है अगर यह आपके या आपके सहकर्मियों / प्रशिक्षक के लिए मायने रखता है।

आप अपने परीक्षण कोड को उसी जावा पैकेज में परीक्षण के तहत कोड के रूप में भी चुन सकते हैं (भले ही यह एक अलग स्रोत फ़ोल्डर में हो), जो आपको पैकेज-प्राइवेट होने के लिए दो पैरामीटर अधिभार की दृश्यता को आराम करने की अनुमति देता है।


आप परीक्षण के लिए थे, विधि processUserInput () इनवोक विधि (में, बाहर) और नहीं processUserInput (में, बाहर)?
नाडज

@ NZZ बेशक; मैंने एक सामान्य उदाहरण के साथ शुरुआत की और अपूर्णता से इसे प्रश्न के विशिष्ट होने के लिए वापस बदल दिया।
जेफ बोमन

4

मैं एक सरल तरीका खोजने में कामयाब रहा। हालाँकि, आपको @Stefan Birkner द्वारा बाहरी लाइब्रेरी System.rules का उपयोग करना होगा

मैंने बस वहां दिए गए उदाहरण को लिया, मुझे लगता है कि यह अधिक सरल नहीं हो सकता है:

import java.util.Scanner;

public class Summarize {
  public static int sumOfNumbersFromSystemIn() {
    Scanner scanner = new Scanner(System.in);
    int firstSummand = scanner.nextInt();
    int secondSummand = scanner.nextInt();
    return firstSummand + secondSummand;
  }
}

परीक्षा

import static org.junit.Assert.*;
import static org.junit.contrib.java.lang.system.TextFromStandardInputStream.*;

import org.junit.Rule;
import org.junit.Test;
import org.junit.contrib.java.lang.system.TextFromStandardInputStream;

public class SummarizeTest {
  @Rule
  public final TextFromStandardInputStream systemInMock
    = emptyStandardInputStream();

  @Test
  public void summarizesTwoNumbers() {
    systemInMock.provideLines("1", "2");
    assertEquals(3, Summarize.sumOfNumbersFromSystemIn());
  }
}

हालांकि मेरे मामले में समस्या मेरे दूसरे इनपुट में रिक्त स्थान है और यह पूरे इनपुट स्ट्रीम को अशक्त बनाता है!


यह बहुत अच्छी तरह से काम करता है ... मुझे नहीं पता कि "इनपुट स्ट्रीम नल" से आपका क्या मतलब है। क्या अच्छा है कि inputEntry = scanner.nextLine();जब उपयोग किया जाता है, System.inतो हमेशा उपयोगकर्ता की प्रतीक्षा करेगा (और खाली लाइन को एक खाली के रूप में स्वीकार करेगा String) ... जब आप systemInMock.provideLines()इस का उपयोग करते हुए लाइनों की आपूर्ति NoSuchElementExceptionकरते हैं तो लाइनें बाहर फेंकने पर फेंक देंगे । यह वास्तव में परीक्षण के लिए पूरा करने के लिए ऐप कोड को बहुत अधिक "विकृत" नहीं करना आसान बनाता है।
माइक कृंतक

मुझे याद नहीं है कि वास्तव में क्या समस्या थी, लेकिन आप सही हैं, मैंने अपने कोड को फिर से जांचा और देखा कि मैंने इसे दो तरीकों से तय किया है: 1. systemInMock.provideLines("1", "2");String data2 = "1 2"; System.setIn(new ByteArrayInputStream(data2.getBytes()));
systemInMock

2

आप उस तर्क को निकालने से शुरू कर सकते हैं जो कीबोर्ड से संख्या को अपनी विधि में पुनर्प्राप्त करता है। फिर आप कीबोर्ड के बारे में चिंता किए बिना सत्यापन तर्क का परीक्षण कर सकते हैं। कीबोर्ड.नेक्स्टइंट () कॉल का परीक्षण करने के लिए आप एक मॉक ऑब्जेक्ट का उपयोग करने पर विचार कर सकते हैं।


2
ध्यान दें, आप स्कैनर ऑब्जेक्ट्स का मजाक नहीं उड़ा सकते (वे अंतिम हैं)।
होलीपोलोई

2

मैंने स्टड से पढ़ने के बारे में समस्या को सांत्वना देने के लिए तय किया है ...

मेरी समस्या यह थी कि मैं एक निश्चित वस्तु बनाने के लिए कंसोल टेस्ट ज्यूनिट में लिखने की कोशिश करूँगा ...

समस्या यह है कि आप सभी कहते हैं: मैं कैसे स्टड में JUnit परीक्षण से लिख सकता हूँ?

तब कॉलेज में मुझे आपके द्वारा पुनर्निर्देशन के बारे में पता चलता है जैसे आप कहते हैं। System.setIn (InputStream) स्टड फाइलसेटर को बदल देता है और आप तब लिख सकते हैं ...

लेकिन इसे ठीक करने के लिए एक और समस्या है ... आपके नए इनपुटस्ट्रीम से पढ़ने के लिए JUnit परीक्षण ब्लॉक प्रतीक्षा कर रहा है, इसलिए आपको InputStream से पढ़ने के लिए एक थ्रेड बनाने की आवश्यकता है और JUnit टेस्ट थ्रेड से नए स्टड में लिखें ... सबसे पहले आपको करना होगा स्टडिन में लिखें क्योंकि यदि आप स्टड से पढ़ने के लिए थ्रेड को बाद में लिखते हैं तो आपको रेस की स्थितियां होने की संभावना है ... आप पढ़ने से पहले InputStream में लिख सकते हैं या लिखने से पहले InputStream से पढ़ सकते हैं ...

यह मेरा कोड है, मेरा अंग्रेजी कौशल खराब है मुझे उम्मीद है कि आप सभी समस्या और समाधान को ज्यूनिट परीक्षा से स्टैड में लिखने के लिए समझ सकते हैं।

private void readFromConsole(String data) throws InterruptedException {
    System.setIn(new ByteArrayInputStream(data.getBytes()));

    Thread rC = new Thread() {
        @Override
        public void run() {
            study = new Study();
            study.read(System.in);
        }
    };
    rC.start();
    rC.join();      
}

1

मैंने java.io.Console के समान तरीकों को परिभाषित करने वाला इंटरफ़ेस बनाने में मददगार पाया है और फिर System.out को पढ़ने या लिखने के लिए इसका उपयोग करता हूं। वास्तविक कार्यान्वयन System.console () में होगा, जबकि आपका JUnit संस्करण डिब्बाबंद इनपुट और अपेक्षित प्रतिक्रियाओं के साथ एक नकली वस्तु हो सकता है।

उदाहरण के लिए, आप एक MockConsole का निर्माण करेंगे, जिसमें उपयोगकर्ता का डिब्बाबंद इनपुट शामिल था। प्रत्येक बार रीडलाइन को सूची में लागू किए जाने पर मॉक कार्यान्वयन एक इनपुट स्ट्रिंग पॉप करेगा। यह प्रतिक्रियाओं की सूची में लिखे गए सभी आउटपुट को इकट्ठा करेगा। परीक्षण के अंत में, यदि सब कुछ ठीक हो जाता है, तो आपका सारा इनपुट पढ़ा जा सकता है और आप आउटपुट पर दावा कर सकते हैं।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.