用C#在Visual Studio写Javascript单元测试(Firefox内核)
引用nuget包:
注意:Geckofx45 nuget包必须是最后引用,否则初始化会出错
编写JsRunner
using Gecko; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Way.UnitTest { class JsRunner:IDisposable { static JsRunner() { Xpcom.Initialize("Firefox"); } static List<JsFileReference> ReferenceConfigs = new List<JsFileReference>(); static List<string> GlobalJSFiles = new List<string>(); /// <summary> /// 设置js文件依赖 /// </summary> /// <param name="jsFile">js文件路径</param> /// <param name="references">所依赖的js文件路径</param> public static void SetJsReference(string jsFile,IEnumerable<string> references) { ReferenceConfigs.Add(new JsFileReference() { JsFile = jsFile, References = references, }); } /// <summary> /// 添加全局js文件 /// </summary> /// <param name="jsFile"></param> public static void AddGlobalJsFile(string jsFile) { GlobalJSFiles.Add(jsFile); } public JsRunner() { JsFiles.AddRange(GlobalJSFiles); } ~JsRunner() { Dispose(); } List<string> JsFiles = new List<string>(); /// <summary> /// 添加js文件 /// </summary> /// <param name="jsPath">js文件路径</param> public void AddJsFile(string jsPath) { putJsFileContentToList(jsPath); } void putJsFileContentToList(string jsPath) { if (JsFiles.Contains(jsPath) ) { return; } //查找该js是否引用其他js var arr = ReferenceConfigs.Where(m => string.Equals(m.JsFile, jsPath, StringComparison.CurrentCultureIgnoreCase)); foreach( var item in arr ) { foreach( var path in item.References ) { putJsFileContentToList(path); } } JsFiles.Add(jsPath); } /// <summary> /// 运行js代码 /// </summary> /// <typeparam name="T">返回值类型</typeparam> /// <param name="jsCode">一段js代码。如:return data.name;</param> /// <param name="data">传到js里面的对象,js可以通过data.*直接使用此参数</param> /// <returns></returns> public T Run<T>(string jsCode, object data) { var gecko = new GeckoWebBrowser(); gecko.CreateControl();bool loadFinished = false; gecko.NavigationError += (s, e) => { }; gecko.NSSError += (s, e) => { }; gecko.DocumentCompleted += (s, e) => { loadFinished = true; }; string tempFileName = System.IO.Path.GetTempFileName(); System.IO.StreamWriter sw = new StreamWriter(System.IO.File.OpenWrite(tempFileName)); sw.WriteLine("<!DOCTYPE html>"); sw.WriteLine("<html>"); foreach (var path in JsFiles) { sw.WriteLine("<script src=\"file:///" + path + "\" type=\"text/javascript\"></script>"); } sw.WriteLine("<body>"); sw.WriteLine("<input type=hidden id='inputResult'>"); sw.WriteLine("<input type=hidden id='inputError'>"); sw.WriteLine("</body>"); sw.WriteLine("</html>"); sw.Dispose(); gecko.Navigate("file:///" + tempFileName); while (!loadFinished) { System.Threading.Thread.Sleep(10); System.Windows.Forms.Application.DoEvents(); } System.IO.File.Delete(tempFileName); var jsContext = new AutoJSContext(gecko.Window); var js = @" (function(d){ try{ var result = (function(data){" + jsCode + @"})(d); return JSON.stringify(result); }catch(e) { if(typeof e == 'string') return JSON.stringify({ ______err : e , line:0 }); else return JSON.stringify({ ______err : e.message , line:e.lineNumber }); } })(" + (data == null ? "null" : Newtonsoft.Json.JsonConvert.SerializeObject(data)) + @"); "; string result; jsContext.EvaluateScript(js, out result); jsContext.Dispose(); gecko.Dispose(); if(result.StartsWith("{\"______err\":")) { var errObj = Newtonsoft.Json.JsonConvert.DeserializeObject<Newtonsoft.Json.Linq.JObject>(result); string errMsg = errObj.Value<string>("______err"); //int lineNumber = errObj.Value<int>("line") - 3; throw new Exception(errMsg); } return Newtonsoft.Json.JsonConvert.DeserializeObject<T>(result); } /// <summary> /// 读取js文件内容 /// </summary> /// <param name="filePath"></param> /// <returns></returns> string readJsFile(string filePath) { using (System.IO.FileStream fs = System.IO.File.OpenRead(filePath)) { var data = new byte[fs.Length]; var isUtf8 = IsUTF8(fs); fs.Position = 0; fs.Read(data, 0, data.Length); if (isUtf8) { return Encoding.UTF8.GetString(data); } else { return Encoding.GetEncoding("gb2312").GetString(data); } } } /// <summary> /// 判断流是否是utf-8编码 /// </summary> /// <param name="stream"></param> /// <returns></returns> static bool IsUTF8(Stream stream) { bool IsUTF8 = true; while (stream.Position < stream.Length) { byte b = (byte)stream.ReadByte(); if (b < 0x80) // (10000000): 值小于0x80的为ASCII字符 { } else if (b < (0xC0)) // (11000000): 值介于0x80与0xC0之间的为无效UTF-8字符 { IsUTF8 = false; break; } else if (b < (0xE0)) // (11100000): 此范围内为2字节UTF-8字符 { if (stream.Position >= stream.Length - 1) { break; } byte nextByte = (byte)stream.ReadByte(); if ((nextByte & (0xC0)) != 0x80) { IsUTF8 = false; break; } } else if (b < (0xF0)) // (11110000): 此范围内为3字节UTF-8字符 { if (stream.Position >= stream.Length - 2) { break; } byte nextByte1 = (byte)stream.ReadByte(); byte nextByte2 = (byte)stream.ReadByte(); if ((nextByte1 & (0xC0)) != 0x80 || (nextByte2 & (0xC0)) != 0x80) { IsUTF8 = false; break; } } else { IsUTF8 = false; break; } } return IsUTF8; } public void Dispose() { JsFiles.Clear(); } } class JsFileReference { /// <summary> /// js文件 /// </summary> public string JsFile; /// <summary> /// 所依赖的js文件 /// </summary> public IEnumerable<string> References; } }
编写单元测试基类
using System; using Microsoft.VisualStudio.TestTools.UnitTesting; using Gecko; namespace Way.UnitTest.Javascript { /// <summary> /// 其他js单元测试,建议继承此类 /// </summary> [TestClass] public class JSUnitTest { static JSUnitTest() { Xpcom.Initialize("Firefox"); //组合文件夹路径 var solutionPath = AppDomain.CurrentDomain.BaseDirectory + "\\..\\..\\..\\"; //添加全局使用的js文件 JsRunner.AddGlobalJsFile($"{solutionPath}\\js\\xpos-10.core.js"); //定义js文件的依赖关系,这里只是举例,文件实际不存在 //设置kkk.js依赖于a1.js a2.js 两个文件, //这样,每当使用kkk.js文件,系统会自动引入a1.js a2.js 两个文件 JsRunner.SetJsReference($"{solutionPath}\\kkk.js", new string[] { //这里写上所依赖js文件的路径 $"{solutionPath}\\a1.js", $"{solutionPath}\\a2.js" }); } } }
编写测试代码
using System; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Way.UnitTest.Javascript { [TestClass] public class Example : JSUnitTest { [TestMethod] public void Test() { using (JsRunner jsEngine = new JsRunner()) { //编写js代码 var js = @" return data.name; "; //运行js代码 var result = jsEngine.Run<string>(js, new {name="JACK"}); if (result != "JACK") throw new Exception("运算结果错误"); } } } }