背景
在代码框架越来越复杂,需求变更越来越快的时代,往往一个项目需要很多人合作完成,这就会涉及到很多的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 ", 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来测试