使用MSTest

背景

在代码框架越来越复杂,需求变更越来越快的时代,往往一个项目需要很多人合作完成,这就会涉及到很多的refactor,refactor过程中,往往会对现有代码造成不必要的bug。所以单元测试以及集成测试就显得非常重要,这边介绍下.net下使用使用MSTest编写unit test和integration test。

创建一个专门用于测试的project,添加如下包

 

然后就可以使用TestClass,TestMethod等特性来测试代码了,NSubstitute是一个很方便的mock工具,可以在构建各种interface的mocking(new出来的对象请不要使用)。

执行

MSTest中有多个特性,ClassInitialize, ClassCleanup, TestInitialize, TestMethod(包括DataTestMethod), TestCleanup

在执行一个或多个TestMethod标注的方法时,ClassInitialize最先执行,ClassCleanup最后执行,对于每一个TestMethod,执行顺序是TestInitialize,TestMethod,TestCleanup。

 

substitute Returns 任何变量的用法

当参数是一个object对象的时候,切记要使用Arg.Any,因为一旦两个对象不同,那么得到的值也不同

        [TestMethod]
        public void FileNameIsNullWhenNoFilesAvailable()
        {
            var dataStreamProvider = Substitute.For<IDataStreamProvider>();
            dataStreamProvider.GetFileNamesAsync(Arg.Any<string>()).Returns(Task.FromResult(Empty<string>.Enumerable));

            var unitUnderTest = Create(dataStreamProvider: dataStreamProvider);

            Assert.IsNull(unitUnderTest.FileName);
        }    

 

举几个DataTestMethod的例子

        [DataTestMethod]
        [DataRow("  123", DisplayName = "Space before")]
        [DataRow("321  ", DisplayName = "Space after")]
        [DataRow("3 000", DisplayName = "Space inside")]
        public void TrimAllWhitespace_RemovesSpaces(string text)
        {
            var trimmed = text.TrimAllWhitespace();
            var result = trimmed.Contains(" ");
            Assert.AreEqual(false, result);
        }    


        /// <summary>
        /// Test saving the setting of <see cref="CountingConfiguration.LockNumberOfFixReferencePieces"/> by different user.
        /// </summary>
        [DataTestMethod]
        [DynamicData(nameof(BoolPropertiesPermissionsTestData), DynamicDataSourceType.Method)]
        public async Task LockNumberOfFixReferencePieces_RespectsPermission(Permission permission, bool valueToSet, bool expectedResult)
        {
            var securityService = Substitute.For<ISecurityService>();
            securityService.CurrentPermission.Returns(permission);

            var service = Create(securityService: securityService);
            var configuration = await service.GetConfigurationToChangeAsync();

            await configuration.UpdateSettingAsync(valueToSet, nameof(configuration.LockNumberOfFixReferencePieces));
            await service.CommitConfigurationToChangeAsync();

            Assert.AreEqual(expectedResult, configuration.LockNumberOfFixReferencePieces);
        }

        private static IEnumerable<object[]> BoolPropertiesPermissionsTestData()
        {
            yield return new object[] { Permissions.Administrator, true, true };
            yield return new object[] { Permissions.Supervisor, true, true };
            yield return new object[] { Permissions.Operator, true, false };
        }

 

Test event

1. 内部事件的测试

如图,CommandCreatorEventHandle是测试对象unitUnderTest内部的event,该测试将其绑定到一个回调方法中,然后测试该回调是否正常运行

 

2. 引用外部对象的事件的测试

如下代码模拟了ProductionTestUIOperationEventHandler的事件,返回了state,该事件是productionTestInternalUiManagementService的OperationChanged触发的。

然后由于测试的类command当中绑定了productionTestInternalUiManagementService的OperationChanged事件的回调方法,因此会跳转到回调方法中,从而实现event的测试

        [TestMethod]
        public async Task Execute_CommandSuccess()
        {
            var productionTestInternalUiManagementService =
                Substitute.For<IProductionTestUIManagementService>();
            var command = TouchTestCommandCreatorTest.Create(Array.Empty<string>(), _channelWriter,productionTestInternalUiManagementService : productionTestInternalUiManagementService);
            Assert.IsNotNull(command);
            var task = command.ExecuteAsync(CancellationToken.None);
            var uiOperationResult = new ProductionTestUIOperationResult(ProductionTestUIOperationExecutingResult.Success);
            var state = new ProductionTestUIOperationState(ProductionTestUIOperation.TouchTest,
                                                          ProductionTestUIOperationStateTrigger.ResponseFromClient,
                                                          uiOperationResult);
            productionTestInternalUiManagementService.OperationChanged +=
                Raise.Event<ProductionTestUIOperationEventHandler>(state);
            var result = await task;
            Assert.AreEqual(ProductionTestCommandResultStatus.Pass, result.Result);
            Assert.AreEqual("TOUCH A\r\n", await ReadFromChannelAsync());
        }

 

测试某个对象执行了某个方法

await classObject.Received().methodName(Arg.Any<string>());

 

善用TaskCompletionSource

        [TestMethod]
        public async Task CurrentDataUpdatedAndEventRaised()
        {
            var completionSource = new TaskCompletionSource<bool>();

            var unitUnderTest = CreateSuccessfulConfiguration(20);
            unitUnderTest.CurrentDataChanged += LocalHandler;

            await unitUnderTest.StartAsync();
            await unitUnderTest.StartByRefXAsync(10);

            var eventRaised = await Task.WhenAny(completionSource.Task, Task.Delay(AsyncTestHelper.TaskCompletionTimeout))
                              == completionSource.Task;

            Assert.IsTrue(eventRaised);
            Assert.IsNotNull(unitUnderTest.CurrentData);

            void LocalHandler(CountingData countingData)
            {
                completionSource.SetResult(true);
            }
        }    

 

当测试UI线程的viewModel时,比如UWP会用到CoreDispatcher,这时候需要使用TaskCompletionSource来封装,最终获取它设置的result来测试

 

posted @ 2020-12-27 23:15  小鸡蛋白  阅读(643)  评论(0编辑  收藏  举报