严正声明:本文系作者davidhopper原创,未经许可,不得转载。

一般地讲,软件测试分为四个阶段:单元测试、集成测试、系统测试、验收测试。单元测试一般指对软件中的最小可测试单元进行检查和验证。最小可测试单元可以是指一个函数、一次调用过程、一个类等。单元测试一般由开发人员来实施。集成测试又称为组装测试,用于测试各个单元模块之间的接口是否满足详细设计说明书的要求,是偏重于技术角度的测试。系统测试用于测试整个系统的功能和性能,验证是否满足了需求规格说明书的要求,是偏重于业务角度的测试。验收测试也称为交付测试,是针对用户需求、业务流程进行的使用测试,以确定软件产品是否能够满足用户的真正需求,由用户、客户或其他授权机构决定是否接受该软件产品。验收测试分为α测试和β测试。
对于C/C++语言而言,单元测试一般是针对一个函数或者一个类的某个公有函数(或称公有方法),单元测试的目的就是检测目标函数在所有可能的输入下,函数的执行过程和输出是否符合预期。可以说,单元测试是颗粒度最小的测试,对于软件开发而言,保证每个小的函数执行正确,才能保证利用这些小模块组合起来的系统能够正常工作。
本文介绍在Ubuntu 20.04系统中,使用VSCode、CMake、GTest实现C/C++程序自动化单元测试的详细步骤。

一、安装GCC、MAKE、GDB等工具

使用“Ctrl+Alt+T”打开一个命令窗口,切换到下载目录,执行如下命令安装VSCode:

# 更新Apt工具软件包列表
sudo apt update
# 装GCC、MAKE、GDB等工具
sudo apt install build-essential gdb
# 检查GCC版本
gcc -v
# 检查MAK版本
make -v
# 检查GDB版本
gdb -v

二、安装CMake

安装CMake很简单,可以使用Ubuntu自带的apt工具下载安装。使用“Ctrl+Alt+T”打开一个命令窗口,执行如下命令:

# 安装CMake
sudo apt install cmake
# 检查CMake版本
cmake --version

三、安装VSCode及插件

3.1 安装VSCode

前往https://code.visualstudio.com/下载VSCode安装包,注意下载deb格式的安装包(不同时间下载的版本不同,2021年10月6日下载的版本为:code_1.60.2-1632313585_amd64.deb)。下载完成后,使用“Ctrl+Alt+T”打开一个命令窗口,切换到下载目录,执行如下命令安装VSCode:

# 安装VSCode
sudo apt install ./code_1.60.2-1632313585_amd64.deb
# 检查VSCode版本号
code -v
# 打开VSCode
code &

注意:如果Ubuntu系统版本较老,可能需要将命令sudo apt install ./code_1.60.2-1632313585_amd64.deb替换为如下命令:

sudo dpkg -i code_1.60.2-1632313585_amd64.deb
sudo apt-get install -f


将VSCode加入工具栏,这样就不必每次从命令窗口启动VSCode。

3.2 安装C++、CMake和CMake Tools插件

在VSCode中安装C++、CMake和CMake Tools插件,具体操作如下所示:
在VSCode界面中使用快捷键Ctrl+Shift+X弹出插件界面,分三次输入"c++”、“cmake”、“cmake tools”,分别安装C++和CMake、CMake Tools插件:

安装后的插件界面如下:

3.3 在VSCode中使用CMake Tools插件实现C++程序的编译和调试

3.3.1 创建一个用于CMake编译的文件夹

使用“Ctrl+Alt+T”打开一个命令窗口,使用如下命令创建“CMakeQuickStart”文件夹,并使用VSCode打开该文件夹。我们将在该文件夹内使用VSCode创建一个基于CMake编译的C++工程。

mkdir CMakeQuickStart
cd CMakeQuickStart
code . &

3.3.2 创建一个基于CMake的“HelloWorld”工程

在刚才打开的VSCode界面中,按下快捷键“Ctrl+Shift+P”打开命令面板,输入“cmake:quick”,按回车键(Enter键)执行命令面板中出现的“CMake: Quick Start”命令,如下图所示:

工程名写为“HelloWorld”(你可以替换为任何自己想取的工程名,注意中间不要有空格,因为该名称将是最后运行的程序名,如果中间有空格,想想在命令行窗口运行该程序有多麻烦!):

构建类型选择“Executable”,因为我们需要创建一个可执行程序。如果你需要创建一个动态库或静态库文件,请选择“Library”:

以下是“CMake: Quick Start ”命令自动给我们生成的“CMakeLists.txt”文件内容(CMake语法请查找其他文档,本文不再赘述):

3.3.3 为“HelloWorld”工程选择一个C++编译器

有些人的电脑上可能安装有多个C++编译器(*我的电脑仅安装了一个GCC 9.3.0编译器),例如:GCC、LLVM,此外GCC或LLVM还可能有多个不同的版本,为此需要为“HelloWorld”工程显式地指定一个C++编译器。下面介绍具体操作步骤:
在VSCode界面中,按下快捷键“Ctrl+Shift+P”打开命令面板,输入“cmake:select”,按回车键(Enter键)执行命令面板中出现的“CMake: Select a Kit”命令,如下图所示:

选择“GCC 9.3.0”编译器(因为我的机器只安装了一种编译器,因此没有其他选择):

3.3.4 为“HelloWorld”工程选择一个编译版本(Debug或Release版本)

在VSCode界面中,按下快捷键“Ctrl+Shift+P”打开命令面板,输入“cmake:select”,按回车键(Enter键)执行命令面板中出现的“CMake: Select Variant”命令,如下图所示:

选择“Debug”编译版本(也可以根据需要选择其他版本):

3.3.5 编译“HelloWorld”工程

在VSCode界面中,按下快捷键“Ctrl+Shift+P”打开命令面板,输入“cmake:build”,按回车键(Enter键)执行命令面板中出现的“CMake: Build ”命令(也可直接使用快捷键“F7”),如下图所示:

或者通过VSCode下方状态栏中的“Build”按钮完成编译(如下图所示):

3.3.6 运行“HelloWorld”工程

在VSCode界面中,按下快捷键“Ctrl+Shift+P”打开命令面板,输入“cmake:run”,按回车键(Enter键)执行命令面板中出现的“CMake: Run Without Debugging”命令(也可直接使用快捷键“Shift+F5”),如下图所示:

3.3.7 调试“HelloWorld”工程

在VSCode界面中,打开“main.cpp”文件,在第4行左侧用鼠标点击一下,出现一个红点,表明已在该行设置一个断点:

按下快捷键“Ctrl+Shift+P”打开命令面板,输入“cmake:debug”,按回车键(Enter键)执行命令面板中出现的“CMake: Debug ”命令(也可直接使用快捷键“Ctrl+F5”),如下图所示:

3.3.8 重新配置“HelloWorld”工程

有时我们会修改CMakeLists.txt文件,这时需要删除原有的Makefile,重新基于CMakeLists.txt生成Makefile,该操作在大型工程中尤为常见。
按下快捷键“Ctrl+Shift+P”打开命令面板,输入“cmake:delete”,按回车键(Enter键)执行命令面板中出现的“CMake: Delete Cache and Reconfigure”命令,即可重新配置“HelloWorld”工程,如下图所示:

四、安装GTest

GTest的全称是googletest,不能使用apt工具下载安装,只能使用源代码安装。前往网站https://github.com/google/googletest/下载源代码(若国内访问GitHub网站速度较慢,建议使用GitHub在国内的镜像网站Gitte下载:https://gitee.com/mirrors/googletest),命令如下:

# 下载最新的release版本并保存为googletest-release-1.11.0.tar.gz
wget -O googletest-release-1.11.0.tar.gz https://github.com/google/googletest/archive/refs/tags/release-1.11.0.tar.gz
# 解压
tar -zxf googletest-release-1.11.0.tar.gz
# 进入解压后的目录
cd googletest-release-1.11.0
# 为编译创建一个目录
mkdir build && cd build
cmake ..
# -j8表示使用8个工作线程编译
make -j8
# 安装
sudo make install

通过上述操作,GTest库文件安装在/usr/local/lib/目录,头文件安装在/usr/local/include/目录

使用Vim手动编写一个文件example.cc验证GTest已正确安装。

#include <gtest/gtest.h>int add(int a, int b) { return a + b;
}
TEST(testCase, test0) { EXPECT_EQ(add(8, 11), 19);
}int main(int argc, char **argv) {testing::InitGoogleTest(&argc, argv);return RUN_ALL_TESTS();
}

在命令窗口中执行如下编译命令:

g++ example.cc -o example -lgtest -lpthread
./example

五、在VSCode中使用CMake和GTest创建单元测试代码

在第四节已经可以借助Vim来创建基于GTest的C/C++程序的单元测试代码了,但这种单元测试有一个致命缺陷,就是需要人工去运行,有没有一种方法让我们在VSCode中使用CMake构建项目时就自动运行单元测试代码呢?答案是肯定的。下面通过一个简单的例子来讲解。为了让我们的例子更加贴近实际,本示例工程包含一个共享库及其单元测试代码、调用共享库的主程序。本示例来源于LeetCode题库,如下所示:

5.1 题目描述

  1. 对输入的字符串进行加解密,并输出。

  2. 加密方法为:
    当内容是英文字母时则用该英文字母的后一个字母替换,同时字母变换大小写,如字母a时则替换为B;字母Z时则替换为a;
    当内容是数字时则把该数字加1,如0替换1,1替换2,9替换0;
    其他字符不做变化。

  3. 解密方法为加密的逆过程。

说明

字符串以\0结尾。

输入描述

输入一串要加密的字符
输入一串加过密的密码

输出描述

输出加密后的密码
输出解密后的字符

示例1

输入
abcdefg
BCDEFGH
输出
BCDEFGH
abcdefg

5.2 题目解答

整个项目名称为:string_operation,根目录也为:string_operation,目录树结构如下图所示:

先讲在VSCode中如何运行单元测试,后面再贴出具体代码。

  1. 打开VSCode,按下快捷键“Ctrl+Shift+P”打开命令面板,输入“cmake:delete”,按回车键(Enter键)执行命令面板中出现的“CMake: Delete Cache and Reconfigure”命令(后台执行的是“cmake -S . -B build”命令),消除工程中原有的makefile,并根据最新的CMakeLists.txt文件重新生成makefile。

  1. 按下快捷键“Ctrl+Shift+P”打开命令面板,输入“cmake:tests”,按回车键(Enter键)执行命令面板中出现的“CMake: Run Tests”命令(后台执行的是“ctest”命令),所有单元测试即被执行。


  • 顶层CMake配置文件CMakeLists.txt
cmake_minimum_required(VERSION 3.1.0)
project(StringOperation VERSION 0.1.0)# 添加子目录encrypt_and_decrypt
add_subdirectory(encrypt_and_decrypt)
# 添加可执行文件,即最后生成的可执行文件名称为:string_operation,依赖的实现文件是main.cpp
add_executable(string_operation main.cpp)
# 生成可执行文件string_operation需要链接encrypt_and_decrypt库
target_link_libraries(string_operation encrypt_and_decrypt)
# 启用测试
include(CTest)
enable_testing()set(CPACK_PROJECT_NAME ${PROJECT_NAME})
set(CPACK_PROJECT_VERSION ${PROJECT_VERSION})
include(CPack)
  • encrypt_and_decryp目录下的CMake配置文件CMakeLists.txt
cmake_minimum_required(VERSION 3.1.0)
project(encrypt_and_decrypt VERSION 0.1.0)# Generate the library file.
# 添加库文件,库文件名称为:${PROJECT_NAME},也就是encrypt_and_decrypt
# 依赖的实现文件是encrypt_and_decrypt.cpp
add_library(${PROJECT_NAME} encrypt_and_decrypt.cpp)# Generate the testing executable file.
include(CTest)
# 启用测试
enable_testing()
# 添加测试可执行文件,即最后生成的可执行文件名称为:encrypt_and_decrypt_test,
# 依赖的实现文件是encrypt_and_decrypt_test.cpp
add_executable(${PROJECT_NAME}_test encrypt_and_decrypt_test.cpp)
# 寻找GTest和Threads库,两个库必需
find_package(GTest REQUIRED)
find_package(Threads REQUIRED)
# 包含GTest的头文件目录
include_directories(${GTEST_INCLUDE_DIRS})
# 生成测试可执行文件encrypt_and_decrypt_test需要链接encrypt_and_decrypt、gtest、pthread库
target_link_libraries(${PROJECT_NAME}_test ${PROJECT_NAME} ${GTEST_BOTH_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} )# Enable CMake's testing runner to discover the test executable file.
# If CMake's version is greater than 3.10, use the "gtest_discover_tests" statement, otherwise use the "add_test" statement.
# 让CMake能够发现encrypt_and_decrypt_test文件,以便使用ctest命令时能够自动运行测试文件。
gtest_discover_tests(${PROJECT_NAME}_test)
# add_test(StringOperationTest ${PROJECT_NAME}_test)set(CPACK_PROJECT_NAME ${PROJECT_NAME})
set(CPACK_PROJECT_VERSION ${PROJECT_VERSION})
include(CPack)
  • encrypt_and_decrypt/encrypt_and_decrypt.h文件:
#ifndef ENCRYPT_AND_DECRYPT_H_
#define ENCRYPT_AND_DECRYPT_H_#include <string>namespace string_operation {class StrEncryptAndDecrypt {public:static void Encrypt(const std::string& contents, std::string& passwords);static void Decrypt(const std::string& passwords, std::string& contents);private:/* data */
};}  // namespace string_operation#endif  // ENCRYPT_AND_DECRYPT_H_
  • encrypt_and_decrypt/encrypt_and_decrypt.cpp文件:
#include "encrypt_and_decrypt.h"#include <algorithm>namespace string_operation {namespace {const std::string A_TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
const std::string D_TABLE = "0123456789";
}  // namespacevoid StrEncryptAndDecrypt::Encrypt(const std::string& contents,std::string& passwords) {if (contents.empty()) {passwords.clear();return;}passwords = contents;char content = '0';char password = '0';for (std::size_t i = 0; i < contents.size(); ++i) {content = contents[i];password = content;if (std::isalpha(content)) {auto pos = A_TABLE.find(std::toupper(content));if (std::string::npos != pos) {password = pos < (A_TABLE.size() - 1) ? A_TABLE[pos + 1] : A_TABLE[0];if (std::isupper(content)) {password = std::tolower(password);}}} else if (std::isdigit(content)) {auto pos = D_TABLE.find(content);if (std::string::npos != pos) {password = pos < (D_TABLE.size() - 1) ? D_TABLE[pos + 1] : D_TABLE[0];}} else {continue;}passwords[i] = password;}
}void StrEncryptAndDecrypt::Decrypt(const std::string& passwords,std::string& contents) {if (passwords.empty()) {contents.clear();return;}contents = passwords;char content = '0';char password = '0';for (std::size_t i = 0; i < passwords.size(); ++i) {password = passwords[i];content = password;if (std::isalpha(password)) {auto pos = A_TABLE.find(std::toupper(password));if (std::string::npos != pos) {content = pos > 0 ? A_TABLE[pos - 1] : A_TABLE[A_TABLE.size() - 1];if (std::isupper(password)) {content = std::tolower(content);}}} else if (std::isdigit(password)) {auto pos = D_TABLE.find(password);if (std::string::npos != pos) {content = pos > 0 ? D_TABLE[pos - 1] : D_TABLE[D_TABLE.size() - 1];}} else {continue;}contents[i] = content;}
}
}  // namespace string_operation
  • encrypt_and_decrypt/encrypt_and_decrypt_test.cpp文件:
#include "encrypt_and_decrypt.h"#include <gtest/gtest.h>namespace string_operation {TEST(EncyrptTest, BasicTests) {std::string passwords;StrEncryptAndDecrypt::Encrypt("abcdefg", passwords);EXPECT_STREQ("BCDEFGH", passwords.c_str());StrEncryptAndDecrypt::Encrypt("SayHelloToMe2019", passwords);EXPECT_STREQ("tBZiFMMPuPnF3120", passwords.c_str());StrEncryptAndDecrypt::Encrypt("Are you ok?", passwords);EXPECT_STREQ("bSF ZPV PL?", passwords.c_str());StrEncryptAndDecrypt::Encrypt("To make use of the new library.", passwords);EXPECT_STREQ("uP NBLF VTF PG UIF OFX MJCSBSZ.", passwords.c_str());StrEncryptAndDecrypt::Encrypt("This tutorial aims to get you up and running with GoogleTest using ""CMake.",passwords);EXPECT_STREQ("uIJT UVUPSJBM BJNT UP HFU ZPV VQ BOE SVOOJOH XJUI hPPHMFuFTU VTJOH ""dnBLF.",passwords.c_str());
}TEST(EncyrptTest, AdvancedTests) {std::string passwords;StrEncryptAndDecrypt::Encrypt(" ", passwords);EXPECT_STREQ(" ", passwords.c_str());StrEncryptAndDecrypt::Encrypt("", passwords);EXPECT_STREQ("", passwords.c_str());StrEncryptAndDecrypt::Encrypt("jlk;fdsa7890341@!@#$^#$&*%%$#KHUrewq B789*^&^$()_,./`TH", passwords);EXPECT_STREQ("KML;GETB8901452@!@#$^#$&*%%$#livSFXR c890*^&^$()_,./`ui",passwords.c_str());StrEncryptAndDecrypt::Encrypt("For this tutorial we will put the library into a subdirectory called ""MathFunctions. This directory already contains a header file, ""MathFunctions.h, and a source file mysqrt.cxx. The source file has one ""function called mysqrt that provides similar functionality to the ""compiler's sqrt function.",passwords);EXPECT_STREQ("gPS UIJT UVUPSJBM XF XJMM QVU UIF MJCSBSZ JOUP B TVCEJSFDUPSZ DBMMFE ""nBUIgVODUJPOT. uIJT EJSFDUPSZ BMSFBEZ DPOUBJOT B IFBEFS GJMF, ""nBUIgVODUJPOT.I, BOE B TPVSDF GJMF NZTRSU.DYY. uIF TPVSDF GJMF IBT POF ""GVODUJPO DBMMFE NZTRSU UIBU QSPWJEFT TJNJMBS GVODUJPOBMJUZ UP UIF ""DPNQJMFS'T TRSU GVODUJPO.",passwords.c_str());
}TEST(DecryptTest, BasicTests) {std::string contents;StrEncryptAndDecrypt::Decrypt("BCDEFGH", contents);EXPECT_STREQ("abcdefg", contents.c_str());StrEncryptAndDecrypt::Decrypt("tBZiFMMPuPnF3120", contents);EXPECT_STREQ("SayHelloToMe2019", contents.c_str());StrEncryptAndDecrypt::Decrypt("bSF ZPV PL?", contents);EXPECT_STREQ("Are you ok?", contents.c_str());StrEncryptAndDecrypt::Decrypt("uP NBLF VTF PG UIF OFX MJCSBSZ.", contents);EXPECT_STREQ("To make use of the new library.", contents.c_str());
}TEST(DecryptTest, AdvancedTests) {std::string contents;StrEncryptAndDecrypt::Decrypt(" ", contents);EXPECT_STREQ(" ", contents.c_str());StrEncryptAndDecrypt::Decrypt("", contents);EXPECT_STREQ("", contents.c_str());StrEncryptAndDecrypt::Decrypt("KML;GETB8901452@!@#$^#$&*%%$#livSFXR c890*^&^$()_,./`ui", contents);EXPECT_STREQ("jlk;fdsa7890341@!@#$^#$&*%%$#KHUrewq B789*^&^$()_,./`TH",contents.c_str());StrEncryptAndDecrypt::Decrypt("gPS UIJT UVUPSJBM XF XJMM QVU UIF MJCSBSZ JOUP B TVCEJSFDUPSZ DBMMFE ""nBUIgVODUJPOT. uIJT EJSFDUPSZ BMSFBEZ DPOUBJOT B IFBEFS GJMF, ""nBUIgVODUJPOT.I, BOE B TPVSDF GJMF NZTRSU.DYY. uIF TPVSDF GJMF IBT POF ""GVODUJPO DBMMFE NZTRSU UIBU QSPWJEFT TJNJMBS GVODUJPOBMJUZ UP UIF ""DPNQJMFS'T TRSU GVODUJPO.",contents);EXPECT_STREQ("For this tutorial we will put the library into a subdirectory called ""MathFunctions. This directory already contains a header file, ""MathFunctions.h, and a source file mysqrt.cxx. The source file has one ""function called mysqrt that provides similar functionality to the ""compiler's sqrt function.",contents.c_str());
}}  // namespace string_operation
  • main.cpp文件:
#include <iostream>
#include <string>#include "encrypt_and_decrypt/encrypt_and_decrypt.h"using string_operation::StrEncryptAndDecrypt;int main(int, char**) {std::string in_contents, out_contents, in_passwords, out_passwords;std::cout << "Please input the contents to be encrypted: " << std::endl;if (std::getline(std::cin, in_contents)) {StrEncryptAndDecrypt::Encrypt(in_contents, out_passwords);std::cout << "The encrypted contents are: " << std::endl;std::cout << out_passwords << std::endl;}std::cout << "Please input the contents to be decrypted: " << std::endl;if (std::getline(std::cin, in_passwords)) {StrEncryptAndDecrypt::Decrypt(in_passwords, out_contents);std::cout << "The decrypted contents are: " << std::endl;std::cout << out_contents << std::endl;}return 0;
}

所有源代码的下载地址为百度网盘:https://pan.baidu.com/s/1DnQdUJXOeFZj3dy3PRCUNw 密码: 211t。

Ubuntu 20.04系统中VSCode+CMake+GTest实现C++程序自动化单元测试的详细方法相关推荐

  1. linux 图标显示 异常,在Ubuntu 18.04系统中VSCode图标显示异常的解决方法

    以下介绍在Ubuntu 18.04系统中VSCode图标显示异常的解决方法,同时附上在Ubuntu 18.04系统中安装Anaconda3-5.3.0方法.VSCode是一款全平台开发的编辑器,它具有 ...

  2. Ubuntu 20.04系统中Sage(sagemath)安装及使用详细过程

    文章目录 一.安装方式一:预编译二进制版本 二.安装方式二:源码编译 最近在做实验遇到要安装Sage,也是花了将近三天时间才弄好,一波三折整理了一下,以便后续还要安装时能少走弯路. 首先,了解一下sa ...

  3. Ubuntu 20.04系统中安装vncserver的方法步骤

    系统中安装vncserver以便于远程图形化管理和使用.安装vncserver的步骤如下: 第一步:安装桌面环境和VNCServer #更新apt update#安装桌面环境sudo apt inst ...

  4. ubuntu 安装kde桌面_在Ubuntu 20.04系统上安装KDE Plasma Desktop的方法

    本文介绍在Ubuntu 20.04系统上安装KDE Plasma Desktop的方法.Plasma Desktop是KDE创建的一种流行且功能强大的桌面环境,主要用于Linux系统.KDE Plas ...

  5. Ubuntu 20.04 系统5分钟后老是自动锁屏怎么取消?

    新装的Ubuntu 20.04系统 跟以往的版本一样,当你不做任何操作的时候,过了5分钟就会自动锁屏,又得重新输入一次账号密码,有办法将时间延长或者取消吗? 当然可以了,下面跟着我一起设置,解决这个烦 ...

  6. 在Win10 PC上安装Ubuntu 20.04 系统

    在Win10 PC上安装Ubuntu 20.04 系统 本文仅适用于UEFI+GPT类型的PC,有关这方面的知识请自行了解. 引言 为什么需要使用 Linux 系统? 既已看到此文,我相信你一定有需求 ...

  7. win10 安装linux 需要多大空间,在Win10 PC上安装Ubuntu 20.04 系统

    在Win10 PC上安装Ubuntu 20.04 系统 本文仅适用于UEFI+GPT类型的PC,有关这方面的知识请自行了解. 引言 为什么需要使用 Linux 系统? 既已看到此文,我相信你一定有需求 ...

  8. Ubuntu 17.04系统中QT5.9.2支持fcitx输入法的解决方案

    本人在Ubuntu 17.04系统中安装了QT 5.9.2,自带Qt Creator 4.4.1,各方面用着都挺顺手,但有一次突然发现在Qt Creator中和使用QT编译的程序运行时均不能使用Ubu ...

  9. Ubuntu 20.04 系统迁移

    一.前言 现实工作中需要在Intel NUC上装一个Ubuntu 20.04系统,并运行ROS以及相关的很多功能包,但如果直接安装新新系统,之前的大量环境变量要重新去配置,所以考虑说将原先的Ubunt ...

最新文章

  1. JavaScript:Object.prototype.toString进行数据类型判定
  2. 你不是在拯救世界就是在拯救世界的路上
  3. python sklearn 归一化_第3章 Sklearn概述
  4. silverlight 学习笔记 (五): MVVM Light Toolkits 之 RealCommand
  5. echarts 在两点之间画一条线_树的手绘很难画?分步骤教你画,简单易学,收藏起来临摹学习...
  6. iOS 数据解析之使用TFHpple解析html
  7. 安卓-08-布局管理器
  8. Java项目:在线购书商城系统(java+jsp+mysql+servlert+ajax)
  9. office韩文版本
  10. 【IoT】产品组合投资地图:如何将产品战略与执行联系起来?
  11. Hadoop(一) Centos7 下Hdoop 安装及伪分布式集群部署
  12. 苹果在新西兰的所得税都缴纳给了澳大利亚
  13. Unity3D说明文档翻译-Audio Manager
  14. 计算机网络技术组装与维护,计算机组装与维护计算机网络技术(组网)()课程标准(范文).doc...
  15. 测试学习——性能测试(一)
  16. 【论文笔记】:CornerNet: Detecting Objects as Paired Keypoints
  17. ABAP CDS View
  18. Linux 命令系统
  19. 差异表达基因热图怎么看_为什么我代码里面选择top1000的sd基因绘制热图呢
  20. 分享166个ASP源码,总有一款适合您

热门文章

  1. python界面怎么改颜色_python使用tkinter做界面之颜色
  2. GET和 POST非常浅薄的理解
  3. 如何提高百度经验推广技巧
  4. 使用Qt创建一个时钟
  5. 快手答题恢复账号6道题
  6. 竞争-冒险现象及其消除方法
  7. 如何设置QTableWideget和行高和列宽
  8. VS2015和VS2017运行项目时,未能找到路径“……\bin\roslyn\csc.exe”的解决方案
  9. TDS协议和FreeTDS开源实现
  10. 搜索框和按钮放在同一行