xUnit测试ET框架项目

 我之前搞java的单元测试,测试类是在一个项目中而且测试,测试也不需要借助许多初始化数据。

第一次使用xUnit而且,测试ET项目,ET项目有许多初始化【可能游戏都是这样的】,由于服务端分为了许多项目,测试也是单独一个项目,而且大多测试方法都需要一些游戏初始化配置数据,这和之前不一样,不过大致都一样的,都是三个要素:准备、执行、断言,ET项目测试主要是准备阶段比较麻烦。

首先:测试方法可以使用,要待测试项目的类及方法

  • 创建xUnit测试项目,
  • 依赖要测试的项目,这里依赖ET框架的服务端Server项目

刚开始可能读取配置文件解析失败【我碰到了这个问题】,xUnit单元测试读取配置文件失败问题解决

 

因为新建的测试项目xUnit使用的.Net Core版本是3.1,而引入的ET项目是2.1,对应C#版本是7.3的

   

 其次:开始测试前准备测试数据

xUnit测试ET框架项目测试环境准备:xUnit测试方法前会执行构造,所以测试环境及初始化数据在构造中准备

  1. 测试类的构造用于初始化测试环境,准备测试方法所需测试数据,即执行ET的main()方法。
  2. main()方法中创建测试登录的用户玩家,并在玩家身上挂载相应组件,如果数据库没有则创建并保存到数据库【因为有的初始化方法StartSystem等是从数据库查询的玩家从而获取组件数据】,并将玩家组件添加到Session的组件SessionUserComponent上供测试方法中从session获取创建的玩家数据,
  3. 如果有将查询出来用户并挂到session的组件SessionUserComponent上供测试方法中从session获取查询出来的玩家数据,从而获取用户组件数据。
  4. 创建后执行100次 EventSystem.Update();用于执行初始化AwakeSystem和StartSystem中的初始化方法,main()一般构造中用于测试方法前初始化,所以不必太多次循环造成不必要的测试时间,但太少又可能太快任务还未加到任务队列中【例如循环5次就不会执行StartSysTem的Start()方法】,100差不多
  5. 然后初始化测试类中的组件字段,一般是要测试的组件从userInfo获取,或Game.Scene获取。
  6. 测试异步方法,官方说推荐使用async ETTask而不是用async void,测试需要异步方法,异步方法调用照样使用await,亲测,在ET框架下,async void、async ETVoid、async ETTask,都可以使用异步保存,然后向下走,官方异步单元测试:https://docs.microsoft.com/zh-cn/archive/msdn-magazine/2014/november/async-programming-unit-testing-asynchronous-code

玩家背包组件测试:

准备数据:将背包组件存到一个用户身上。挂载后运行EventSystem.update事件,才会执行StartSystem。

测试异步方法一直运行不结束情况:搞了我好久最后终于让我找到了解决办法
如果遇到测试方法一直运行不结束,可能是由于异步await方法未请求到结果,可在将单线程注释掉即可解决

//注释掉就会多线程
//SynchronizationContext.SetSynchronizationContext(OneThreadSynchronizationContext.Instance);

【期间我试了好多方式但是一直偶尔不结束偶尔结束 不稳定,】例如

  1. await方法方法未请求到结果,然后测试方法就结束的猜测,解决办法是在await后再次循环运行EventSystem.update,但这种方式还是不稳定
  2. await之后的代码使用了await之前的变量,导致await后的代码不能执行,因为await之后打印的输出都未打印
  3. 等等
  4. ……许多其他错误的猜测,这虽然多走了许多弯路,但是经验不就是这么来的嘛,现在不走可能以后也会走,和人生一样,我以前不管学习、工作、还是生活上都走了不少弯路,让人痛悔不已,说多了都是泪,扯远了,
  5. 为了大家遇到这种问题少浪费时间在这上面,我把我的解决方法写到这里,真正原因还未清楚【此解决办法暂时用着还可以】

 最后:执行测试和断言

我的测试xUnit代码:

测试类:

public class PlayerItemsComponentTest
    {
        PlayerItemsComponent self;
        ItemsManagerComponent itemsManagerComponent;//用于
        public ITestOutputHelper Output;
        public PlayerItemsComponentTest(ITestOutputHelper tempOutput)
        {
            AppProgramHelper.Main2(new string[] { "--appId=1", "--appType=AllServer", "--config=../Config/StartConfig/LocalAllServer.txt" });

            LobbyManagerComponent lobbyManagerComponent = Game.Scene.GetComponent<LobbyManagerComponent>();
            itemsManagerComponent = lobbyManagerComponent.GetComponent<ItemsManagerComponent>();
            Output = tempOutput;

            self = AppProgramHelper.GetSession().GetComponent<SessionUserComponent>()?.userInfo?.GetComponent<PlayerItemsComponent>();
            //System.Diagnostics.Stopwatch stopwatch = new Stopwatch();//Stopwatch
            //stopwatch.Start();
            //耗时方法...()
            //TimeSpan timespan = stopwatch.Elapsed; //  获取当前实例测量得出的总时间
            //double milliseconds = timespan.TotalMilliseconds;  //  总毫秒数
            //Output.WriteLine($"构造耗时:{milliseconds}毫秒");
        }
        [Fact]
        //只保存一次到数据库,数据库中不存在userInfo 才运行此测试方法。
        public async ETTask PrepareTestDataAsync()
        {
            try
            {
                self.Add(2);
                self.Add(3);
                self.Add(4);
                self.Add(5);
                self.Add(6);
                self.Add(7);
                self.Add(9);
                for (int i = 0; i < 30; i++)
                {
                    self.Add(11);
                }
                self.Add(12);
                Output.WriteLine("测试await前,会不会打印输出");
                Session session = AppProgramHelper.GetSession();
                ETModel.UserInfo userInfo = session.GetComponent<SessionUserComponent>()?.userInfo;
                await AppProgramHelper.SaveEntityAsync(userInfo);
                await AppProgramHelper.SaveEntityAsync(session);
                Output.WriteLine("测试await后,会不会打印输出");//也可以打印出来,断点执行更容易通过,运行会一直测试不结束。
            }
            catch (Exception e)
            {
                Output.WriteLine(e.Message + e.StackTrace);
            }
        }

        ……
        [Fact]
        public void ConvertTest()
        {
            ItemInfo itemInfo = itemsManagerComponent.getItemById(11);
            ItemInfo ConvertedItem = itemsManagerComponent.getItemById(itemInfo.CompositeObject);
            int oldCount = self.GetItemCountById(ConvertedItem.Id);
            self.Convert(itemInfo);
            ETModel.UserInfo userInfo = AppProgramHelper.GetSession().GetComponent<SessionUserComponent>()?.userInfo;
            if (userInfo != null)
            {
                AppProgramHelper.SaveEntityAsync(userInfo);
            }
            Assert.Equal(oldCount+1, self.GetItemCountById(ConvertedItem.Id));
        }
    }

测试项目构造调用的初始化【准备测试数据】:Main()

    internal static class AppProgramHelper
    {
        public static void Main2(string[] args)
        {
            //TaskStatus
            // 异步方法全部会回掉到主线程,即Task.Run()开启的任务,和Task.Delay()开启的任务。
            //如果不设置同步上下文,你会发现打印出来当前线程就不是主线程了,设置后,线程id一致全是1。
            //SynchronizationContext.SetSynchronizationContext(OneThreadSynchronizationContext.Instance);

            try
            {
                Game.EventSystem.Add(DLLType.Model, typeof(Game).Assembly);
                Game.EventSystem.Add(DLLType.Hotfix, DllHelper.GetHotfixAssembly());
                //命令行参数。配置的有四个服务器组件的ip+port信息和appId等信息。
                Options options = Game.Scene.AddComponent<OptionComponent, string[]>(args).Options;
                //StartConfig属于Entity里边有其他属性哦ID跟components,配置的有 outerConfig,innerConfig,clientConfig,HttpConfig 组件信息。
                //初始化StartConfigComponent组件中的各种服务器配置的是AllServer:DBConfig,RealmConfig,LocationConfig,MapConfigs,GateConfigs,
                //启动配置
                StartConfig startConfig = Game.Scene.AddComponent<StartConfigComponent, string, int>(options.Config, options.AppId).StartConfig;
                StartConfigComponent startConfigComponent = Game.Scene.GetComponent<StartConfigComponent>();
                if (!options.AppType.Is(startConfig.AppType))
                {
                    Log.Error("命令行参数apptype与配置不一致");
                    return;
                }

                IdGenerater.AppId = options.AppId;

                LogManager.Configuration.Variables["appType"] = $"{startConfig.AppType}";
                LogManager.Configuration.Variables["appId"] = $"{startConfig.AppId}";
                LogManager.Configuration.Variables["appTypeFormat"] = $"{startConfig.AppType,-8}";
                LogManager.Configuration.Variables["appIdFormat"] = $"{startConfig.AppId:0000}";

                Console.ForegroundColor = ConsoleColor.Green;
                Console.WriteLine($"游龙方舟ServerStart........................ AppId:{startConfig.AppId}  AppType:{startConfig.AppType}");

                Game.Scene.AddComponent<TimerComponent>();
                Game.Scene.AddComponent<OpcodeTypeComponent>();
                Game.Scene.AddComponent<MessageDispatcherComponent>();

                // 根据不同的AppType添加不同的组件
                OuterConfig outerConfig = startConfig.GetComponent<OuterConfig>();
                InnerConfig innerConfig = startConfig.GetComponent<InnerConfig>();
                ClientConfig clientConfig = startConfig.GetComponent<ClientConfig>();

                switch (startConfig.AppType)
                {
                    case AppType.Manager:
                        Game.Scene.AddComponent<AppManagerComponent>();
                        Game.Scene.AddComponent<NetInnerComponent, string>(innerConfig.Address);
                        Game.Scene.AddComponent<NetOuterComponent, string>(outerConfig.Address);
                        break;
                    case AppType.Realm:
                        Game.Scene.AddComponent<MailboxDispatcherComponent>();
                        Game.Scene.AddComponent<ActorMessageDispatcherComponent>();
                        Game.Scene.AddComponent<NetInnerComponent, string>(innerConfig.Address);
                        Game.Scene.AddComponent<NetOuterComponent, string>(outerConfig.Address);
                        Game.Scene.AddComponent<LocationProxyComponent>();
                        Game.Scene.AddComponent<RealmGateAddressComponent>();
                        Game.Scene.AddComponent<DBProxyComponent>();
                        break;
                    case AppType.DB:
                        Game.Scene.AddComponent<DBComponent>();
                        Game.Scene.AddComponent<NetInnerComponent, IPEndPoint>(innerConfig.IPEndPoint);
                        break;
                    case AppType.Gate:
                        Game.Scene.AddComponent<PlayerComponent>();
                        Game.Scene.AddComponent<MailboxDispatcherComponent>();
                        Game.Scene.AddComponent<ActorMessageDispatcherComponent>();
                        Game.Scene.AddComponent<NetInnerComponent, string>(innerConfig.Address);
                        Game.Scene.AddComponent<NetOuterComponent, string>(outerConfig.Address);
                        Game.Scene.AddComponent<LocationProxyComponent>();
                        Game.Scene.AddComponent<ActorMessageSenderComponent>();
                        Game.Scene.AddComponent<ActorLocationSenderComponent>();
                        Game.Scene.AddComponent<GateSessionKeyComponent>();
                        break;
                    case AppType.Location:
                        Game.Scene.AddComponent<NetInnerComponent, string>(innerConfig.Address);
                        Game.Scene.AddComponent<LocationComponent>();
                        break;
                    case AppType.Map:
                        Game.Scene.AddComponent<NetInnerComponent, string>(innerConfig.Address);
                        Game.Scene.AddComponent<UnitComponent>();//多个Unit。
                                                                 // 访问location server的组件
                        Game.Scene.AddComponent<LocationProxyComponent>();
                        Game.Scene.AddComponent<ActorMessageSenderComponent>();
                        Game.Scene.AddComponent<ActorLocationSenderComponent>();
                        Game.Scene.AddComponent<MailboxDispatcherComponent>();
                        Game.Scene.AddComponent<ActorMessageDispatcherComponent>();
                        Game.Scene.AddComponent<PathfindingComponent>();
                        break;
                    case AppType.AllServer:
                        // 发送普通actor消息
                        Game.Scene.AddComponent<ActorMessageSenderComponent>();
                        // 发送location actor消息
                        Game.Scene.AddComponent<ActorLocationSenderComponent>();
                        Game.Scene.AddComponent<DBComponent>();
                        Game.Scene.AddComponent<DBProxyComponent>();
                        // location server需要的组件
                        Game.Scene.AddComponent<LocationComponent>();
                        // 访问location server的组件
                        Game.Scene.AddComponent<LocationProxyComponent>();
                        // 这两个组件是处理actor消息使用的
                        Game.Scene.AddComponent<MailboxDispatcherComponent>();
                        Game.Scene.AddComponent<ActorMessageDispatcherComponent>();
                        // 内网消息组件
                        Game.Scene.AddComponent<NetInnerComponent, string>(innerConfig.Address);
                        // 外网消息组件
                        Game.Scene.AddComponent<NetOuterComponent, string>(outerConfig.Address);
                        // manager server组件,用来管理其它进程使用
                        Game.Scene.AddComponent<AppManagerComponent>();
                        Game.Scene.AddComponent<RealmGateAddressComponent>();
                        Game.Scene.AddComponent<GateSessionKeyComponent>();
                        // 配置管理
                        Game.Scene.AddComponent<ConfigComponent>();
                        //控制台组件
                        Game.Scene.AddComponent<ConsoleComponent>();

                        /**************游龙方舟项目***************/
                        // 添加计时器组件
                        Game.Scene.AddComponent<TimeComponent>();
                        //验证服务组件
                        Game.Scene.AddComponent<EducationalGateSessionKeyCpt>();
                        //用户管理组件
                        Game.Scene.AddComponent<PlayerManagerComponent>();
                        //玩家在线组件
                        Game.Scene.AddComponent<OnlineComponent>();
                        Game.Scene.RemoveComponent<OnlineComponent>();
                        //大厅管理组件
                        Game.Scene.AddComponent<LobbyManagerComponent>();
                        //数据库定时存储数据组件
                        Game.Scene.AddComponent<DataBaseSaveComponet>();
                        break;
                    case AppType.Benchmark:
                        Game.Scene.AddComponent<NetOuterComponent>();
                        Game.Scene.AddComponent<BenchmarkComponent, string>(clientConfig.Address);
                        break;
                    case AppType.BenchmarkWebsocketServer:
                        Game.Scene.AddComponent<NetOuterComponent, string>(outerConfig.Address);
                        break;
                    case AppType.BenchmarkWebsocketClient:
                        Game.Scene.AddComponent<NetOuterComponent>();
                        Game.Scene.AddComponent<WebSocketBenchmarkComponent, string>(clientConfig.Address);
                        break;
                    default:
                        throw new Exception($"命令行参数没有设置正确的AppType: {startConfig.AppType}");
                }
                //Console.WriteLine("Game.EventSystem:" + Game.EventSystem);


                //Test.testCallMsg();//测试发送消息
                //Test.TestETTaskAsync();
                //Log.Info("测试ETasync/await完成");
                //TestBytesUtils.TestBytes();
                CreateUserInfo(3);//id=2的用户登录游戏。3:我的时装测试

                //先循环一次才会执行StartSystem事件。循环100次,把之前需要运行的线程全部执行
                int i = 0;
                while (i < 100)//此方法一般构造中用于初始化,所以不必太多次循环,但太少又获取不到新添加的任务队列,100差不多
                {//循环一次就相当于一帧。
                    i++;
                    try
                    {
                        Thread.Sleep(1);
                        OneThreadSynchronizationContext.Instance.Update();
                        Game.EventSystem.Update();
                    }
                    catch (Exception e)
                    {
                        Log.Error(e);
                    }
                }


                //TestBytesUtils.testMongoDB();
                //TestBytesUtils.testAddPlayerItems();
                //TestBytesUtils.testDictionaryOrderBy();
                //TestBytesUtils.testHashSet();
                //TestBytesUtils.testExcept();
            }
            catch (Exception e)
            {
                Log.Error(e);
            }
        }
        public static void WhileTrue(int times)
        {
            int i = 0;
            while (i < times)
            {//循环一次就相当于一帧。
                i++;
                try
                {
                    Thread.Sleep(1);
                    OneThreadSynchronizationContext.Instance.Update();
                    Game.EventSystem.Update();
                }
                catch (Exception e)
                {
                    Log.Error(e);
                }
            }

        }
        //创建登录游戏的用户,并保存到session的组件SessionUserComponent上。
        public static async ETTask CreateUserInfo(long userId)
        {
            Session realmSession = GetSession();
            ETModel.DBProxyComponent dbProxy = Game.Scene.GetComponent<ETModel.DBProxyComponent>();
            ETModel.UserInfo userInfo = await dbProxy.Query<ETModel.UserInfo>(userId);
            if (userInfo == null)
            {
                userInfo = UserInfoFactory.Create(userId, realmSession);//这样创建,可以从组件SessionUserComponent【挂在session上】获取【可能还没保存进数据库】。
            }
            else
            {
                userInfo.Account="二龙戏珠";
                realmSession.AddComponent<SessionUserComponent>().userInfo = userInfo;
                realmSession.GetComponent<SessionUserComponent>().sessionId = realmSession.Id;
            }
        }

        public static Session GetSession() {
            StartConfigComponent config = Game.Scene.GetComponent<StartConfigComponent>();
            IPEndPoint realmIPEndPoint = config.RealmConfig.GetComponent<InnerConfig>().IPEndPoint;
            Session realmSession = Game.Scene.GetComponent<NetInnerComponent>().Get(realmIPEndPoint);
            return realmSession;
        }
        public static async ETTask SaveEntityAsync(ComponentWithId componentWithId)
        {
            ETModel.DBProxyComponent dbProxy = Game.Scene.GetComponent<ETModel.DBProxyComponent>();
            await dbProxy.Save(componentWithId);
        }
    }

 

 

posted @ 2020-05-14 20:38  好Wu赖  阅读(468)  评论(0编辑  收藏  举报