当前位置: 首页 > news >正文

网站建设运营要求云南网站开发网络公司前10

网站建设运营要求,云南网站开发网络公司前10,四川省建设厅信息平台,火车头 wordpress 4.3为了继续改进 Mockito 并进一步改善单元测试体验#xff0c;我们希望您升级到 2.1.0#xff01;Mockito 遵循语义版本控制#xff0c;仅在主要版本升级时包含重大更改。在库的生命周期中#xff0c;重大更改是推出一组全新功能所必需的#xff0c;这些功能会改变现有行为甚…为了继续改进 Mockito 并进一步改善单元测试体验我们希望您升级到 2.1.0Mockito 遵循语义版本控制仅在主要版本升级时包含重大更改。在库的生命周期中重大更改是推出一组全新功能所必需的这些功能会改变现有行为甚至更改 API。有关新版本包括不兼容更改的综合指南请参阅“ Mockito 2 中的新功能”维基页面。我们希望您喜欢 Mockito 2 0.1. Mockito Android 支持 链接图标 使用 Mockito 2.6.1 版我们提供了“原生”Android 支持。要启用 Android 支持请将“mockito-android”库作为依赖项添加到您的项目中。此工件已发布到同一个 Mockito 组织可以按如下方式导入 Android repositories {mavenCentral()}dependencies {testCompile org.mockito:mockito-core:androidTestCompile org.mockito:mockito-android:}您可以在“testCompile”范围内使用“mockito-core”工件继续在常规 VM 上运行相同的单元测试如上所示。请注意由于 Android VM 的限制您无法在 Android 上使用内联模拟生成器。如果您在 Android 上遇到模拟问题请在官方问题跟踪器上打开问题 。请提供您正在使用的 Android 版本和项目的依赖项。 0.2.无需配置的内联模拟制作 链接图标 从 2.7.6 版开始我们提供了“mockito-inline”工件无需配置 MockMaker 扩展文件即可实现内联模拟制作。要使用此功能请添加“mockito-inline”工件而不是“mockito-core”工件如下所示 repositories {mavenCentral()}dependencies {testCompile org.mockito:mockito-inline:}请注意从 5.0.0 开始内联模拟制作器成为默认模拟制作器并且该工件可能会在未来版本中被废除。 有关内联模拟制作的更多信息请参阅第 39 节。 0.3.明确设置用于内联模拟的检测 (Java 21) 链接图标 从 Java 21 开始JDK 限制了库将 Java 代理附加到其自己的 JVM 的能力。 因此如果没有明确设置启用检测inline-mock-maker 可能无法运行并且 JVM 将始终显示警告。 要在测试执行期间明确附加 Mockito需要将库的 jar 文件指定为-javaagent 执行 JVM 的参数。要在 Gradle 中启用此功能以下示例将 Mockito 添加到所有测试任务中 val mockitoAgent configurations.create(mockitoAgent)dependencies {testImplementation(libs.mockito)mockitoAgent(libs.mockito) { isTransitive false }}tasks {test {jvmArgs(-javaagent:${mockitoAgent.asPath})}}假设Mockito在版本目录中声明如下 [versions]mockito 5.14.0[libraries]mockito { module org.mockito:mockito-core, version.ref mockito }要将 Mockito 作为代理添加到 Maven 的 surefire 插件中需要进行以下配置 plugingroupIdorg.apache.maven.plugins/groupIdartifactIdmaven-dependency-plugin/artifactIdexecutionsexecutiongoalsgoalproperties/goal/goals/execution/executions/pluginplugingroupIdorg.apache.maven.plugins/groupIdartifactIdmaven-surefire-plugin/artifactIdconfigurationargLine{argLine} -javaagent:${org.mockito:mockito-core:jar}/argLine/configuration/plugin1.让我们验证一些行为 以下示例模拟了一个 List因为大多数人都熟悉该接口例如 add()、get()、clear()方法。 实际上请不要模拟 List 类。请改用真实实例。 //Lets import Mockito statically so that the code looks clearerimport static org.mockito.Mockito.*;//mock creationList mockedList mock(List.class);//using mock objectmockedList.add(one);mockedList.clear();//verificationverify(mockedList).add(one);verify(mockedList).clear();一旦创建模拟就会记住所有交互。然后您可以选择性地验证您感兴趣的任何交互。 2.来点存值怎么样 //You can mock concrete classes, not just interfacesLinkedList mockedList mock(LinkedList.class);//stubbingwhen(mockedList.get(0)).thenReturn(first);when(mockedList.get(1)).thenThrow(new RuntimeException());//following prints firstSystem.out.println(mockedList.get(0));//following throws runtime exceptionSystem.out.println(mockedList.get(1));//following prints null because get(999) was not stubbedSystem.out.println(mockedList.get(999));//Although it is possible to verify a stubbed invocation, usually its just redundant//If your code cares what get(0) returns, then something else breaks (often even before verify() gets executed).//If your code doesnt care what get(0) returns, then it should not be stubbed.verify(mockedList).get(0);默认情况下对于所有返回值的方法模拟将根据情况返回 null、原始/原始包装器值或空集合。例如对于 int/Integer 返回 0对于 boolean/Boolean 返回 false。 存根可以被覆盖例如常见的存根可以转到夹具设置但测试方法可以覆盖它。请注意覆盖存根是一种潜在的代码异味指出了过多的存根 一旦被存根该方法将始终返回一个存根值无论它被调用多少次。 最后的存根更为重要 - 当您多次使用相同的参数存根相同的方法时。换句话说存根的顺序很重要但它很少有意义例如当存根完全相同的方法调用或有时使用参数匹配器时等等。 3.参数匹配器 Mockito 以自然的 Java 风格验证参数值通过使用equals()方法。有时当需要额外的灵活性时您可能会使用参数匹配器 //stubbing using built-in anyInt() argument matcherwhen(mockedList.get(anyInt())).thenReturn(element);//stubbing using custom matcher (lets say isValid() returns your own matcher implementation):when(mockedList.contains(argThat(isValid()))).thenReturn(true);//following prints elementSystem.out.println(mockedList.get(999));//you can also verify using an argument matcherverify(mockedList).get(anyInt());//argument matchers can also be written as Java 8 Lambdasverify(mockedList).add(argThat(someString - someString.length() 5));参数匹配器允许灵活的验证或存根。 查看更多内置匹配器和自定义参数匹配器/ hamcrest 匹配器的示例。 Click here or here 有关自定义参数匹配器的信息请查看ArgumentMatcher类的 javadoc。 合理使用复杂的参数匹配。equals()偶尔使用anyX()匹配器的自然匹配风格往往会产生干净而简单的测试。有时最好重构代码以允许equals()匹配甚至实现equals()方法来帮助测试。 另外请阅读第 15 节或ArgumentCaptor类的 javadoc。 ArgumentCaptor是参数匹配器的特殊实现用于捕获参数值以进行进一步的断言。 参数匹配器警告 如果您使用参数匹配器则所有参数都必须由匹配器提供。 下面的示例显示了验证但同样适用于存根 verify(mock).someMethod(anyInt(), anyString(), eq(third argument));//above is correct - eq() is also an argument matcherverify(mock).someMethod(anyInt(), anyString(), third argument);//above is incorrect - exception will be thrown because third argument is given without an argument matcher.匹配器方法例如any()eq() 不返回匹配器。在内部它们在堆栈上记录匹配器并返回虚拟值通常为 null。此实现是由于 Java 编译器强加的静态类型安全性。结果是您不能在已验证/存根方法之外 使用any()、方法。eq() 4.验证确切的调用次数/ 至少 x / 从不 //using mockmockedList.add(once);mockedList.add(twice);mockedList.add(twice);mockedList.add(three times);mockedList.add(three times);mockedList.add(three times);//following two verifications work exactly the same - times(1) is used by defaultverify(mockedList).add(once);verify(mockedList, times(1)).add(once);//exact number of invocations verificationverify(mockedList, times(2)).add(twice);verify(mockedList, times(3)).add(three times);//verification using never(). never() is an alias to times(0)verify(mockedList, never()).add(never happened);//verification using atLeast()/atMost()verify(mockedList, atMostOnce()).add(once);verify(mockedList, atLeastOnce()).add(three times);verify(mockedList, atLeast(2)).add(three times);verify(mockedList, atMost(5)).add(three times);times(1) 是默认值。因此可以省略显式使用 times(1)。 5.使用异常来对 void 方法进行存根 doThrow(new RuntimeException()).when(mockedList).clear();//following throws RuntimeException:mockedList.clear();请参阅第 12 节中有关doThrow()|方法系列的 更多信息。 doAnswer() 6.按顺序验证 // A. Single mock whose methods must be invoked in a particular orderList singleMock mock(List.class);//using a single mocksingleMock.add(was added first);singleMock.add(was added second);//create an inOrder verifier for a single mockInOrder inOrder inOrder(singleMock);//following will make sure that add is first called with was added first, then with was added secondinOrder.verify(singleMock).add(was added first);inOrder.verify(singleMock).add(was added second);// B. Multiple mocks that must be used in a particular orderList firstMock mock(List.class);List secondMock mock(List.class);//using mocksfirstMock.add(was called first);secondMock.add(was called second);//create inOrder object passing any mocks that need to be verified in orderInOrder inOrder inOrder(firstMock, secondMock);//following will make sure that firstMock was called before secondMockinOrder.verify(firstMock).add(was called first);inOrder.verify(secondMock).add(was called second);// Oh, and A B can be mixed together at will按顺序验证是灵活的 -您不必逐一验证所有交互而只需按顺序验证您感兴趣的测试。 另外您可以创建一个 InOrder 对象仅传递与按顺序验证相关的模拟。 7.确保交互永远不会在模拟中发生 //using mocks - only mockOne is interactedmockOne.add(one);//ordinary verificationverify(mockOne).add(one);//verify that method was never called on a mockverify(mockOne, never()).add(two);8.查找冗余调用 链接图标//using mocksmockedList.add(one);mockedList.add(two);verify(mockedList).add(one);//following verification will failverifyNoMoreInteractions(mockedList);警告一些进行过大量经典的 expect-run-verify 模拟的用户倾向于非常频繁地使用verifyNoMoreInteractions()甚至在每种测试方法中都使用。 verifyNoMoreInteractions()不建议在每种测试方法中使用。 verifyNoMoreInteractions()是来自交互测试工具包的便捷断言。仅在相关时使用它。滥用它会导致过度指定、更难维护的测试。 另请参阅never()- 它更加明确可以很好地传达意图。 9.模拟创建的简写 -Mock注释 最大限度地减少重复的模拟创建代码。 使测试类更具可读性。 由于字段名称 用于识别模拟因此使验证错误更易于阅读。 public class ArticleManagerTest {Mock private ArticleCalculator calculator;Mock private ArticleDatabase database;Mock private UserProvider userProvider;private ArticleManager manager;org.junit.jupiter.api.Testvoid testSomethingInJunit5(Mock ArticleDatabase database) {重要这需要位于基类或测试运行器中的某个位置 MockitoAnnotations.openMocks(testClass);您可以使用内置运行器MockitoJUnitRunner或规则MockitoRule。对于 JUnit5 测试请参阅第 45 节中描述的 JUnit5 扩展。 更多详情请阅读MockitoAnnotations 10.对连续调用进行存根迭代器样式存根 有时我们需要对同一方法调用使用不同的返回值/异常进行存根。典型的用例可能是模拟迭代器。Mockito 的原始版本没有此功能来促进简单的模拟。例如可以使用迭代器Iterable或简单的集合来代替。这些提供了自然的存根方式例如使用真实集合。但在极少数情况下对连续调用进行存根可能会很有用 when(mock.someMethod(some arg)).thenThrow(new RuntimeException()).thenReturn(foo);//First call: throws runtime exception:mock.someMethod(some arg);//Second call: prints fooSystem.out.println(mock.someMethod(some arg));//Any consecutive call: prints foo as well (last stubbing wins).System.out.println(mock.someMethod(some arg));连续存根的替代、较短版本 when(mock.someMethod(some arg)).thenReturn(one, two, three); 警告如果不是链接.thenReturn()调用而是使用相同匹配器或参数的多个存根则每个存根将覆盖前一个存根 //All mock.someMethod(some arg) calls will return twowhen(mock.someMethod(some arg)).thenReturn(one)when(mock.someMethod(some arg)).thenReturn(two)11.使用回调进行存根 允许使用通用Answer接口进行存根。 这是 Mockito 最初未包含的另一个有争议的功能。我们建议简单地使用thenReturn()或 进行存根thenThrow()这足以测试/测试驱动任何干净简单的代码。但是如果您确实需要使用通用 Answer 接口进行存根以下是示例 when(mock.someMethod(anyString())).thenAnswer(new Answer() {public Object answer(InvocationOnMock invocation) {Object[] args invocation.getArguments();Object mock invocation.getMock();return called with arguments: Arrays.toString(args);}});//Following prints called with arguments: [foo]System.out.println(mock.someMethod(foo)); doReturn()| doThrow()| doAnswer()| doNothing()|doCallRealMethod()方法系列 对 void 方法进行存根需要采用不同的方法when(Object)因为编译器不喜欢括号内的 void 方法…… doThrow()当您想要存根带有异常的 void 方法时 使用 doThrow(new RuntimeException()).when(mockedList).clear();//following throws RuntimeException:mockedList.clear();对于任何方法 您 都可以使用doThrow()、doAnswer()、 和代替 的相应调用。当您doNothing()doReturn()doCallRealMethod()when() 存根无效方法 间谍对象上的存根方法见下文 多次存根相同的方法以在测试过程中改变模拟的行为。 when()但是您可能更喜欢在所有存根调用中 使用这些方法来代替替代方法。 阅读有关这些方法的更多信息 doReturn(Object)doThrow(Throwable...)doThrow(Class)doAnswer(Answer)doNothing()doCallRealMethod()13.监视真实物体 您可以创建真实对象的间谍。使用间谍时将调用真实方法除非方法被存根。 真正的间谍应该谨慎且偶尔地 使用例如在处理遗留代码时。 监视真实对象可能与“部分模拟”概念有关。 在 1.8 版之前Mockito 间谍并不是真正的部分模拟。原因是我们认为部分模拟是一种代码异味。在某个时候我们发现了部分模拟的合法用例第三方接口、遗留代码的临时重构。 List list new LinkedList();List spy spy(list);//optionally, you can stub out some methods:when(spy.size()).thenReturn(100);//using the spy calls *real* methodsspy.add(one);spy.add(two);//prints one - the first element of a listSystem.out.println(spy.get(0));//size() method was stubbed - 100 is printedSystem.out.println(spy.size());//optionally, you can verifyverify(spy).add(one);verify(spy).add(two);监视真实物体的重要提 有时使用存根间谍是不可能或不切实际的when(Object)。因此在使用间谍时请考虑使用doReturn| Answer|Throw()系列方法进行存根。示例 List list new LinkedList();List spy spy(list);//Impossible: real method is called so spy.get(0) throws IndexOutOfBoundsException (the list is yet empty)when(spy.get(0)).thenReturn(foo);//You have to use doReturn() for stubbingdoReturn(foo).when(spy).get(0); Mockito 不将调用委托给传递的真实实例而是实际创建它的副本。因此如果您保留真实实例并与其交互不要指望被监视者知道这些交互及其对真实实例状态的影响。推论是当在间谍上调用未存根的方法但在真实实例上没有调用时您将看不到对真实实例的任何影响。 注意 final 方法。Mockito 不会模拟 final 方法因此底线是当您监视真实对象 您尝试存根 final 方法 麻烦。此外您也无法验证这些方法。 14. 更改未存根调用的默认返回值自 1.7 版起 链接图标 您可以创建一个模拟并为其返回值指定策略。这是一项相当高级的功能通常您不需要它来编写像样的测试。但是它对于处理遗留系统很有帮助。 这是默认答案因此仅当您不存根方法调用时才会使用它。 Foo mock mock(Foo.class, Mockito.RETURNS_SMART_NULLS);Foo mockTwo mock(Foo.class, new YourOwnAnswer()); 阅读有关答案的这个有趣的实现的更多信息RETURNS_SMART_NULLS 15.捕获进一步断言的论据自 1.8.0 起 链接图标 Mockito 以自然的 Java 风格验证参数值通过使用equals()方法。这也是推荐的匹配参数的方式因为它使测试简洁明了。但在某些情况下在实际验证之后对某些参数进行断言会很有帮助。例如 ArgumentCaptorPerson argument ArgumentCaptor.forClass(Person.class);verify(mock).doSomething(argument.capture());assertEquals(John, argument.getValue().getName());警告建议将 ArgumentCaptor 与验证一起使用而不是与存根一起使用。将 ArgumentCaptor 与存根一起使用可能会降低测试的可读性因为捕获器是在断言又称验证或“then”块之外创建的。此外它可能会减少缺陷定位因为如果没有调用存根方法则不会捕获任何参数。 在某种程度上ArgumentCaptor 与自定义参数匹配器相关请参阅ArgumentMatcher类的 javadoc。这两种技术都可用于确保将某些参数传递给模拟。但是如果满足以下条件ArgumentCaptor 可能更适合 自定义参数匹配器不太可能被重用 你只需要它对参数值进行断言即可完成验证 通过自定义参数匹配器ArgumentMatcher通常更适合存根。 16.真正的部分模拟(自 1.8.0 起) 最后经过多次内部辩论和邮件列表讨论Mockito 添加了部分模拟支持。以前我们认为部分模拟是代码异味。然而我们发现了部分模拟的合法用例。 在 1.8 版之前 spy()它不会生成真正的部分模拟这会让一些用户感到困惑。阅读有关监视的更多信息此处或方法的 javadoc spy(Object)。 //you can create partial mock with spy() method: List list spy(new LinkedList()); //you can enable partial mock capabilities selectively on mocks: Foo mock mock(Foo.class); //Be sure the real implementation is ‘safe’. //If real implementation throws exceptions or depends on specific state of the object then you’re in trouble. when(mock.someMethod()).thenCallRealMethod(); 像往常一样您将阅读部分模拟警告面向对象编程通过将复杂性划分为单独的、特定的 SRPy 对象来处理复杂性。部分模拟如何适应这种范式嗯它就是不适合…部分模拟通常意味着复杂性已转移到同一对象上的不同方法。在大多数情况下这不是您想要设计应用程序的方式。 然而在极少数情况下部分模拟会派上用场处理无法轻易更改的代码第三方接口、遗留代码的临时重构等。但是我不会对新的、测试驱动的和精心设计的代码使用部分模拟。 17.重置模拟自 1.8.0 起 链接图标 使用此方法可能表明测试质量不佳。通常您不需要重置模拟只需为每种测试方法创建新的模拟即可。 请reset()考虑编写简单、小而有针对性的测试方法而不是冗长、过度指定的测试。 第一个潜在的代码异味是reset()在测试方法的中间。这可能意味着你测试太多了。遵循测试方法的提示“请让我们保持小规模并专注于单一行为”。mockito 邮件列表中有几个关于它的帖子。 我们添加方法的唯一原因reset()是使其能够与容器注入模拟一起使用。有关更多信息请参阅常见问题解答此处。 不要伤害自己。 reset()在测试方法的中间存在代码异味您可能测试太多了。 List mock mock(List.class);when(mock.size()).thenReturn(10);mock.add(1);reset(mock);//at this point the mock forgot any interactions and stubbing18.故障排除和验证框架使用情况自 1.8.0 起 链接图标 首先如果遇到任何问题我建议您阅读 Mockito 常见问题解答 https://github.com/mockito/mockito/wiki/FAQ 如果有任何疑问您也可以发帖至 mockito 邮件列表 https://groups.google.com/group/mockito 接下来你应该知道 Mockito 会验证你是否始终正确使用它。但是有一个问题所以请阅读 javadocvalidateMockitoUsage() 19.行为驱动开发的别名自1.8.0起 行为驱动开发编写测试的风格使用//given //when //then注释作为测试方法的基本部分。这正是我们编写测试的方式我们热烈鼓励您这样做 从这里开始了解 BDD https: //en.wikipedia.org/wiki/Behavior-driven_development 问题是当前具有when字的规范角色的存根 API 无法与//given //when //then注释很好地集成。这是因为存根属于测试的给定组件而不是测试的whenBDDMockito组件。因此类引入了一个别名以便您使用BDDMockito.given(Object)方法存根方法调用。现在它真的很好地与BDD 风格测试的给定组件集成 测试可能如下所示 import static org.mockito.BDDMockito.*;Seller seller mock(Seller.class);Shop shop new Shop(seller);public void shouldBuyBread() throws Exception {//givengiven(seller.askForBread()).willReturn(new Bread());//whenGoods goods shop.buyBread();//thenassertThat(goods, containBread());} 20.可序列化模拟自 1.8.1 版起 模拟可以序列化。利用此功能您可以在需要依赖项可序列化的地方使用模拟。 警告这在单元测试中很少使用。 该行为是针对具有不可靠外部依赖关系的 BDD 规范的特定用例实现的。这是在 Web 环境中来自外部依赖关系的对象被序列化以在各层之间传递。 要创建可序列化的模拟使用MockSettings.serializable() List serializableMock mock(List.class, withSettings().serializable()); 假设该类满足 所有正常 序列化要求 则可以对模拟进行序列化。 使真实对象间谍可序列化需要付出更多努力因为 spy(…) 方法没有接受 MockSettings 的重载版本。不用担心您几乎不会使用它。 ListObject list new ArrayListObject();ListObject spy mock(ArrayList.class, withSettings().spiedInstance(list).defaultAnswer(CALLS_REAL_METHODS).serializable());新增注释Captor、 Spy、 InjectMocks自 1.8.3 起 版本 1.8.3 带来了新的注释有时可能会有帮助 Captor简化了创建ArgumentCaptor - 当捕获的参数是一个讨厌的泛型类并且你想避免编译器警告时很有用 Spy-你可以改用它spy(Object)。 InjectMocks-自动将模拟或间谍字段注入到测试对象中。 请注意InjectMocks也可以与 注释结合使用Spy这意味着 Mockito 会将模拟注入到测试中的部分模拟中。这种复杂性也是您应该只在万不得已的情况下使用部分模拟的另一个很好的理由。请参阅第 16 点关于部分模拟的内容。 所有新注释仅在 上处理MockitoAnnotations.openMocks(Object)。就像 Mock注释一样您可以使用内置的运行器MockitoJUnitRunner或规则 MockitoRule。 22.超时验证(自 1.8.5 起) 允许超时验证。它会导致验证等待指定的一段时间以进行所需的交互而不是在尚未发生交互时立即失败。可能对并发条件下的测试有用。 这个特性应该很少使用——找出更好的方法来测试你的多线程系统。 尚未实现与 InOrder 验证配合使用。 例子 //passes when someMethod() is called no later than within 100 ms//exits immediately when verification is satisfied (e.g. may not wait full 100 ms)verify(mock, timeout(100)).someMethod();//above is an alias to:verify(mock, timeout(100).times(1)).someMethod();//passes as soon as someMethod() has been called 2 times under 100 msverify(mock, timeout(100).times(2)).someMethod();//equivalent: this also passes as soon as someMethod() has been called 2 times under 100 msverify(mock, timeout(100).atLeast(2)).someMethod(); 23.自动实例化和构造Spies函数 InjectMocks注入优点自 1.9.0 起 Mockito 现在将尝试实例化 Spy并将InjectMocks使用构造函数注入、setter注入或字段注入来实例化字段。 要利用此功能您需要使用MockitoAnnotations.openMocks(Object)、MockitoJUnitRunner 或MockitoRule。 在 javadoc 中了解有关可用技巧和注入规则的更多信息InjectMocks //instead:Spy BeerDrinker drinker new BeerDrinker();//you can write:Spy BeerDrinker drinker;//same applies to InjectMocks annotation:InjectMocks LocalPub;24.单行存根(自 1.9.0 起) Mockito 现在允许您在存根时创建模拟。基本上它允许在一行代码中创建一个存根。这有助于保持测试代码的整洁。例如可以在测试中的字段初始化时创建和存根一些无聊的存根 public class CarTest {Car boringStubbedCar when(mock(Car.class).shiftGear()).thenThrow(EngineNotStarted.class).getMock();Test public void should... {}25.验证忽略存根自 1.9.0 起 Mockito 现在允许为了验证而忽略存根。有时与verifyNoMoreInteractions()或 验证结合使用时很有用inOrder()。有助于避免对存根调用进行冗余验证 - 通常我们对验证存根不感兴趣。 警告ignoreStubs()可能会导致过度使用 verifyNoMoreInteractions(ignoreStubs(…)); 请记住Mockito 不建议对每个测试都进行轰炸verifyNoMoreInteractions() 原因在 javadoc 中有所概述verifyNoMoreInteractions(Object…) 一些例子 verify(mock).foo();verify(mockTwo).bar();//ignores all stubbed methods:verifyNoMoreInteractions(ignoreStubs(mock, mockTwo));//creates InOrder that will ignore stubbedInOrder inOrder inOrder(ignoreStubs(mock, mockTwo));inOrder.verify(mock).foo();inOrder.verify(mockTwo).bar();inOrder.verifyNoMoreInteractions(); 高级示例和更多详细信息请参阅 javadocignoreStubs(Object…) 26.模拟细节(2.2.x 版改进) Mockito 提供 API 来检查模拟对象的详细信息。此 API 对高级用户和模拟框架集成者很有用。 //To identify whether a particular object is a mock or a spy:Mockito.mockingDetails(someObject).isMock();Mockito.mockingDetails(someObject).isSpy();//Getting details like type to mock or default answer:MockingDetails details mockingDetails(mock);details.getMockCreationSettings().getTypeToMock();details.getMockCreationSettings().getDefaultAnswer();//Getting invocations and stubbings of the mock:MockingDetails details mockingDetails(mock);details.getInvocations();details.getStubbings();//Printing all interactions (including stubbing, unused stubs)System.out.println(mockingDetails(mock).printInvocations());有关详细信息请参阅 javadoc MockingDetails。 27.将调用委托给真实实例(自 1.9.5 起) 对于难以使用常规间谍 API 进行模拟或监视的对象的间谍或部分模拟非常有用。自 Mockito 1.10.11 起委托可能与模拟属于同一类型也可能不属于同一类型。如果类型不同则需要在委托类型上找到匹配的方法否则会引发异常。此功能的可能用例 带有接口的最终类 已经自定义代理对象 具有 finalize 方法的特殊对象即避免执行 2 次 与普通间谍的区别 常规间谍 ( spy(Object)) 包含来自被监视实例的所有状态并且方法在间谍上被调用。被监视实例仅在模拟创建时用于从中复制状态。如果您在常规间谍上调用一个方法并且它在内部调用此间谍上的其他方法则这些调用会被记住以进行验证并且可以有效地对它们进行存根。 委托的模拟只是将所有方法委托给委托。委托始终被使用因为方法被委托给它。如果您在委托的模拟上调用一个方法并且它在内部调用此模拟上的其他方法则这些调用不会被记住以进行验证存根也不会对它们产生影响。委托的模拟不如常规间谍强大但在无法创建常规间谍时很有用。 请参阅文档中的更多信息AdditionalAnswers.delegatesTo(Object)。 MockMakerAPI自 1.9.5 起 在 Google Android 开发人员的要求和补丁的推动下Mockito 现在提供了一个扩展点允许替换代理生成引擎。默认情况下Mockito 使用Byte Buddy 创建动态代理。 该扩展点适用于想要扩展 Mockito 的高级用户。例如现在可以借助dexmaker使用 Mockito 进行 Android 测试。 有关更多详细信息、动机和示例请参阅文档MockMaker。 BDD 风格验证(自 1.10.0 起) 链接图标 通过使用 BDD then关键字 开始验证启用行为驱动开发 (BDD) 样式验证。 given(dog.bark()).willReturn(2);// when...then(person).should(times(2)).ride(bike);有关详细信息和示例请参阅BDDMockito.then(Object) 30.监视或模拟抽象类自 1.10.12 起在 2.7.13 和 2.7.14 中进一步增强 现在可以方便地监视抽象类。请注意过度使用间谍程序可能会引起代码设计问题请参阅spy(Object)。 以前只能监视对象实例。新 API 使得在创建模拟实例时可以使用构造函数。这对于模拟抽象类特别有用因为用户不再需要提供抽象类的实例。目前仅支持无参数构造函数如果不够请告诉我们。 //convenience API, new overloaded spy() method:SomeAbstract spy spy(SomeAbstract.class);//Mocking abstract methods, spying default methods of an interface (only available since 2.7.13)FunctionFoo, Bar function spy(Function.class);//Robust API, via settings builder:OtherAbstract spy mock(OtherAbstract.class, withSettings().useConstructor().defaultAnswer(CALLS_REAL_METHODS));//Mocking an abstract class with constructor arguments (only available since 2.7.14)SomeAbstract spy mock(SomeAbstract.class, withSettings().useConstructor(arg1, 123).defaultAnswer(CALLS_REAL_METHODS));//Mocking a non-static inner abstract class:InnerAbstract spy mock(InnerAbstract.class, withSettings().useConstructor().outerInstance(outerInstance).defaultAnswer(CALLS_REAL_METHODS)); 更多信息请参阅MockSettings.useConstructor(Object…)。 31. Mockito 模拟可以跨类加载器进行序列化/反序列化自 1.10.0 起 Mockito 引入了跨类加载器的序列化。与任何其他形式的序列化一样模拟层次结构中的所有类型都必须可序列化包括答案。由于此序列化模式需要做更多工作因此这是一个可选设置。 // use regular serializationmock(Book.class, withSettings().serializable());// use serialization across classloadersmock(Book.class, withSettings().serializable(ACROSS_CLASSLOADERS));详情请参阅MockSettings.serializable(SerializableMode)。 32.通过深存根提供更好的通用支持自 1.10.0 起 深度存根已得到改进可以查找类中可用的通用信息。这意味着可以使用此类类而无需模拟行为。 class Lines extends ListLine {// ...}lines mock(Lines.class, RETURNS_DEEP_STUBS);// Now Mockito understand this is not an Object but a LineLine line lines.iterator().next();请注意在大多数情况下返回模拟的模拟是错误的。 33. Mockito JUnit 规则自 1.10.17 起 Mockito 现在提供 JUnit 规则。到目前为止 在 JUnit 中有两种方法可以初始化由 Mockito 注释注释的字段例如、、等。 MockSpyInjectMocks 使用注释 JUnit 测试类RunWith(MockitoJUnitRunner.class) 方法MockitoAnnotations.openMocks(Object)中调用Before 现在您可以选择使用一条规则 RunWith(YetAnotherRunner.class)public class TheTest {Rule public MockitoRule mockito MockitoJUnit.rule();// ...}更多信息请参阅MockitoJUnit.rule()。 34.打开或关闭插件 (自1.10.15 起) mockito 中正在酝酿一项功能允许切换 mockito 插件。更多信息请见此处PluginSwitch。 35.自定义验证失败信息(自 2.1.0 起) 链接图标 允许指定在验证失败时打印的自定义消息。 例子 // will print a custom message on verification failureverify(mock, description(This will print on failure)).someMethod();// will work with any verification modeverify(mock, times(2).description(someMethod should be called twice)).someMethod(); Java 8 Lambda Matcher 支持自 2.1.0 起 您可以使用 Java 8 lambda 表达式ArgumentMatcher来减少对 的依赖ArgumentCaptor。如果您需要验证模拟函数调用的输入是否正确那么您通常会使用ArgumentCaptor来查找使用的操作数然后对它们进行后续断言。虽然对于复杂的示例这可能很有用但它也很冗长。 编写 lambda 来表达匹配非常简单。当与 argThat 结合使用时函数的参数将作为强类型对象传递给 ArgumentMatcher因此可以对其进行任何操作。 例子 // verify a list only had strings of a certain length added to it// note - this will only compile under Java 8verify(list, times(2)).add(argThat(string - string.length() 5));// Java 7 equivalent - not as neatverify(list, times(2)).add(argThat(new ArgumentMatcherString(){public boolean matches(String arg) {return arg.length() 5;}}));// more complex Java 8 example - where you can specify complex verification behaviour functionallyverify(target, times(1)).receiveComplexObject(argThat(obj - obj.getSubObject().get(0).equals(expected)));// this can also be used when defining the behaviour of a mock under different inputs// in this case if the input list was fewer than 3 items the mock returns nullwhen(mock.someMethod(argThat(list - list.size()3))).thenReturn(null);Java 8 自定义答案支持自 2.1.0 起 由于该Answer接口只有一个方法因此在 Java 8 中已经可以使用 lambda 表达式来实现它适用于非常简单的情况。您需要使用方法调用的参数越多您就越需要对 中的参数进行类型转换InvocationOnMock。 例子 // answer by returning 12 every timedoAnswer(invocation - 12).when(mock).doSomething();// answer by using one of the parameters - converting into the right// type as your go - in this case, returning the length of the second string parameter// as the answer. This gets long-winded quickly, with casting of parameters.doAnswer(invocation - ((String)invocation.getArgument(1)).length()).when(mock).doSomething(anyString(), anyString(), anyString());为了方便起见可以编写自定义答案/操作这些答案/操作使用方法调用的参数就像 Java 8 lambda 一样。即使在 Java 7 及更低版本中这些基于类型化接口的自定义答案也可以减少样板。特别是这种方法将使测试使用回调的函数变得更容易。方法AdditionalAnswers.answer(Answer1)} 和AdditionalAnswers.answerVoid(VoidAnswer1) 可用于创建答案。它们依赖于 org.mockito.stubbing 中支持最多 5 个参数的答案的相关答案接口。 例子 // Example interface to be mocked has a function like:void execute(String operand, Callback callback);// the example callback has a function and the class under test// will depend on the callback being invokedvoid receive(String item);// Java 8 - style 1doAnswer(AdditionalAnswers.String,CallbackanswerVoid((operand, callback) - callback.receive(dummy))).when(mock).execute(anyString(), any(Callback.class));// Java 8 - style 2 - assuming static import of AdditionalAnswersdoAnswer(answerVoid((String operand, Callback callback) - callback.receive(dummy))).when(mock).execute(anyString(), any(Callback.class));// Java 8 - style 3 - where mocking function to is a static member of test classprivate static void dummyCallbackImpl(String operation, Callback callback) {callback.receive(dummy);}doAnswer(answerVoid(TestClass::dummyCallbackImpl)).when(mock).execute(anyString(), any(Callback.class));// Java 7doAnswer(answerVoid(new VoidAnswer2String, Callback() {public void answer(String operation, Callback callback) {callback.receive(dummy);}})).when(mock).execute(anyString(), any(Callback.class));// returning a value is possible with the answer() function// and the non-void version of the functional interfaces// so if the mock interface had a method likeboolean isSameString(String input1, String input2);// this could be mocked// Java 8doAnswer(AdditionalAnswers.Boolean,String,Stringanswer((input1, input2) - input1.equals(input2))).when(mock).execute(anyString(), anyString());// Java 7doAnswer(answer(new Answer2String, String, String() {public String answer(String input1, String input2) {return input1 input2;}})).when(mock).execute(anyString(), anyString());38.元数据和泛型类型保留自 2.1.0 起 Mockito 现在会保留模拟方法和类型的注释以及通用元数据。以前模拟类型不会保留类型的注释除非它们被明确继承并且永远不会保留方法的注释。因此现在以下条件成立 MyAnnotationclass Foo {ListString bar() { ... }}Class? mockType mock(Foo.class).getClass();assert mockType.isAnnotationPresent(MyAnnotation.class);assert mockType.getDeclaredMethod(bar).getGenericReturnType() instanceof ParameterizedType;使用 Java 8 时Mockito 现在还会保留类型注释。这是默认行为如果使用 替代方法则可能不成立。MockMaker 39.模拟最终类型、枚举和最终方法自 2.1.0 起 Mockito 现在默认支持模拟 final 类和方法。这是一项了不起的改进体现了 Mockito 不断追求改善测试体验的决心。我们的目标是让 Mockito “只适用于” final 类和方法。以前它们被认为是不可模拟的阻止用户模拟。从 5.0.0 开始此功能默认启用。 此替代模拟生成器结合使用了 Java 检测 API 和子类化而不是创建新类来表示模拟。这样就可以模拟最终类型和方法。 在 5.0.0 之前的版本中此模拟生成器默认处于关闭状态因为它基于完全不同的模拟机制需要更多来自社区的反馈。它可以通过 mockito 扩展机制明确激活只需在类路径中创建一个 /mockito-extensions/org.mockito.plugins.MockMaker包含值的文件即可mock-maker-inline。 为方便起见Mockito 团队提供了一个预配置此模拟生成器的工件。不要使用 mockito-core工件而是在项目中包含mockito-inline工件。请注意一旦将最终类和方法的模拟集成到默认模拟生成器中此工件可能会停用。 关于这个模拟制作器有几点值得注意 模拟最终类型和枚举与以下模拟设置不兼容 明确序列化支持withSettings().serializable() 额外接口withSettings().extraInterfaces() 有些方法不能被模拟 包可见的方法java.* native方法 此模拟生成器是围绕 Java Agent 运行时附件设计的这需要兼容的 JVM即 JDK或 Java 9 VM的一部分。然而当在 Java 9 之前的非 JDK VM 上运行时可以 在启动 JVM 时 使用参数手动添加Byte Buddy Java 代理 jar 。-javaagent 如果你对该功能的更多细节感兴趣请阅读 org.mockito.internal.creation.bytebuddy.InlineByteBuddyMockMaker 使用“更严格”的 Mockito 提高生产力并进行更清晰的测试自 2. 起 要快速了解“更严格”的 Mockito 如何提高您的工作效率并使您的测试更清洁请参阅 MockitoRule.strictness(Strictness)使用JUnit4 规则进行严格存根 -Strictness.STRICT_STUBS 使用 JUnit4 Runner 进行严格存根 -MockitoJUnitRunner.Strict 使用 JUnit5 扩展进行严格存根 -org.mockito.junit.jupiter.MockitoExtension 使用 TestNG Listener MockitoTestNGListener进行严格存根 如果不能使用运行器/规则则进行严格存根 -MockitoSession 不必要的存根检测MockitoJUnitRunner 存根参数不匹配警告记录在MockitoHint Mockito 默认是一个“松散”的模拟框架。模拟可以进行交互而无需事先设定任何期望。这是有意为之它通过强制用户明确说明他们想要存根/验证的内容来提高测试质量。它也非常直观、易于使用并且与干净测试代码的“给定”、“何时”、“然后”模板完美融合。这也不同于过去的经典模拟框架它们默认是“严格”的。 默认情况下“松散”使得 Mockito 测试有时更难调试。在某些情况下错误配置的存根例如使用错误的参数会迫使用户使用调试器运行测试。理想情况下测试失败是显而易见的不需要调试器来识别根本原因。从 2.1 版开始Mockito 获得了新功能推动框架走向“严格”。我们希望 Mockito 提供出色的可调试性同时不失去其核心模拟风格并针对直观性、明确性和干净的测试代码进行了优化。 帮助 Mockito试用新功能向我们提供反馈加入有关 Mockito 严格性的讨论GitHub 问题 769。 用于框架集成的高级公共 API自 2.10. 起 2017 年夏天我们决定 Mockito 应该提供更好的 API 以实现高级框架集成。新 API 不适用于想要编写单元测试的用户。它适用于需要使用一些自定义逻辑扩展或包装 Mockito 的其他测试工具和模拟框架。在设计和实施过程中问题 1110我们开发并更改了以下公共 API 元素 新MockitoPlugins功能 - 使框架集成商能够访问默认的 Mockito 插件。当需要实现自定义插件MockMaker 并将某些行为委托给默认的 Mockito 实现时此功能非常有用。 新建- 创建 Mockito 稍后使用的模拟设置的不可变视图。对于在实现自定义时MockSettings.build(Class)使用或创建调用很有用。 InvocationFactoryMockHandler 新的MockingDetails.getMockHandler()- 其他框架可以使用模拟处理程序以编程方式模拟对模拟对象的调用。 新建MockHandler.getMockSettings()——有助于获取创建模拟对象的设置。 New InvocationFactory- 提供创建Invocation对象实例的方法。对于需要以编程方式模拟模拟对象上的方法调用的框架集成非常有用。 新MockHandler.getInvocationContainer()- 提供对没有方法标记接口的调用容器对象的访问。需要容器来隐藏内部实现并避免将其泄露给公共 API。 已更改Stubbing- 它现在扩展了Answer接口。它向后兼容因为 Stubbing 接口不可扩展请参阅NotExtensible。对于我们的用户来说这种更改应该是无缝的。 NotExtensible- 公共注释向用户表明她不应提供给定类型的自定义实现。帮助框架集成商和我们的用户了解如何安全地使用 Mockito API。 您有反馈吗请在问题 1110 中留下评论。新的集成 API监听验证开始事件自 2.11. 起 Spring Boot 等框架集成需要公共 API 来解决双代理用例问题 1191。我们添加了 新的VerificationStartedListener并VerificationStartedEvent 启用框架集成器来替换模拟对象进行验证。主要驱动用例是Spring Boot集成。有关详细信息请参阅 Javadoc VerificationStartedListener。 新的公共方法MockSettings.verificationStartedListeners(VerificationStartedListener…) 允许在模拟创建时提供验证启动的监听器。 添加了新的便捷方法MockingDetails.getMock()使MockingDetailsAPI 更加完整。我们发现此方法在实施过程中非常有用。新的集成 APIMockitoSession可供测试框架使用自 2.15. 起 链接图标 MockitoSessionBuilder并MockitoSession通过测试框架集成例如 JUnit进行了增强以实现重用MockitoRule MockitoSessionBuilder.initMocks(Object…)允许传入多个测试类实例以初始化使用 Mockito 注释注释的字段例如Mock。当测试使用多个例如嵌套的测试类实例时此方法对于高级框架集成例如 JUnit Jupiter很有用。 MockitoSessionBuilder.name(String)允许从测试框架传递一个名称到 MockitoSession用于在Strictness.WARN使用时打印警告。 MockitoSessionBuilder.logger(MockitoSessionLogger)可以自定义用于在完成模拟时产生的提示/警告的记录器对于测试和连接测试框架如 JUnit Jupiter提供的报告功能很有用。 MockitoSession.setStrictness(Strictness)允许改变MockitoSession 一次性场景的严格性例如它可以为类中的所有测试配置默认严格性但可以更改单个或几个测试的严格性。 MockitoSession.finishMocking(Throwable)是为了避免由于存在多个竞争故障而可能产生的混淆而添加的。当提供的故障 不是时它将禁用某些检查null。 44. 已弃用org.mockito.plugins.InstantiatorProvider因为它泄漏了内部 API。它被替换为org.mockito.plugins.InstantiatorProvider2 (Since 2.15.4) 链接图标 org.mockito.plugins.InstantiatorProvider 返回了一个内部 API。因此它已被弃用并被 取代InstantiatorProvider2。org.mockito.plugins.InstantiatorProvider 现已被删除。 45.新的 JUnit Jupiter (JUnit5) 扩展 要与 JUnit Jupiter (JUnit5) 集成请使用 org.mockito:mockito-junit-jupiter 工件。有关集成用法的更多信息请参阅的 JavaDocMockitoExtension。 46. 新功能Mockito.lenient()和MockSettings.lenient()方法 (自 2.20.0 起) 链接图标 自早期的 Mockito 2 开始就提供了严格的存根功能。它非常有用因为它可以推动更清晰的测试并提高生产率。严格的存根会报告不必要的存根检测存根参数不匹配并使测试更加 DRYStrictness.STRICT_STUBS。这需要权衡在某些情况下您可能会从严格的存根中得到假阴性。为了解决这些问题您现在可以将特定存根配置为宽松的而所有其他存根和模拟都使用严格的存根 lenient().when(mock.foo()).thenReturn(“ok”); 如果您希望给定模拟上的所有存根都宽松则可以相应地配置模拟 Foo mock Mockito.mock(Foo.class, withSettings().lenient()); 更多信息请参阅lenient()。通过打开 GitHub 问题进行讨论让我们知道您如何发现新功能 47.用于清除内联模拟中的模拟状态的新 API自 2.25.0 起 在某些特定的罕见情况下问题#1619内联模拟会导致内存泄漏。没有干净的方法可以完全缓解此问题。因此我们引入了一个新的 API 来明确清除模拟状态仅在内联模拟中有意义。请参阅中的示例用法MockitoFramework.clearInlineMocks()。如果您有反馈或更好的解决问题的想法请联系我们。 48.模拟静态方法自 3.4.0 起 使用内联模拟生成器时可以模拟当前线程和用户定义范围内的静态方法调用。这样Mockito 可确保并发和顺序运行的测试不会相互干扰。为确保静态模拟保持临时性建议在 try-with-resources 构造中定义范围。在以下示例中类型Foo的静态方法将返回foo除非被模拟 assertEquals(foo, Foo.method());try (MockedStatic mocked mockStatic(Foo.class)) {mocked.when(Foo::method).thenReturn(bar);assertEquals(bar, Foo.method());mocked.verify(Foo::method);}assertEquals(foo, Foo.method());由于静态模拟的范围已定义一旦范围被释放它将返回其原始行为。要定义模拟行为并验证静态方法调用请使用MockedStatic返回的。 49.模拟对象构造自 3.5.0 起 使用内联模拟生成器时可以在当前线程和用户定义范围内对构造函数调用生成模拟。这样Mockito 可确保并发和顺序运行的测试不会相互干扰。为确保构造函数模拟保持临时性建议在 try-with-resources 构造中定义范围。在以下示例中类型Foo的构造将生成模拟 assertEquals(foo, new Foo().method());try (MockedConstruction mocked mockConstruction(Foo.class)) {Foo foo new Foo();when(foo.method()).thenReturn(bar);assertEquals(bar, foo.method());verify(foo).method();}assertEquals(foo, new Foo().method()); 由于模拟构造的范围已定义一旦范围被释放对象构造将返回其原始行为。要定义模拟行为并验证方法调用请使用MockedConstruction返回的。 50.避免在仅模拟接口时生成代码自 3.12.2 起 链接图标 JVM 提供了Proxy创建接口类型动态代理的功能。对于大多数应用程序Mockito 必须能够模拟默认模拟生成器支持的类甚至模拟内联模拟生成器支持的最终类。要创建此类模拟Mockito 需要设置不同的 JVM 功能并且必须应用代码生成。如果只模拟接口则可以选择使用基于 API 的 org.mockito.internal.creation.proxy.ProxyMockMakerProxy 这可以避免其他模拟生成器的多种开销但也将模拟限制在接口上。可以通过 mockito 扩展机制明确激活此模拟生成器只需在类路径中创建一个 /mockito-extensions/org.mockito.plugins.MockMaker包含值的文件即可mock-maker-proxy。 51.将类标记为不可模拟自 4.1.0 起 链接图标 在某些情况下模拟类/接口可能会导致意外的运行时行为。例如java.util.List 考虑到接口所施加的要求模拟很困难。这意味着在运行时根据应用程序在列表中调用的方法您的模拟可能会以违反接口的方式运行。 对于您拥有的任何难以模拟的类/接口您现在可以使用 标记该类DoNotMock。有关注释的使用以及如何发送您自己的注释以避免对测试工件的编译时依赖请参阅其 JavaDoc。 Mock 注释和方法 的新严格性属性MockSettings.strictness()自 4.6.0 起 现在您可以使用 Mock 注释严格性属性或使用 MockSettings.strictness() 自定义单个模拟的严格性级别。如果您希望所有模拟都严格但其中一个模拟宽松那么这将非常有用。 Mock(strictness Strictness.LENIENT)Foo mock;// using MockSettings.withSettings()Foo mock Mockito.mock(Foo.class, withSettings().strictness(Strictness.WARN));为单个模拟指定模拟制作者自 4.8.0 起 您可能会遇到只想对特定测试使用不同的模拟生成器的情况。在这种情况下您可以暂时使用MockSettings.mockMaker(String)和Mock.mockMaker() 为导致问题的特定模拟指定模拟生成器。 // using annotationMock(mockMaker MockMakers.SUBCLASS)Foo mock;// using MockSettings.withSettings()Foo mock Mockito.mock(Foo.class, withSettings().mockMaker(MockMakers.SUBCLASS));不指定类别的模拟/监视自 4.10.0 起 现在您可以调用不带参数的方法或方法而不是mock(Class)使用Classspy(Class)参数 mock()spy() Foo foo Mockito.mock();Bar bar Mockito.spy();Mockito 将自动检测所需的类。 仅当您将结果分配给具有显式类型的变量或字段时它才有效mock()。spy()对于隐式类型Java 编译器无法自动确定模拟的类型您需要Class显式传入。 使用断言进行验证自 5.3.0 起 链接图标 ArgumentCaptor为了在验证过程中验证参数现在可以使用 } 而不是使用 捕获它们ArgumentMatchers.assertArg(Consumer) verify(serviceMock).doStuff(assertArg(param - {assertThat(param.getField1()).isEqualTo(foo);assertThat(param.getField2()).isEqualTo(bar);}));
http://www.w-s-a.com/news/960257/

相关文章:

  • 网站建立风格二手交易网站开发可参考文献
  • 成都微信网站开发优化大师优化项目有哪些
  • 哪个网站做自考题目免费郑州网站建设公司qq
  • 地方性的网站有前途顺的网络做网站好不好
  • 学校申请建设网站的原因不要网站域名
  • 推荐响应式网站建设子域名查询工具
  • 如何建设学校的微网站广告推广是什么
  • 设计类专业哪个就业前景好网站建设seoppt
  • 济南建站公司网站网站友链查询源码
  • 校园失物招领网站建设涪陵网站建设公司
  • 怎么做盗号网站手机网站建设需要租用什么科目
  • 成品网站是什么意思沈阳seo推广
  • 购物网站后台流程图昆明官网seo技术
  • 创建自己网站全网零售管理系统
  • 江苏省建设厅网站建筑电工证wordpress收费插件大全
  • 北京中国建设银行招聘信息网站宁德蕉城住房和城乡建设部网站
  • 泉州做网站优化哪家好wordpress站点预览
  • 创建门户网站一页网站首页图如何做
  • 服装手机商城网站建设sns社交网站有哪些
  • 无锡工程建设招标网站怎么自己建设公司网站
  • 哪个网站可以学做咸菜安卓软件开发需要学什么软件
  • 自有网站建设的团队遂宁市建设局网站
  • 网站建设哪个好一些网站内容导出
  • 什么网站的页面做的比较好看网上做平面设计的网站
  • 网站建设单选网站建设学校培训学校
  • 可以做app的网站logo设计在线生成免费标小智
  • 网站变更备案做酒类网站
  • 网站必须要备案吗东莞市非凡网站建设
  • 太原建网站公司网站设计的流程是怎样的
  • 网站开发交易平台北京网站建设的价格低