前言

What's mocking and its benefits

Mocking is an integral part of unit testing. Although you can run your unit tests without use of mocking but it will drastically slow down the executing time of unit tests and also will be dependent on external resources. In this article we will explain mocking and different benefits that we can achieve by introducing mocking in our unit tests. - (Introduction to Mocking

好文评鉴

Mocking and Isolation in Unit Testing - Vagif Abilov

Introduction

Rapid growth of test frameworks and tools accompanied by books and articles on various aspects of TDD does not only bring solutions to developers' needs, it also brings confusion. Dummy objects, fakes, stubs, mocks – what should we use? And when? And why? What complicates the situation is that most of developers can not afford to invest much time into test methodology studies – their efficiency is usually measured using different criteria, and although the management of modern software projects is well aware of how important is to surround production code with maintainable test environment, they are not easy to convince to dedicate hundreds of working hours into updating system tests due to a paradigm shift.

So we have to try to get things right with minimal effort, make our decisions based on simple practical criteria. And in the end, it is the compactness, readability and maintainability of our code that matters. So what I'm going to do is to write a very simple class with dependencies on a database, write an integration test that illustrates how inconvenient is to drag these dependencies with such simple class, and then show how to break the dependencies: first using traditional mocking with recorded expectations, and then using more lightweight arrange-act-assert approach that does not require accurate reconstruction of mocked object behavior.

The code that illustrates this article uses Typemock Isolator framework.

1. Code to test: calculating insurance price group

Our goal is to write tests for an algorithm that calculates a price group for car insurance. For simplicity the algorithm is based only on customer age: people under 16 fall into a Child group (with great chances to be refused), people between 16 and 25 belong to Junior group, age of 25 to 65 puts them into an Adult group, and anyone older is considered to be a Senior. Here's the code:

public class CarInsurance
{
public PriceGroup GetCustomerPriceGroup(int customerID)
{
DataLayer dataLayer = new DataLayer();
dataLayer.OpenConnection();
Customer customer = dataLayer.GetCustomer(customerID);
dataLayer.CloseConnection();
DateTime now = DateTime.Now;
if (customer.DateOfBirth > now.AddYears(-16))
return PriceGroup.Child;
else if (customer.DateOfBirth > now.AddYears(-25))
return PriceGroup.Junior;
else if (customer.DateOfBirth < now.AddYears(-65))
return PriceGroup.Senior;
else
return PriceGroup.Adult;
}
}

The code is simple, but note a potential test challenge. The method GetCustomerPriceGroup does not take an instance of a Customer type, instead it requires a customer ID to be sent as an argument, and database lookup occurs inside the method. So if we don't use any stubs or mocks, we'll have to create a Customer record and then pass it's ID to GetCustomerPriceGroup method.

Another issue is a visibility of Customer constructor. This is how it is defined:

public class Customer
{
internal Customer()
{
}
public int CustomerID { get; internal set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime DateOfBirth { get; set; }
}

Our tests will be written in a different assembly, so we won't be able to create a Customer object directly. It is supposed to be instantiated only from a DataLayer object that is defined below:
public class DataLayer

public int CreateCustomer(string firstName, string lastName, DateTime dateOfBirth)
{
throw new Exception("Unable to connect to a database");
} public Customer GetCustomer(int customerID)
{
throw new Exception("Unable to connect to a database");
} internal void OpenConnection()
{
throw new Exception("Unable to connect to a database");
} internal void CloseConnection()
{
throw new Exception("Unable to connect to a database");
}

Of course, the real DataLayer definition won't throw exceptions: it will perform a series of well-known steps: obtaining connection strings, connecting to a database, implementing IDisposable interface, executing SQL queries and retrieving results. But for us it does not matter because the purpose of our work is to write and run test code without connecting to a database. Therefore it makes no difference if I instantiate and execute an SqlCommand or simply throw an exception: we don't expect a database to be reachable, we haven't created required tables and stored procedures, and any attempt to execute an SQL query will fail. So let's not focus on database code, we assume other developers will fix it.

2. Integration tests

So we are ready to test GetCustomerPriceGroup method. Here's how the body of a test might look:

var carInsurance = new CarInsurance();
PriceGroup priceGroup = carInsurance.GetCustomerPriceGroup(1);
Assert.AreEqual(PriceGroup.Adult, priceGroup, "Incorrect price group");

No, that won't work. Notice hard-coded customer ID passed to GetCustomerPriceGroup method. We must first create a customer that would belong to an adult price group and pass the ID returned by CreateCustomer to a GetCustomerPriceGroup call. Data creation API often is separated from data retrieval API, so in a larger system CreateCustomer method might reside in a different assembly. Or even worse – not even available to us for security reasons. In case it's available, we have to learn new API, just for use in our test code. It shifts our focus from our main job – write and test CarInsurance class.
Here's integration test code:

[Test]
public void GetCustomerPriceGroup_Adult()
{
var dataLayer = new DataLayer();
int customerID = dataLayer.CreateCustomer("John", "Smith",
new DateTime(1970, 1, 1, 0, 0, 0)); var carInsurance = new CarInsurance();
PriceGroup priceGroup = carInsurance.GetCustomerPriceGroup(customerID);
Assert.AreEqual(PriceGroup.Adult, priceGroup, "Incorrect price group");
}

If we compile and run this test we will receive the following output:

TestCase 'UnitTests.CarInsuranceTest1_Integration.GetCustomerPriceGroup_Adult'
failed: System.Exception : Unable to connect to a database
C:\Projects\NET\TypeMockAAA\DataLayer.cs(12,0): at Customers.DataLayer.CreateCustomer(
String firstName, String lastName, DateTime dateOfBirth)
C:\Projects\NET\TypeMockAAA\UnitTests\CarInsuranceTest1.cs(19,0):
at UnitTests.CarInsuranceTest1_Integration.GetCustomerPriceGroup_Adult()

Not very encouraging, isn't it? We spent some time learning customer creation API, expanded our code to create a customer record – only to find out that the database is not available. Some developers might object that this is not a bad experience: we need integration tests at some level. Yes, but should we write any test as an integration test? We are testing a very simple computational algorithm. Should this work include setting up a database and its connection strings and learning customer creation API that we haven't used until now?

I must admit that many (actually too many) tests that we have in our company have been written like the one above: they are integration tests. And this is probably one of the reasons the average test code coverage for our projects is still at 50-60%: we don't have time to cover all code. Writing integration tests requires extra effort, and fixing broken integration tests is even bigger effort over time. It's useful when hundreds of integration tests fail for different reasons. It becomes boring when they all fail due to a change in a single place. So we can draw a couple of conclusions:

  • Writing integration tests requires learning additional APIs. It may also require referencing additional assemblies. This makes developer's work less efficient and test code more complex.
  • Running integration tests requires setting up and configuring access permissions to external resources (such as database).

In the next section we will see how we can avoid this.

3. Unit tests with traditional mocking  

Now that we know that writing integration tests when the actual purpose is to test a small unit of code is not a good idea, let's look at what we can do. One approach is to inherit DataLayer class from IDataLayer interface and then implement an IDataLayer-derived stub which would return a Customer object of our choice. Note that we won't just need to change DataLayer implementation, we will also have to change visibility of Customer constructor that is currently defined as internal. And while this will obviously make our design more testable, it won't necessarily make it better. I am all for use of interfaces, but not for relaxing visibility constraints without important reasons. But isn't class testability important enough? I am not sure. Bear in mind that making class instantiation public does not open it only for testing. It also opens it for improper use. Luckily, starting from .NET 2.0 an assembly attribute InternalsVisibleTo opens internal definition just for explicitly selected assemblies.

Anyway, we'll take another approach: we'll use mocking. No need to change implementation of other classes, no need to define new interfaces. Using TypeMock framework a new version of our test will look like this:

public void GetCustomerPriceGroup_Adult()
{
Customer customer = MockManager.MockObject<Customer>().Object;
customer.DateOfBirth = new DateTime(1970, 1, 1, 0, 0, 0); using (RecordExpectations recorder = new RecordExpectations())
{
var dataLayer = new DataLayer();
recorder.ExpectAndReturn(dataLayer.GetCustomer(0), customer);
} var carInsurance = new CarInsurance();
PriceGroup priceGroup = carInsurance.GetCustomerPriceGroup(0);
Assert.AreEqual(PriceGroup.Adult, priceGroup, "Incorrect price group");
}

As you can see, we recorded our expectations regarding GetCustomer method behavior. It will return a Customer object with properties that we expect so we can use this object to test GetCustomerPriceGroup.
We compile and run the test, and here’s what we get:

TestCase 'UnitTests.CarInsuranceTest2_NaturalMocks.GetCustomerPriceGroup_Adult'
failed: System.Exception : Unable to connect to a database
C:\Projects\NET\TypeMockAAA\DataLayer.cs(22,0): at Customers.DataLayer.OpenConnection()
C:\Projects\NET\TypeMockAAA\CarInsurance.cs(13,0):
at Customers.CarInsurance.GetCustomerPriceGroup(Int32 customerID)
C:\Projects\NET\TypeMockAAA\UnitTests\CarInsuranceTest2.cs(31,0):
at UnitTests.CarInsuranceTest2_NaturalMocks.GetCustomerPriceGroup_Adult()
at TypeMock.VerifyMocksAttribute.Execute()
at TypeMock.MethodDecorator.e()
at TypeMock.MockManager.a(String A_0, String A_1, Object A_2, Object A_3,
Boolean A_4, Object[] A_5)
at TypeMock.InternalMockManager.getReturn(Object that, String typeName,
String methodName, Object methodParameters, Boolean isInjected)
C:\Projects\NET\TypeMockAAA\UnitTests\CarInsuranceTest2.cs(20,0):
at UnitTests.CarInsuranceTest2_NaturalMocks.GetCustomerPriceGroup_Adult()

Still the same exception! Unable to connect to a database. This is because we can’t just set an expectation on a method that returns a long awaited object, we have to record the whole chain of calls on a mocked DataLayer object. So what we need to add is calls that open and close database connections. A revised (and first successful) version of a test looks like this:

[Test]
[VerifyMocks]
public void GetCustomerPriceGroup_Adult()
{
Customer customer = MockManager.MockObject<Customer>().Object;
customer.DateOfBirth = new DateTime(1970, 1, 1, 0, 0, 0); using (RecordExpectations recorder = new RecordExpectations())
{
var dataLayer = new DataLayer();
dataLayer.OpenConnection();
recorder.ExpectAndReturn(dataLayer.GetCustomer(0), customer);
dataLayer.CloseConnection();
} var carInsurance = new CarInsurance();
PriceGroup priceGroup = carInsurance.GetCustomerPriceGroup(0);
Assert.AreEqual(PriceGroup.Adult, priceGroup, "Incorrect price group");
}

And by the way, we had to add an assembly attribute InternalsVisibleTo to grant access to methods OpenConnection and CloseConnection that were not public.
Using mock objects helped us isolate the code to test from database dependencies. It didn’t however fully isolate the code from a class that connects to a database. Moreover, if you look at the test code wrapped within the RecordExpections block, you will easily recognize a part of the original GetCustomerPriceGroup method code. This smells code duplication with its notorious consequences. We can conclude the following:

    • Mocking requires the knowledge of mocked object behavior which should not be necessary when writing unit tests.
    • Setting a sequence of behavior expectations requires call chain duplication from the original code. It won’t gain you more robust test environment, quite opposite - it will require additional code maintenance.

4. Unit tests with isolation

So what can we improve in the above scenario? Obviously, we don’t want to eliminate a call to GetCustomer, since it is from that method that we want to obtain a customer with a specific state. But this is the only method that interests us, everything else in DataLayer is irrelevant to us in the given context. Can we manage to write our tests only referencing GetCustomer method from DataLayer class?
Yes, we can, with some help from mocking framework. As I mentioned earlier, our company uses TypeMock Isolator that recently has been upgraded with support for exactly what we’re trying to achieve. Here’s how it works:

  • Create an instance of Customer object with required properties.
  • Create a fake instance of DataLayer object.
  • Set the behavior of a call to GetCustomer that will return previously created Customer object.

What is the difference with an approach that we used in a previous section? The difference is that we no longer need to care about additional calls made on DataLayer object – only about calls that affect states used in our test. Here’s the code:

[Test]
[Isolated]
public void GetCustomerPriceGroup_Adult()
{
var customer = Isolate.Fake.Instance<Customer>();
Isolate.WhenCalled(() => customer.DateOfBirth)
.WillReturn(new DateTime(1970, 1, 1, 0, 0, 0)); var dataLayer = Isolate.Fake.Instance<Datalayer>();
Isolate.SwapNextInstance<Datalayer>().With(dataLayer);
Isolate.WhenCalled(() => dataLayer.GetCustomer(0)).WillReturn(customer); var carInsurance = new CarInsurance();
PriceGroup priceGroup = carInsurance.GetCustomerPriceGroup(0);
Assert.AreEqual(PriceGroup.Adult, priceGroup, "Incorrect price group");
}

Note that the first lines that fake a Customer object are needed only because Customer constructor is not public, otherwise we could have created its instance directly. So the lines that are essential are the three lines where we create a DataLayer fake, swap creation of next instance with the faked object and then set the expectation on GetCustomer return value. And after the article was published I received a comment with suggestion how to completely eliminate creation of Customer instance: since the only expectation regarding customer state is his/her birth date, it can be set right away, without instantiating Customer object first:

[Test]
[Isolated]
public void GetCustomerPriceGroup_Adult()
{
var dataLayer = Isolate.Fake.Instance<Datalayer>();
Isolate.SwapNextInstance<Datalayer>().With(dataLayer);
Isolate.WhenCalled(() => dataLayer.GetCustomer(0).DateOfBirth)
.WillReturn(new DateTime(1970, 1, 1, 0, 0, 0)); var carInsurance = new CarInsurance();
PriceGroup priceGroup = carInsurance.GetCustomerPriceGroup(0);
Assert.AreEqual(PriceGroup.Adult, priceGroup, "Incorrect price group");
}

But what happened to OpenConnection and CloseConnection? Are they just ignored? And what if they returned values? What values would they then return?
It all depends on how the fake is created. Isolate.Fake.Instance has an overload that takes as an argument fake creation mode. The possible modes are represented in Members enumerator:

  • MustSpecifyReturnValues – this is default that was used in the code above. All void methods are ignored, and values for the methods with return values must be specified using WhenCalled, just like we did. If return value is not specified, an attempt to execute a method with return value will cause an exception.
  • CallOriginal – this is the mode to make the mocked object running as if it was not mocked except for cases specified using WhenCalled.
  • ReturnNulls – all void methods are ignored, and those that are not void will return null or zeroes.
  • ReturnRecursiveFakes – probably the most useful mode for isolation. All void methods are ignored, and those that return values will return fake value unless specific value is set using WhenCalled. This behavior is applied recursively.

For better understanding of how this works, let’s play with our test code. We begin by removing expectation on Customer state:

[Test]
[Isolated]
public void GetCustomerPriceGroup_Adult()
{
var dataLayer = Isolate.Fake.Instance<Datalayer>();
Isolate.SwapNextInstance<Datalayer>().With(dataLayer); var carInsurance = new CarInsurance();
PriceGroup priceGroup = carInsurance.GetCustomerPriceGroup(0);
Assert.AreEqual(PriceGroup.Adult, priceGroup, "Incorrect price group");
}

What do you think should happen here? Let’s see. OpenConnection and CloseConnection will be ignored. GetCustomer can’t be ignored because it returns a value. Since we didn’t specify any fake creation mode, a default one, MustSpecifyReturnValues will be used. So we must specify return value for GetCustomer. And we didn’t. Here what happens if we run the test:

TestCase 'UnitTests.CarInsuranceTest3_ArrangeActAssert.GetCustomerPriceGroup_Adult'
failed: TypeMock.VerifyException :
TypeMock Verification: Unexpected Call to Customers.DataLayer.GetCustomer()
at TypeMock.MockManager.a(String A_0, String A_1, Object A_2, Object A_3, Boolean A_4,
Object[] A_5)
at TypeMock.InternalMockManager.getReturn(Object that, String typeName,
String methodName, Object methodParameters, Boolean isInjected, Object p1)
C:\Projects\NET\TypeMockAAA\DataLayer.cs(16,0): at Customers.DataLayer.GetCustomer(
Int32 customerID)
C:\Projects\NET\TypeMockAAA\CarInsurance.cs(14,0):
at Customers.CarInsurance.GetCustomerPriceGroup(Int32 customerID)
C:\Projects\NET\TypeMockAAA\UnitTests\CarInsuranceTest3.cs(42,0):
at UnitTests.CarInsuranceTest3_ArrangeActAssert.GetCustomerPriceGroup_Adult ()
at TypeMock.MethodDecorator.CallRealMethod()
at TypeMock.DecoratorAttribute.CallDecoratedMethod()
at TypeMock.ArrangeActAssert.IsolatedAttribute.Execute()
at TypeMock.MethodDecorator.e()
at TypeMock.MockManager.a(String A_0, String A_1, Object A_2, Object A_3,
Boolean A_4, Object[] A_5)
at TypeMock.InternalMockManager.getReturn(Object that, String typeName,
String methodName, Object methodParameters, Boolean isInjected)
C:\Projects\NET\TypeMockAAA\UnitTests\CarInsuranceTest3.cs(37,0):
at UnitTests.CarInsuranceTest3_ArrangeActAssert.GetCustomerPriceGroup_Adult ()

It’s fair, isn’t it? Any call to a method with return value is unexpected – values must be assigned first.
But what if we do the same but set the fake creation mode to ReturnRecursiveFakes? In this case Isolator will have to create an instance of a Customer object that will be returned by DataLayer.GetCustomer. Let’s try:

[Test]
[Isolated]
public void GetCustomerPriceGroup_Adult()
{
var dataLayer = Isolate.Fake.Instance<Datalayer>(Members.ReturnRecursiveFakes);
Isolate.SwapNextInstance<Datalayer>().With(dataLayer); var carInsurance = new CarInsurance();
PriceGroup priceGroup = carInsurance.GetCustomerPriceGroup(0);
Assert.AreEqual(PriceGroup.Adult, priceGroup, "Incorrect price group");
}

And here’s the output:

TestCase 'UnitTests.CarInsuranceTest3_ArrangeActAssert.GetCustomerPriceGroup_Adult'
failed:
Incorrect price group
Expected: Adult
But was: Senior
C:\Projects\NET\TypeMockAAA\UnitTests\CarInsuranceTest3.cs(55,0):
at UnitTests.CarInsuranceTest3_ArrangeActAssert.GetCustomerPriceGroup_Adult()
at TypeMock.MethodDecorator.CallRealMethod()
at TypeMock.DecoratorAttribute.CallDecoratedMethod()
at TypeMock.ArrangeActAssert.IsolatedAttribute.Execute()
at TypeMock.MethodDecorator.e()
at TypeMock.MockManager.a(String A_0, String A_1, Object A_2, Object A_3, Boolean A_4,
Object[] A_5)
at TypeMock.InternalMockManager.getReturn(Object that, String typeName,
String methodName, Object methodParameters, Boolean isInjected)
C:\Projects\NET\TypeMockAAA\UnitTests\CarInsuranceTest3.cs(49,0):
at UnitTests.CarInsuranceTest3_ArrangeActAssert.GetCustomerPriceGroup_Adult()

That’s interesting. Why did we get a senior customer? Because Isolator created a Customer instance with default properties, setting therefore DateOfBirth to 01.01.0001. How can such an old customer not be treated as a senior?

Conclusion: write less test code, write good test code

We have looked at different methods of writing test code: integration tests, unit tests based on recording of the expected behavior, and unit tests with isolating the class under test from any irrelevant aspects of code execution. I believe the latter approach is a winner in many cases: it lets you set the state of objects that you are not interested to test in a lightweight straightforward manner. Not only it lets you focus on the functionality being tested, it also saves the time you would need to spend in the future updating recorded behavior affected by design changes.
I know I am at risk of oversimplifying the choice. If you haven’t read Martin Fowler’s article "Mocks Aren’t Stubs", you can find there both definitions of terms and reasoning behind the selection of it. But with all respect to the problem complexity, I think we should not ignore such simple criteria as compactness and maintainability of the test code combined with using the original code to test “as is”, without revising it for better testability. These are strong arguments, and they justify selection of a lightweight "arrange-act-assert" method shown in this article.
I think what we’re observing now is a beginning of a new phase in test methodology: applying the same strict rules to test code quality that we have been using for production code. In theory same developers should write both production and test code with the same quality. In practice test code has always been a subject to compromises. Look for example at developers’ acceptance of low code coverage: they may target 80% code coverage but won’t postpone a release if they reach only 60%. Can you imagine that they could release a product with 20% less functions without management approval?
Such attitude had practical and even moral grounds. On the practical side, test code was treated as code of a second priority. This code is not supposed to be deployed, it is for internal use, so it received lower attention. And on the moral side, when automated testing is so underused every developer’s effort to write automated tests should be sacred. Criticizing a developer writing tests for writing bad tests was like criticizing an animal abuse fighter for not fighting smart. But we must leave behind all romantics around unit testing. It has to obey well-defined rules of software development. And one of those rules is "write less code".
One of the statements that played positive role in early stage of test-driven development and that should be considered harmful now is “there can’t be too many tests”. Yes there can. Over several years we’ve been writing tests for systems consisting of several complex layers and external integration points that were hard or impossible to reach from test environment. We were trying to hit a logical error one way or another, and usually succeeded. However, speaking for myself I noticed that I developed a relaxed attitude about not catching an error in the first place: in the code that was written specifically to test a given function. This is a dangerous attitude. It lowers an effort dedicated to covering each class with unit tests that would validate all aspects of its behavior. "If unit tests for A does not expose all errors, then unit tests for B may expose them. In the worst case they will be exposed by integration tests". And I’ve seen many times that when developers had to find an error that occurred in production, they managed to write new integration test that failed (production errors often are easier to reproduce from integration tests), then they traced this test in a debugger, identified an offensive module and fixed the error there. After the fix they made sure that the integration test succeeded and considered the issue solved. They did not write a new unit test for the affected module. "Why? We already wrote one." After a while, the system is covered by thousands of tests, but it is often possible to introduce an error in a module without breaking its dedicated tests.
For a long period this was hard to blame. There was so much excitement around daily builds, nightly tests, traffic light-alike test runners that some developers even managed to wire to large industrial LED boards so every visitor could know who broke the last build. It was great, and it’s not over. We just have to start measuring efficiency of our test efforts. If unit test code written to test module A exposes a bug in module B, and this bug is not exposed by unit tests written to test B, then you have some work to do. And if a logical bug is exposed by integration tests, you also have work to do. Integration tests should only expose problems related to configuration, databases and communication with external resources. Anything else is subject to unit tests written specifically to validate respective functions. It is bad practice to duplicate test code. It is bad practice to bring many dependencies to a test module. It is bad practice to make test code aware of anything that is not related to states used by the code under test. And this is where latest development in TDD tools and frameworks can provide us with great help. We only need to start changing our old habits.

    

写在最后

If you want to go further and learn unit testing in depth using mocking frameworks such as Moq, FakeItEasy and Typemock Isolator, I highly recommend checking out The Art of Unit Testing: with examples in C# by Roy Osherove.

C# Note37: Writing unit tests with use of mocking的更多相关文章

  1. Unit Tests

    The Three Laws of TDD First Law : you may not write production code until you have written a failing ...

  2. [转]Creating Unit Tests for ASP.NET MVC Applications (C#)

    本文转自:http://www.asp.net/mvc/tutorials/older-versions/unit-testing/creating-unit-tests-for-asp-net-mv ...

  3. Building Local Unit Tests

    If your unit test has no dependencies or only has simple dependencies on Android, you should run you ...

  4. [Java in NetBeans] Lesson 07. JavaDoc and Unit Tests

    这个课程的参考视频和图片来自youtube. 主要学到的知识点有: 1. organize code into packages Create a package if want to make th ...

  5. [Python Test] Use pytest fixtures to reduce duplicated code across unit tests

    In this lesson, you will learn how to implement pytest fixtures. Many unit tests have the same resou ...

  6. Android测试:Building Local Unit Tests

    原文:https://developer.android.com/training/testing/unit-testing/local-unit-tests.html 如果你的单元测试没有依赖或者只 ...

  7. [Angular + Unit Testing] Mock HTTP Requests made with Angular’s HttpClient in Unit Tests

    In a proper unit test we want to isolate external dependencies as much as possible to guarantee a re ...

  8. Unit Tests Tool - <What is “Mock You”> The introduction to moq #Reprinted#

    From: http://www.cnblogs.com/wJiang/archive/2010/02/21/1670632.html Moq即Mock You Framework,故名思意是一个类似 ...

  9. ASP.Net MVC3 - The easier to run Unit Tests by moq #Reprinted#

    From: http://www.cnblogs.com/techborther/archive/2012/01/10/2317998.html 前几天调查完了unity.现在给我的任务是让我调查Mo ...

随机推荐

  1. Oracle day05 索引_数据去重

    索引 自动:当在表上定义一个primary key或者unique 约束条件时,oracle数据库自动创建一个对应的唯一索引. 手动:用户可以创建索引以加速查询 在一列或者多列上创建索引: creat ...

  2. Oracle day04 DML_事务_序列_视图_数据类型_DDL

    DMLinsert关键字作用:往表中插入一条(多条)数据 语法1:元祖值式的插入语法1: insert into tablename(column1,column2,...,columnN) valu ...

  3. Spring笔记02_注解_IOC

    目录 Spring笔记02 1. Spring整合连接池 1.1 Spring整合C3P0 1.2 Spring整合DBCP 1.3 最终版 2. 基于注解的IOC配置 2.1 导包 2.2 配置文件 ...

  4. java开发环境配置——IntelliJ IDEA

    关于开发工具,之前是用eclipse,后来用了一段时间idea后,发现idea比eclipse好用太多了,所以推荐大家用idea 官网下载地址:https://www.jetbrains.com/id ...

  5. .babelrc配置(webpack)

    babel是一种js语法编译器,在前端开发过程中,由于浏览器的版本和兼容性问题,很多js的新方法和特性的使用都受到了限制.使用babel可以将代码中js代码编译成兼容绝大多数主流浏览器的代码. bab ...

  6. 博弈论进阶之SG函数

    SG函数 个人理解:SG函数是人们在研究博弈论的道路上迈出的重要一步,它把许多杂乱无章的博弈游戏通过某种规则结合在了一起,使得一类普遍的博弈问题得到了解决. 从SG函数开始,我们不再是单纯的同过找规律 ...

  7. Vue介绍

    1.Vue的简介 Vue.js(读音 /vjuː/, 类似于 view) 是一套构建用户界面的渐进式框架. Vue 只关注视图层, 采用自底向上增量开发的设计. Vue 的目标是通过尽可能简单的 AP ...

  8. Apex 中文件夹相关的单元测试

    Salesforce 中的文件夹 在 Salesforce 中,我们可以建立各种文档.报表.仪表板.电子邮件模板等.它们都被保存在相应的文件夹中. Salesforce 的后端将这些文件夹保存为 Fo ...

  9. C语言使用HZK16显示每个像素的代码

    下边内容段是关于C语言使用HZK16显示每个像素的内容. #include<stdio.h>#include<stdlib.h>void main(){ int i,j; ch ...

  10. tofixed方法 四舍五入

    tofixed方法 四舍五入 toFixed() 方法可把 Number 四舍五入为指定小数位数的数字.例如将数据Num保留2位小数,则表示为:toFixed(Num):但是其四舍五入的规则与数学中的 ...