根据VS2015的提示,仅支持在共有类或共有方法中支持创建单元测试。所以,如果我们要测试私有或是保护的类和方法,是要先将他们暂时设定成公有类型。
在VS2015中创建单元测试,只要在我们想测试的地方点击右键,就会出现 “创建单元测试” 选项。
如果菜单没有显示 测试,可以参照这篇博客进行设置。http://www.bubuko.com/infodetail-1370830.html
单击 “创建单元测试” 后,会出项如下对话框。不用修改,保持默认选项就可以。
点击“确定”
创建完成后,会出项一个名为 “WCTests” 的文件,代码:
using Microsoft.VisualStudio.TestTools.UnitTesting; using wc; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace wc.Tests { [TestClass()] public class WCTests { [TestMethod()] public void WCTest() { Assert.Fail(); } [TestMethod()] public void OperatorTest() { Assert.Fail(); } } }
在进行单元测试时,主要使用的是 Assert 类,他的用发有很多,详细用法可以参照 https://msdn.microsoft.com/zh-cn/library/microsoft.visualstudio.testtools.unittesting.assert.aspx
下面给出我们要进行测试的代码,为了方便测试,我们对其中代码输出的部分进行了修改,将原先控制台输出的部分改成了函数返回。
public class WC { private string sFilename; // 文件名 private string[] sParameter; // 参数数组 private int iCharcount; // 字符数 private int iWordcount; // 单词数 private int iLinecount; // 行 数 private int iNullLinecount; // 空行数 private int iCodeLinecount; // 代码行数 private int iNoteLinecount; // 注释行数 // 初始化 public WC() { this.iCharcount = 0; this.iWordcount = 0; this.iLinecount = 0; this.iNullLinecount = 0; this.iCodeLinecount = 0; this.iNoteLinecount = 0; } // 控制信息 public string Operator(string[] sParameter, string sFilename) { this.sParameter = sParameter; this.sFilename = sFilename; string retrun_str = ""; foreach (string s in sParameter) { if(s == "-x") { string resultFile = ""; OpenFileDialog fd = new OpenFileDialog(); fd.InitialDirectory = "D:\\Patch"; fd.Filter = "All files (*.*)|*.*|txt files (*.txt)|*.txt"; fd.FilterIndex = 2; fd.RestoreDirectory = true; if (fd.ShowDialog() == DialogResult.OK) { resultFile = fd.FileName; //Console.WriteLine("文件名:{0}", resultFile); SuperCount(resultFile); BaseCount(resultFile); retrun_str = DisplayAll(); } break; } // 遍历文件 else if (s == "-s") { try { string[] arrPaths = sFilename.Split('\\'); int pathsLength = arrPaths.Length; string path = ""; // 获取输入路径 for (int i = 0; i < pathsLength - 1; i++) { arrPaths[i] = arrPaths[i] + '\\'; path += arrPaths[i]; } // 获取通配符 string filename = arrPaths[pathsLength - 1]; // 获取符合条件的文件名 string[] files = Directory.GetFiles(path, filename); foreach (string file in files) { //Console.WriteLine("文件名:{0}", file); SuperCount(file); BaseCount(file); retrun_str = Display(); } break; } catch (IOException ex) { //Console.WriteLine(ex.Message); return ""; } } // 高级选项 else if (s == "-a") { //Console.WriteLine("文件名:{0}", sFilename); SuperCount(sFilename); BaseCount(sFilename); retrun_str = Display(); break; } // 基本功能 else if (s == "-c" || s == "-w" || s == "-l") { //Console.WriteLine("文件名:{0}", sFilename); BaseCount(sFilename); retrun_str = Display(); break; } else { //Console.WriteLine("参数 {0} 不存在", s); break; } } Console.WriteLine("{0}", retrun_str); return retrun_str; } // 统计基本信息:字符数 单词数 行数 private void BaseCount(string filename) { try { // 打开文件 FileStream file = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read); StreamReader sr = new StreamReader(file); int nChar; int charcount = 0; int wordcount = 0; int linecount = 0; //定义一个字符数组 char[] symbol = { ' ', ',', '.', '?', '!', ':', ';', '\'', '\"', '\t', '{', '}', '(', ')', '+' ,'-', '*', '='}; while ((nChar = sr.Read()) != -1) { charcount++; // 统计字符数 foreach (char c in symbol) { if(nChar == (int)c) { wordcount++; // 统计单词数 } } if (nChar == '\n') { linecount++; // 统计行数 } } iCharcount = charcount; iWordcount = wordcount + 1; iLinecount = linecount + 1; sr.Close(); } catch (IOException ex) { Console.WriteLine(ex.Message); return; } } // 统计高级信息:空行数 代码行数 注释行数 private void SuperCount(string filename) { try { // 打开文件 FileStream file = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read); StreamReader sr = new StreamReader(file); String line; int nulllinecount = 0; int codelinecount = 0; int notelinecount = 0; while ((line = sr.ReadLine()) != null) { line = line.Trim(' '); line = line.Trim('\t'); // 空行 if (line == "" || line.Length <= 1) { nulllinecount++; } // 注释行 else if(line.Substring(0, 2) == "//" || line.Substring(1, 2) == "//") { notelinecount++; } // 代码行 else { codelinecount++; } } iNullLinecount = nulllinecount; iCodeLinecount = codelinecount; iNoteLinecount = notelinecount; sr.Close(); } catch (IOException ex) { Console.WriteLine(ex.Message); return; } } // 打印信息 private string Display() { string return_str = ""; foreach (string s in sParameter) { if (s == "-c") { //Console.WriteLine("字 符 数:{0}", iCharcount); return_str += "字符数:"+iCharcount.ToString(); } else if (s == "-w") { //Console.WriteLine("单 词 数:{0}", iWordcount); return_str += "单词数:" + iWordcount.ToString(); } else if (s == "-l") { //Console.WriteLine("总 行 数:{0}", iLinecount); return_str += "总行数:" + iLinecount.ToString(); } else if(s == "-a") { //Console.WriteLine("空 行 数:{0}", iNullLinecount); //Console.WriteLine("代码行数:{0}", iCodeLinecount); //Console.WriteLine("注释行数:{0}", iNoteLinecount); return_str += "空行数:" + iNullLinecount.ToString(); return_str += "代码行数:" + iCodeLinecount.ToString(); return_str += "注释行数:" + iNoteLinecount.ToString(); } } //Console.WriteLine(); return return_str; } private string DisplayAll() { string return_str = ""; foreach (string s in sParameter) { //Console.WriteLine("字 符 数:{0}", iCharcount); //Console.WriteLine("单 词 数:{0}", iWordcount); //Console.WriteLine("总 行 数:{0}", iLinecount); //Console.WriteLine("空 行 数:{0}", iNullLinecount); //Console.WriteLine("代码行数:{0}", iCodeLinecount); //Console.WriteLine("注释行数:{0}", iNoteLinecount); return_str += "字符数:" + iCharcount.ToString(); return_str += "单词数:" + iWordcount.ToString(); return_str += "总行数:" + iLinecount.ToString(); return_str += "空行数:" + iNullLinecount.ToString(); return_str += "代码行数:" + iCodeLinecount.ToString(); return_str += "注释行数:" + iNoteLinecount.ToString(); } //Console.WriteLine(); return return_str; } }
接下来,我们对测试代码进行修改,在我们进行单元测试时,某种程度上就是将我们人工给出的程序运行结果与程序实际输出结果进行比较,所以单元测试的过程一般分为 3 步:
- 给出我们期望的结果 expected
- 执行需测试代码,返回结果 actual
- 比较 actual 和 expected
下面以 WC 程序执行 -c 参数对 123.txt 文件进行统计的功能为例进行测试,我们将测试代码修改如下,其中 AreEqual 方法用于对期望值与实际值进行比较。
using Microsoft.VisualStudio.TestTools.UnitTesting; using wc; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace wc.Tests { [TestClass()] public class WCTests { [TestMethod()] public void WCTest() { // arrange string expected = "字符数:138"; WC testwc = new WC(); // act string[] opar = new string[1]; opar[0] = "-c"; string actual = testwc.Operator(opar, @"D:\学习\我的工程\软件工程\wc\wc\wc\123.txt"); // assert Assert.AreEqual(expected, actual); } } }
"123.txt" 文件:
我们预期的测试结果是此文件有138个字符,所以会输出 “字符数:138” ;我们来看看测试的结果,点击右键,选择 “运行测试” ,选项测试通过了。
我们现在给出一个错误的预期来看看结果会如何,我们现在认为 "123.txt" 文件有50个字符。
系统会提示出错,并且给出实际值与期望值分别是多少。
编写测试方法
单元测试的基本方法是调用被测代码的函数,输入函数的参数值,获取返回结果,然后与预期测试结果进行比较,如果相等则认为测试通过,否则认为测试不通过。
1、Assert类的使用
Assert.Inconclusive() 表示一个未验证的测试;
Assert.AreEqual() 测试指定的值是否相等,如果相等,则测试通过;
AreSame() 用于验证指定的两个对象变量是指向相同的对象,否则认为是错误
AreNotSame() 用于验证指定的两个对象变量是指向不同的对象,否则认为是错误
Assert.IsTrue() 测试指定的条件是否为True,如果为True,则测试通过;
Assert.IsFalse() 测试指定的条件是否为False,如果为False,则测试通过;
Assert.IsNull() 测试指定的对象是否为空引用,如果为空,则测试通过;
Assert.IsNotNull() 测试指定的对象是否为非空,如果不为空,则测试通过;
2、CollectionAssert类的使用
用于验证对象集合是否满足条件
StringAssert类的使用
用于比较字符串。
StringAssert.Contains
StringAssert.Matches
StringAssert.tartWith