its my first time doing unit tests/integration tests and I have a question. So I started doing unit tests for my code, but I have a method, which is actually the logic of the whole application, where multiple methods are called, and user input is required. How can I test that method? Here is the method:
public async Task RunAsync()
{
var watch = System.Diagnostics.Stopwatch.StartNew();
var playAgain = 'y';
do
{
var attempts = 1;
var foundNumber = false;
while (attempts < 10 && foundNumber == false)
{
try
{
var inputNumber = int.Parse(GetInput());
if (inputNumber == _randomNumber)
{
foundNumber = true;
OnSuccesfulGuess(watch, attempts);
}
else if (attempts < 10)
{
OnWrongGuessWithinAttempts(inputNumber);
}
else
{
Console.WriteLine("Oops, maybe next time.");
}
}
catch (Exception ex)
{
Console.WriteLine("Please enter a number");
}
attempts++;
}
playAgain = PlayAgain(playAgain);
_randomNumber = await GetRandomNumber(1, 100);
Log.Information("User wants to play again");
}
while (playAgain == 'y' || playAgain == 'Y');
}
This is the method where i run in my Program class in order to start the application.
Your code is essentially untestable.
The method does too much work. It should be splitted into several smaller ones that can be tested separately.
You should get rid of static methods. Because you can't get them fake.
Getting data over the network (I see using WebSocket), as well as from the database or file system, should be brought out. You should pass ready-made data to this method.
Here is the modified code, broken down into small methods. Logging and events are removed from the code so as not to complicate the explanation.
public class App
{
private readonly Random _random = new Random();
private Task<int> GetRandomNumber(int min, int max)
{
return Task.FromResult(_random.Next(min, max));
}
internal int GetInput()
{
Console.WriteLine("Please guess a number between 1 and 100");
int value;
while (true)
{
string input = Console.ReadLine();
bool result = int.TryParse(input, out value);
if (!result)
Console.WriteLine("Not a number");
else if (value < 1 || value > 100)
Console.WriteLine("Must be between 1 and 100");
else
break;
}
return value;
}
internal bool PlayAgain()
{
Console.WriteLine("Do you want to play again?");
string input = Console.ReadLine();
return input == "Y" || input == "y";
}
internal void Guessing(int randomNumber)
{
int attempts = 1;
while (attempts < 10)
{
var inputNumber = GetInput();
// logging
if (inputNumber == randomNumber)
{
// OnSuccesfulGuess
return;
}
else
{
// OnWrongGuessWithinAttempts
}
attempts++;
}
Console.WriteLine("Oops, maybe next time.");
// logging
}
public async Task RunAsync()
{
do
{
int randomNumber = await GetRandomNumber(1, 100);
Guessing(randomNumber);
}
while (PlayAgain());
}
}
Now we have the ability to test individual methods.
I use MSTest.
[DataTestMethod]
[DataRow("Y")]
[DataRow("y")]
public void PlayAgain_InputY_ReturnsTrue(string value)
{
using (var reader = new StringReader(value))
{
Console.SetIn(reader);
var app = new App();
bool result = app.PlayAgain();
Assert.IsTrue(result);
}
}
[DataTestMethod]
[DataRow("N")]
[DataRow("boo")]
[DataRow("")]
public void PlayAgain_InputNotY_ReturnsFalse(string value)
{
using (var reader = new StringReader(value))
{
Console.SetIn(reader);
var app = new App();
bool result = app.PlayAgain();
Assert.IsFalse(result);
}
}
We do the same with the other methods.
Here are the tests for the GetInput method.
Since there is a loop inside that runs indefinitely when incorrect values are entered, we must interrupt it by entering the correct value. This is done by passing two values via a line feed: "0\n50". Entering an incorrect value is a test of the output string, then interrupting the loop with the correct value.
[DataTestMethod]
[DataRow("1")]
[DataRow("50")]
[DataRow("100")]
public void GetInput_InputCorrectString_ReturnsNumber(string value)
{
using (var reader = new StringReader(value))
{
Console.SetIn(reader);
var app = new App();
int actual = app.GetInput();
int expected = int.Parse(value);
Assert.AreEqual(expected, actual);
}
}
[DataTestMethod]
[DataRow("0\n50")]
[DataRow("101\n50")]
public void GetInput_InputSmallerOrGreaterValue_WritesMessage(string value)
{
using (var reader = new StringReader(value))
using (var writer = new StringWriter())
{
Console.SetIn(reader);
Console.SetOut(writer);
var app = new App();
_ = app.GetInput();
string actualMessage = writer.ToString();
string expectedMessage = "Must be between 1 and 100";
Assert.IsTrue(actualMessage.Contains(expectedMessage));
}
}
[DataTestMethod]
[DataRow("x\n50")]
[DataRow("qwerty\n50")]
public void GetInput_InputNotNumber_WritesMessage(string value)
{
using (var reader = new StringReader(value))
using (var writer = new StringWriter())
{
Console.SetIn(reader);
Console.SetOut(writer);
var app = new App();
_ = app.GetInput();
string actualMessage = writer.ToString();
string expectedMessage = "Not a number";
Assert.IsTrue(actualMessage.Contains(expectedMessage));
}
}
Normally the unit testing methods are made regarding the different results that may return. You can create an interface to handle this method and communicate giving values depending of the output expected (Mocking). Check this post maybe would help!:
How do I apply unit testing to C# function which requires user input dynamically?
Related
So basically the program does what it is supposed to do. The two patterns have to be split with an open line. But I did something wrong now the input wont go in smoothly. I have to press enter a couple of times. The Expected input is:
...........*........
....*.....*.........
.........*..*...*...
*..*..*......***....
..*.....*...........
.*..................
.......*.........*.*
....................
.....*............*.
..........
.*.**.*...
*....*.*.*
..........
..*.....*.
and Output should be
...................*
.................**.
..............***...
........******......
......**............
.....*..............
..***...............
....................
**..................
..........
......****
..****....
..........
**........
But mine looks like this
...........*........
....*.....*.........
.........*..*...*...
*..*..*......***....
..*.....*...........
.*..................
.......*.........*.*
....................
.....*............*.
...................*
.................**.
..............***...
........******......
......**............
.....*..............
..***...............
....................
**..................
..........
.*.**.*...
*....*.*.*
..........
..*.....*.
......****
..****....
..........
**........
I have to press enter a couple of times to get this.
My Code looks like this
using System;
using System.Runtime.CompilerServices;
using Microsoft.VisualBasic;
using System.Collections.Generic;
using System.Linq;
public class Program
{
static void defragDisk(Queue<string> diskLine) // Method that will move all the Starsto the right
{
int lnLength = 0;
int iIndex = 0;
int iStars = 0;
int iTotalStars = 0;
while (diskLine.Count > 0)
{
var line = diskLine.Dequeue();
lnLength = line.Length;
iIndex = lnLength - 1 - iTotalStars;
iStars = line.Count(x => x == '*');
iTotalStars += iStars;
var rangeFrom = iIndex - iStars + 1;
var availableIndexes = Enumerable.Range(rangeFrom, iStars).ToDictionary(x => x);
for (int i = 0; i < lnLength; i++)
{
if (availableIndexes.ContainsKey(i))
Console.Write("*");
else
Console.Write(".");
}
Console.Write("\n");
}
}
static void populateQueue(Queue<string> diskLine) // I am using a queue as the sizes can vary without indication
{
bool bCompleted = false;
while (!bCompleted)
{
var line = Console.ReadLine();
if (line == "")
{
bCompleted = true;
break;
}
else
{
diskLine.Enqueue(line);
}
}
}
public static void Main()
{
bool bCompleted = false;
Queue<string> diskLine = new Queue<string>();
while (!bCompleted)
{
populateQueue(diskLine);
defragDisk(diskLine);
diskLine.Clear();
if (Console.ReadLine() == "")
{
bCompleted = true;
break;
}
}
}
}
This is a simple functionality I have in my chatbot, but I want to customize it a little bit.
Right now, when a user types !lurk, it returns one message.
I want to add multiple other messages, and for it to pick a random one each time a user uses the command.
I would also like to have a separate list of sentences for when they come BACK from lurking. If you notice in the code, there's a second set where it displays a different message when the user uses the command a second time. It would also be nice if I could get it to un-lurk the user whenever they type ANYTHING in chat, instead of them having to use the command again.
Here's the code from inside the bot:
using System;
public class CPHInline
{
public bool Execute()
{
int connectionID = 0;
int count;
int totalCount;
string messageOut;
string userName = args["user"].ToString();
bool lurker = CPH.GetUserVar<bool>(userName, "lurker", true);
count = CPH.GetGlobalVar<int>("lurkCount", true);
totalCount = CPH.GetGlobalVar<int>("totalLurkCount", true);
string inString = args["rawInput"].ToString();
string[] outputer = inString.Split(' ');
if(String.Equals(outputer[0].ToLower(), "")) {
if(lurker == true) {
count--;
CPH.SetUserVar(userName, "lurker", false, true);
messageOut = $"{userName} is no longer lurking! Current Lurkers are now: {count}.";
} else if(lurker == false) {
count++;
totalCount++;
CPH.SetUserVar(userName, "lurker", true, true);
messageOut = $"{userName} is now lurking! Current Lurkers are now: {count}. We're now at {totalCount} all time lurkers!";
} else {
messageOut = "No Bueno";
}
} else if(String.Equals(outputer[0].ToLower(), "check")) {
if(lurker) {
messageOut = $"You are currently lurking! The current amount of lurkers: {count}. Total Lurkers: {totalCount}";
} else if(!lurker) {
messageOut = $"The current amount of lurkers: {count}. Total Lurkers: {totalCount}";
} else {
messageOut = "No Bueno";
}
} else if(String.Equals(outputer[0].ToLower(), "reset")) {
count = 0;
messageOut = $"The total amount of lurkers have been reset. New Count: {count}";
} else {
messageOut = "Incorrect usuage of the !lurk command";
}
CPH.SendMessage(messageOut);
CPH.SetGlobalVar("totalLurkCount", totalCount, true);
CPH.SetGlobalVar("lurkCount", count, true);
return true;
}
}```
I have my code for linked list here I have attached below, In this all methods are declared in a single class, now I am refactoring my code so I am moving my methods to separate class, after moving my data to separate class my data is not getting passed which so my data is not getting displayed, I think my way of passing the data through the variables to other classes is where the problem occurs, I don't know how to rectify this..here I have attached my code..
here is my code:
//My node class:
public class Node
{
public int info;
public Node link;
public Node(int i)
{
info = i;
link = null;
}
}
// My class to print the data:
public class Display
{
public void DispalyList()//list to display the items
{
Node p;
SingleLinkedList lst = new SingleLinkedList();
if (lst.start == null) // I have a doubt whether calling the start variable using that object is not correct., If data is being passed here I can proceed further
{
Console.WriteLine("List is empty");
return;
}
Console.Write("list is: ");
p = lst.start; //The way of calling the start object is wrong I guess
while (p != null) // p value is null here
{
Console.Write(p.info + " ");
p = p.link;
}
Console.WriteLine();
}
}
// My Single Linked list class://for time being i have two methods in this class
public class SingleLinkedList
{
public Node start;
public SingleLinkedList()//constructor
{
start = null;
}
public void CreateList() //Method where I am inserting the data
{
int i, n, data;//variable declaration
Console.Write("enter the number of nodes");
n = Convert.ToInt32(Console.ReadLine());
if (n == 0)
return;
for (i = 1; i <= n; i++)
{
Console.WriteLine("enter the element to be inserted");
data = Convert.ToInt32(Console.ReadLine());
InsertAtEnd(data);
}
}
public void InsertAtEnd(int data)//Method to insert the data into the list
{
Node p;
Node temp = new Node(data);
if (start == null)
{
start = temp;
return;
}
p = start;
while (p.link != null)
p = p.link;
p.link = temp;
}
}
}
//Main class using switch case:// I am just calling the display method alone for time being
public class Program
{
static void Main(string[] args)//Main program calling the methods
{
int choice;
SingleLinkedList list = new SingleLinkedList();
list.CreateList();// calling the createlist method
while (true)
{
Console.WriteLine("1.Display List");
Console.WriteLine("Enter your choice");
choice = Convert.ToInt32(Console.ReadLine());
if (choice == 13) //I have 13 cases for time being I have shown only one below
break;
switch(choice)//Switch case calling the methods
{
case 1:
Display dis = new Display();
dis.DispalyList();//calling display method which prints the data
break;
default://default case
Console.WriteLine("Wrong Choice");
}
}
}
}
Here is my above code, in which the value of the start in the[ DisplayList()] method is assigning null.If the data is being passed to it in this method i can follow the same for my other classes too. I don't know how to assign the data here..
I would suggest using ReadAllLines() instead, which will return a collection of strings, one per line in your input:
public class ReadingTextFile: IReadingTextFile {
public IEnumerable<string> content() { // change the return type
string path = # "C:\Users\s\Desktop\Datas\Data Input.txt";
var data = File.ReadAllLines(path); // and this
return data;
}
}
Then you can either just use the file.content() if all you need is a list:
IReadingTextFile file = new ReadingTextFile();
LinkedList < string > data = new LinkedList < string > ();
IEnumerable<string> inp = file.content(); // this is a collection of strings, one per line of your input
// ...
Or if you still want a linked list, you can just use the LinkedList constructor that takes a collection:
public class Readingtolist {
public void Input() {
IReadingTextFile file = new ReadingTextFile();
Stopwatch sw = new Stopwatch();
sw.Start();
IEnumerable<string> inp = file.content(); // the reading the file is probably what you really want to time.
sw.Stop();
var data = new LinkedList<string>(inp); // note the use of (inp) here
Console.Write("\n time Taken For Read Data: {0} ms",
sw.Elapsed.TotalMilliseconds);
Console.WriteLine("\n The items are{0}", inp);
}
}
Trying to complete a stock system in a console application and thus I am stuck on this part, to make sure that a user can't have a duplicate 8 digit long ID number, my issue is as follows.
Basically I am unsure why this code will not work, I'm probably missing a very obvious piece of code here, help would be appreciated, have tried changing values around already so have more than likely overlooked a value.
static int checkIDNumber(int ID)
{
// Check the number passed in & then loop through all the lines...
// If number is taken then output error, because id exists already
// Else allow the value to be used in the stock system.
int IDNumber = ID;
using (StreamReader sr = new StreamReader("Stockfile.txt"))
{
string lineValues;
while (sr.EndOfStream == false)
{
lineValues = sr.ReadLine();
if (lineValues.Contains(IDNumber.ToString()))
{
Console.WriteLine("Id number is currently already taken.");
}
else
{
return IDNumber;
}
}
}
}
I pass in my value from this line in another procedure where it is defined in the local scope.
stockID = checkIDNumber(stockID);
Here is the full code:
class Program
{
static void Main(string[] args)
{
char menuOption = '0';
while (menuOption != '3')
{
DisplayMenuOption();
menuOption = GetMenuOption();
switch (menuOption)
{
case '1':
AddStock();
break;
case '2':
CheckStock();
break;
case '3':
Console.WriteLine("Goodbye");
break;
default:
Console.WriteLine("That is not a valid option");
break;
}
}
// Keep it all happy for a screenshot ;)
Console.ReadLine();
}
static void DisplayMenuOption()
{
Console.WriteLine("Do you wish to Add Stock(1) or Check Stock(2) or Exit(3)?");
}
static void DisplayStockOption()
{
Console.WriteLine("Do you want to search by ID(1) or by Name(2), Delete current stock(3) or Exit(4)?");
}
static char GetMenuOption()
{
char userChoice = '0';
userChoice = Convert.ToChar(Console.ReadLine());
return userChoice;
}
static void CheckStock()
{
char menuOption = 'a';
while (menuOption != '4')
{
DisplayStockOption();
menuOption = GetMenuOption();
switch (menuOption)
{
case '1':
SearchID();
break;
case '2':
SearchName();
break;
case '3':
RemoveStock();
break;
case '4':
Console.WriteLine("Goodbye");
break;
default:
Console.WriteLine("That is not a valid option");
break;
}
}
}
static void RemoveStock()
{
List<string> tempList = new List<string>();
string lineValues = "";
bool found = false;
int ID = 0;
using (StreamReader sr = new StreamReader("Stockfile.txt"))
{
Console.Write("Please enter the ID number to delete: ");
ID = Convert.ToInt32(Console.ReadLine());
while (sr.EndOfStream == false)
{
lineValues = sr.ReadLine();
if (lineValues.Contains(ID.ToString()) == false)
{
tempList.Add(lineValues);
}
else
{
found = true;
}
}
}
if (found == true)
{
using (StreamWriter sw = new StreamWriter("Stockfile.txt", false))
{
for (int i=0; i < tempList.Count; i++)
{
sw.Write(tempList[i]);
sw.WriteLine();
}
}
}
}
static void SearchName()
{
using (StreamReader sr = new StreamReader("Stockfile.txt"))
{
string name;
Console.Write("Please enter the name: ");
name = Console.ReadLine();
while (sr.EndOfStream == false)
{
string lineValues = sr.ReadLine();
if (lineValues.Contains(name))
{
Console.WriteLine("{0}", lineValues);
}
else
{
Console.WriteLine("{0} does not exist in this stock system!",name); // Could try to match a similar string incase of spelling errors here, although after looking at it it may be a bit far for what is being required now, but in the real world application this would be a must else people would mistype words thus not having an exact match.
}
}
}
}
static void SearchID()
{
using (StreamReader sr = new StreamReader("Stockfile.txt"))
{
int IDNumber;
string lineValues;
Console.Write("Please enter the ID number: ");
IDNumber = Convert.ToInt32(Console.ReadLine());
while (sr.EndOfStream == false)
{
lineValues = sr.ReadLine();
if (lineValues.Contains(IDNumber.ToString()))
{
Console.WriteLine("{0}", lineValues);
}
else
{
Console.WriteLine("{0} does not exist in this stock system!", IDNumber); // Could try to match a similar string incase of spelling errors here, although after looking at it it may be a bit far for what is being required now, but in the real world application this would be a must else people would mistype words thus not having an exact match.
}
}
}
}
static int checkIDNumber(int ID)
{
// Check the number passed in & then loop through all the lines...
// If number is taken then output error, becuase id exists already
// Else allow the value to be used in the stock system.
using (StreamReader sr = new StreamReader("Stockfile.txt"))
{
int IDNumber;
string lineValues;
Console.Write("Please enter the ID number: ");
IDNumber = Convert.ToInt32(Console.ReadLine());
while (sr.EndOfStream == false)
{
lineValues = sr.ReadLine();
if (lineValues.Contains(IDNumber.ToString()))
{
Console.WriteLine("Id number is currently already taken.");
}
else
{
ID = IDNumber;
return ID;
}
}
}
}
static void AddStock(int IDNumber)
{
using (StreamWriter sw = new StreamWriter("Stockfile.txt", true))
{
int stockID = 0;
int stockQuantity = 0;
double stockPrice = 0.00;
string stockName = "";
string s = ""; // Being Lazy here, to convert to when needed.
while (stockID.ToString().Length != 8)
{
Console.Write("Please enter the stock ID number: ");
stockID = Convert.ToInt32(Console.ReadLine());
}
s = stockID.ToString();
sw.Write(s + "\t"); // Will only accept an 8 figure digit so is safe to have a single value here.
while (stockName.Length <= 2) // No fancy brands here......
{
Console.Write("Please enter the name of the stock: ");
stockName = Console.ReadLine();
}
s = stockName;
sw.Write(s + "\t");
while (stockQuantity < 1) // Running a small shop here...
{
Console.Write("Please enter the quanity of stock: ");
stockQuantity = Convert.ToInt32(Console.ReadLine());
}
s = stockQuantity.ToString();
sw.Write(s + "\t");
while (stockPrice < 0.01) // Running a very small shop....
{
Console.Write("Please enter the price of the stock: ");
stockPrice = Convert.ToDouble(Console.ReadLine());
}
s = stockPrice.ToString();
sw.Write(s + "\t");
sw.WriteLine(); // TO create the new line.....
}
}
}
}
The problem is that you're only returning a value from inside the else block.
Your method needs to return a value regardless of which path the program takes through your code. You can fix this in any number of ways, depending on your requirements. For instance, you can have a "catch-all" return value at the bottom of the method so that if it passes through all your tests (i.e. if blocks) and reaches the bottom, as long as that's a meaningful result, it will return the catch-all value.
Alternatively, you could just make sure you put a return statement inside each of the code paths. For that, you'd need to add a return in the if portion of your if block, but you'd likely also still need a return outside of your while loop since that may never execute.
Again, it all depends on your needs.
It's at least logically possible that the file contains nothing but the ID. For example, if I enter "10" as an ID and the file is:
10
10
10
10
...
It might be the case that you know that that'll never actually happen, but the compiler can't really prove that it won't. From the compiler's "perspective," there's not really a difference between "might happen" and "can't prove that it won't happen."
Also, your logic is wrong. If you know that the user requested a duplicate ID, you don't need to check the rest of the file - you already know it's a duplicate.
Right now, if the first line of the file isn't the ID they requested, it'll allow the user to take it. For example, if the user requested "9" as an ID and the file is as follows:
3 -- It only actually checks this line
5
9 -- Obviously the ID is already taken here but it'll never check this line
2
1
See my comments below:
// I'd suggest making this "bool" - they already know what the ID number is,
// so there's no point in returning it back to them
static int checkIDNumber(int ID)
{
// Check the number passed in & then loop through all the lines...
// If number is taken then output error, because id exists already
// Else allow the value to be used in the stock system.
int IDNumber = ID;
using (StreamReader sr = new StreamReader("Stockfile.txt"))
{
string lineValues;
// In general, you shouldn't explicitly compare to "true" or "false."
// Just write this as "!sr.EndOfStream"
while (sr.EndOfStream == false)
{
lineValues = sr.ReadLine();
if (lineValues.Contains(IDNumber.ToString()))
{
// In this case, you don't have to bother checking the rest of the file
// since you already know that the ID is taken
Console.WriteLine("Id number is currently already taken.");
}
else
{
// You actually can't return that at this point - you need to check
// the *entire* file before you conclude that it's not a duplicate
return IDNumber;
}
}
}
}
I have the following method. How can I write a unit test for this function to make sure the machine is added to the repairs list?
public void AddMachineToRepairsList()
{
Console.WriteLine("Would you like to add this Machine to the repairs list?");
var addToRepairs = Console.ReadLine().ToLower();
if (addToRepairs == "yes")
{
int cost = 0;
int hoursWorked = 0;
var machine = new Repair(cost, hoursWorked);
Repairs.Add(machine);
Console.WriteLine("Machine successfully added!");
}
else
{
Console.WriteLine("Please enter machine information again");
this.Run();
}
}
You will need to pass addToRepairs as parameter to the method and call like
Console.WriteLine("Would you like to add this Machine to the repairs list?");
var addToRepairs = Console.ReadLine().ToLower();
while(AddMachineToRepairsList(addToRepairs)==false)
{
Console.WriteLine("Please enter machine information again");
this.Run();
}
Definition would be
public bool AddMachineToRepairsList(string option)
{
string addToRepairs = "";
if (addToRepairs == "yes")
{
int cost = 0;
int hoursWorked = 0;
var machine = new Repair(cost, hoursWorked);
Repairs.Add(machine);
Console.WriteLine("Machine successfully added!");
return true;
}
else
{
return false;
}
}
Now one unit tests will be with value "yes" and one with "no" . you
can write unit test and assert the return value is true/false based on option passed