单元测试之旅:预见优秀之②——寻求优秀_汇智动力IT学院的博客-程序员信息网

技术标签: IT技术  软件测试  单元测试  IT行业  干货  

2. 单元测试进阶——寻求优秀

2.1 使用测试替身

在现代开发者测试的上下文中,除了允许在某些依赖缺失的情况下编译执行代码以外,崇尚测试的程序员还创建了一套“仅供测试”的工具,用于隔离被测试的代码、加速执行测试、使得随机行为变得确定、模拟特殊情况以及能够使测试访问隐藏信息等。满足这些目的的各种对象具有相似之处,但又有所区别,我们统称为测试替身(test double)。

这一节我们先探讨开发者采用测试替身的理由,理解了测试替身潜在的好处以后,我们再解析来看看各种可供选择的测试替身的类型。

  • 测试替身的威力

引入测试替身的最根本的原因是——将被测试代码与周围隔离开。为了时不时的验证一段代码的行为是否符合期望值,我们最好的选择就是替换其周围的代码,使得获取对环境的完整控制,从而在其中测试目标代码。

通过以下的几个部分,我们来讨论测试替身的好处。

  • 隔离被测试的代码
    代码的世界,一般包括了两种:被测试代码和与被测试代码进行交互的代码。
    接下来我们用一个简单的例子,展示如何隔离代码。示例代码如下:
public class Car {
    private Engine engine;

    public Car(Engine engine) {
        this.engine = engine;
    }

    public void start() {
        this.engine.startUp();
    }

    public void stop() {
        this.engine.shutDown();
    }

    public void drive(Route route) {
        for (Directions directions : route.directions()) {
            directions.follow();
        }
    }
}


这个例子中,包括了两个协作类:Engine 和 Route,还有一个间接使用者:Directions
我们站在 Car 的视角,用测试替身替换 Engine 和 Route , 用伪实现替换Route,那么我们就完全控制了向 Car 提供的各种 Directions
类之间的关系如下:

 

  • 加速执行测试
    由于Car 需要调用 Directions,而后者的产生依赖于 Route,假设在 Route 层面需要的时间比较多,测试来不及等这么久的情况下,可以通过使用对 Route 放置测试替身,实现快速的不用等待的测试执行。
    放置一个测试替身,令它总是返回预先计算好的路径,这样会避免不必要的等待,而且测试运行的更快了,
  • 使执行变得确定
    任何的测试代码,都可能包含了不确定的随机性。为了验证代码和测试具有确定的结果,我们需要能够针对同样的代码进行重复的运行测试,并总能够得到相同的结果。
    事实上,这个情况非常理想状态。很多时候,生产的代码有随机因素。或许不确定的行为,最典型的情形就是依赖于时间的行为。回到我们的 Car 的这个例子,不同的时间,得到的路线(RouteDirections)可能是不同的。在高峰时间和非高峰时间,得到的路径导航,可能是不相同的。我们通过对 Route进行测试替身,使得之前不确定的测试变得确定起来。
  • 暴露隐藏的信息
    在 Car 这个例子里面,可以用测试替身完成最后一个需要它的理由。我们能看到,当 Car 进行启动的时候,需要调用了engine的 start()的方法。engine目前是私有型,我们在测试中无法获得的engine的项目类型。那么我们需要用一个测试替身,来通过给它增加状态的方式,验证单元测试对乱码的讨厌。
    被测试的代码:
public class TestEngine extends Engine {
    public boolean isRunning() {
        return isRunning;
    }

    private boolean isRunning;

    public void start() {
        this.isRunning = true;
    }
}
 
  • 测试替身的类型

主要的测试替身有 桩 (Stub)、伪造对象(Fake)、测试间谍(Spy)以及模拟对象(Mock)四种。

  1. Stub(桩):一般什么都不做,实现空的方法调用或者简单的硬编码返回即可。
  2. Fake(伪造对象):真实事物的简答版本,优化的伪造真实事物的行为,但是没有副作用或者使用真实事物的其它后果。比如替换数据库的对象,而得到虚假的伪造对象。
  3. Spy(测试间谍):需要得到对象内部的状态的时候,而该对象对外又是封闭的,那么需要做一个测试间谍,事先学会反馈消息,然后潜入对象内部去获取对象的状态。测试间谍是一种测试替身,它用于记录过去发生的情况,这样测试在事后就能知道所发生的一切。
  4. Mock(模拟对象):模拟对象是一个特殊的测试间谍。是一个在特定的情况下可以配置行为的对象,规定了在什么情况下,返回什么样的值的一种测试替身。Mock已经有了非常成熟的对象库,包括JMock、Mockito和EasyMock等。

2.2 [探讨]优秀单元测试的支柱

  • 分析:独立的测试易于单独运行

什么样的单元测试是独立的测试?

  • 分析:可维护的测试才是有意义的

什么样的措施可以使得单元测试是可维护的?

  • 可读的代码才是可维护的

如何从测试用例的要素中匹配单元测试代码的可读性?

  • 可靠的测试才是可靠的

从哪些角度的思考与设计可以让单元测试代码变得可信赖和可靠?

2.3 识别单元测试中的坏味道

  • 过度断言

过度断言是如此谨慎的敲定每个待检查行为的细节,以致它变得脆弱,并且掩盖了整体广度很深度之下的意图。当遇到过度断言,很难说清楚它要检查什么,并且当你退后一步观察,会看到测试打断的频率可能远超平均水平。它如此挑剔,以致无论任何变化都会造成输出与期望不同。

我们看下面的例子来具体讨论。被测试的类叫做LogFileTransformer,是一个用来转换日志格式的类。

public class LogFileTransformerTest {
    private String expectedOutput;
    private String logFile;
    @Before
    public void setUpBuildLogFile(){
        StringBuilder lines = new StringBuilder();
        lines.append("[2015-05-23 21:20:33] LAUNCHED");
        lines.append("[2015-05-23 21:20:33] session-di###SID");
        lines.append("[2015-05-23 21:20:33] user-id###UID");
        lines.append("[2015-05-23 21:20:33] presentation-id###PID");
        lines.append("[2015-05-23 21:20:33] screen1");
        lines.append("[2015-05-23 21:20:33] screen2");
        //TODO: lines.append(...)
        logFile = lines.toString();
    }
    @Before
    public void setUpBuildTransformedFile(){
        StringBuilder lines = new StringBuilder();
        lines.append("LAUNCHED");
        lines.append("session-di###SID");
        lines.append("user-id###UID");
        lines.append("presentation-id###PID");
        lines.append("screen1");
        lines.append("screen2");
        //TODO: lines.append(...)
        expectedOutput = lines.toString();
    }
    @Test
    public void testTransformationGeneratesRgiht(){
        TransfermationGenerator generator = new TransfermationGenerator();
        File outputFile = generator.transformLog(logFile);
        Assert.assertTrue("目标文件转换后不存在!", outputFile.exists());
        Assert.assertEquals("目标文件转换后不匹配!", expectedOutput, getFileContent(outputFile));
    }
}

 

看到过度断言了么?这里有两个断言,但是哪个是罪魁祸首,什么造成断言被滥用了呢?

第一个断言检查目标文件是否创建,第二个断言检查目标文件的内容是否符合期望。现在,第一个断言的价值值得商榷,而且很可能需要被删除。但是我们主要关注第二个断言——过度断言:

java Assert.assertEquals("目标文件转换后不匹配!", expectedOutput, getFileContent(outputFile));

看上去,它精确的验证了测试名称所暗示的内容,这是个重要的断言。问题是这个测试太宽泛了,导致断言对整个日志文件进行大规模的比较。这是一张厚厚的安全网,毫无疑问,即使是输出中最微小的变化,也会是断言失败。这也正是存在的问题。

上述例子太容易失败而变得脆弱,断言并无本质的错误,但是问题在于测试违反了构成优秀测试的基本指导原则。

一个测试应该只有一个失败原因

那么我们如何改进这个测试?

我们需要避免全文测试,就算需要要求,也需要分部分内容去测试。

@Test
public void testTransformationGeneratesRgiht2(){
    TransfermationGenerator generator = new TransfermationGenerator();
    File outputFile = generator.transformLog(logFile);
    Assert.assertTrue("目标文件转换后不匹配!", getFileContent(outputFile).contains("screen1###0"));
    Assert.assertTrue("目标文件转换后不匹配!", getFileContent(outputFile).contains("screen1###51"));
}
@Test
public void testTransformationGeneratesRgiht3(){
    TransfermationGenerator generator = new TransfermationGenerator();
    File outputFile = generator.transformLog(logFile);
    Assert.assertTrue("目标文件转换后不匹配!", getFileContent(outputFile).contains("session-di###SID#0"));
}

 

修改后,分部对指定的部分进行测试。

  • 人格分裂

改进测试的一个最简单的方法,就是找出人格分裂的情况。当测试出现了人格分裂的时候,我们认为它本身体现了多个测试,那是不对的。一个测试应当仅检查一件事并妥善执行。

我们看下面的例子。测试类针对一些命令行接口,用不同的命令行参数来测试Configuration类对象的行为。

 

public class ConfigurationTest {
    @Test
    public void testParingCommandLineArguments() {
        String[] args = {"-f", "hello.txt", "-v", "--version"};
        Configuration c = new Configuration();
        c.processArguments(args);
        Assert.assertEquals("hello.txt", c.getFileName());
        Assert.assertFalse(c.isDebuggingEnabled());
        Assert.assertFalse(c.isWarningsEnabled());
        Assert.assertTrue(c.isVerbose());
        Assert.assertTrue(c.shouldShowVersion());
        
        c = new Configuration();
        try{
            c.processArguments(new String[] {"-f"});
            Assert.fail("should 测试失败" );
        }catch (InvalidArgumentException expected){
            // 没有问题
        }
    }
}

 

这个测试的多重人格体现在它涉及了文件名、调试、警告、信息开关、版本号显示,还处理了空的命令行参数列表。这里没有遵循准备 --> 执行 --> 断言的结构。很明显这里断言了许多东西,虽然它们全部与解析命令行参数有关,但是还是可以彼此隔离的。

这个测试的主要问题是胃口太大,同时还存在一些重复,我们先排除这些干扰,这样就可以看清主要问题了。

首先,在测试里用了多次对Configuration类的构造器实例化的操作,我们可以将此类的操作抽取出来,并用@Before方法中实例化。这样也去掉了测试中的一部分重复。

代码如下:

protected Configuration c;
@Before
public void instantiateDefaultConfiguration() {
    c = new Configuration();
}

 

去掉重复的实例化以后,我们剩下来对 processArguments()的两次不同调用和6个不同的断言(包括了 try-catch-fail模式)。这样意味着我们至少要用两个不同的场景——也就是两个不同的测试。

结合上面的 @Before,代码如下:

@Test
public void validArgumentsProvided(){
      String[] args = {"-f", "hello.txt", "-v", "--version"};
    c.processArguments(args);
    Assert.assertEquals("hello.txt", c.getFileName());
    Assert.assertFalse(c.isDebuggingEnabled());
    Assert.assertFalse(c.isWarningsEnabled());
    Assert.assertTrue(c.isVerbose());
    Assert.assertTrue(c.shouldShowVersion());
}
@Test
public void missingArgument(){
    try{
          c.processArguments(new String[] {"-f"});
          Assert.fail("should 测试失败" );
    }catch (InvalidArgumentException expected){
          // 没有问题
    }
}

但是其实我们还在半路上,一些检查条件是命令行参数的显然结果,另一些是隐含的默认值。从这个角度改进,我们将测试分解成多个测试类。如下图所示:

 

 

这次重构意味着有一个测试关注于验证正确的默认值,另一个测试类验证显示设置的命令行值能正确工作,第三个指出应当如何处理错误的配置项。代码如下:

  • AbstractConfigTestCase
public abstract class AbstractConfigTestCase {
    protected Configuration c;

    @Before
    public void instantiateDefaultConfiguration() {
        c = new Configuration();
        c.processArguments(args());
    }

    protected String[] args() {
        return new String[] {};
    }
}
 ​
 
  • TestDefaultConfigValues
public class TestDefaultConfigValues extends AbstractConfigTestCase {
    @Test
    public void defaultOptionsAreSetCorrectly() {
        assertFalse(c.isDebuggingEnabled());
        assertFalse(c.isWarningsEnabled());
        assertFalse(c.isVerbose());
        assertFalse(c.shouldShowVersion());
    }
}
 
  • TestExplicitlySetConfigValues
public class TestExplicitlySetConfigValues extends AbstractConfigTestCase {
    @Override
    protected String[] args() {
        return new String[] { "-f", "hello.txt", "-v", "-d", "-w", "--version" };
    }

    @Test
    public void explicitOptionsAreSetCorrectly() {
        assertEquals("hello.txt", c.getFileName());
        assertTrue(c.isDebuggingEnabled());
        assertTrue(c.isWarningsEnabled());
        assertTrue(c.isVerbose());
        assertTrue(c.shouldShowVersion());
    }
}
  • TestConfigurationErrors
public class TestConfigurationErrors extends AbstractConfigTestCase {
    @Override
    protected String[] args() {
        return new String[] { "-f" };
    }

    @Test(expected = InvalidArgumentException.class)
    public void missingArgumentRaisesAnError() {
    }
}
 
  • 过分保护

运行 Java 代码的时候,常见的Bug之一就是突然出现NullPointerExceptionInndexOutOfBoundsException,这是由于方法意外的收到空指针或者空串参数造成的。当然这些可以由程序员对其进行单元测试,从而增强守卫,保护好自己。

但是,程序员往往不是保护测试免于以NullPointerException而失败,而是让测试优雅的以华丽措辞的断言而失败。这是一种典型的坏味道。

代码示例:用了两个断言来验证正确的计算:一个验证返回的Data对象不为空,另一个验证实际的计数是正确的。

public class TestCount {
    @Test
    public void count(){
        Data data = project.getData();
        Assert.assertNotNull(data);
        Assert.assertEquals(8, data.count());
    }
}

 

这是过度保护的测试,以为assertNotNull(data)是多余的。在调用方法之前,第一个断言检查data不为空,如果为空,测试就失败,这样的测试受到了过度的保护。这是因为当data为空的时候,就算没有第一个断言,测试仍然会时报。第二个断言试图调用data上的count()时,测试会不幸的以NullPointerException而失败。

需要做的事情,是删除冗余的断言,它基本上是不能提供附加价值的断言和测试语句。

删除第5行。Assert.assertNotNull(data);

  • 重复测试

程序员在写代码的时候,往往关注和追求整洁的代码(clean code)。而重复就是导致代码失去整洁的罪魁祸首之一。那么什么是重复呢?简单来说,重复是存在多份拷贝或对单一概念的多次表达——这都是不必要的重复。

重复是不好的,它增加了代码的不透明性,使得散落在各处的概念和逻辑很难理解。此外,对于修改代码的程序员来说,每一处重复都是额外的开销。如果忘记或者遗漏了某处的改动,那么又增加了出现Bug的机会。

代码示例:这个代码展示了几种形式的重复。

public class TestTemplate {
  @Test
  public void emptyTemplate() throws Exception {
      assertEquals("", new Template("").evaluate());
  }

  @Test
  public void plainTextTemplate() throws Exception {
      assertEquals("plaintext", new Template("plaintext").evaluate());
  }
}

代码中出现了最常见的文本字符串重复,在两个断言中,空字符串和plaintext字符都出现了两次。我们叫这种重复为文字重复。我们可以通过定义局部变量来移除它们。同时在上述测试类中,还存在另一种重复,也许比显而易见的字符串重复有趣的多。当我们提取那些局部变量的时候,这种重复会变得更加清晰。

首先,我们抽取重复的字符串,清理这些坏的味道。

public class TestTemplate {
  @Test
  public void emptyTemplate() throws Exception {
      String template = "";
      assertEquals(template, new Template(template).evaluate());
  }

  @Test
  public void plainTextTemplate() throws Exception {
      String template = "plaintext";
      assertEquals(template, new Template(template).evaluate());
  }
}

其次,确实还有一些比较严重的重复,我们看这两个测试,只有字符串是不同的。当我们抽取的字符串之后,剩下的断言是一模一样的,这种操作不同数据的重复逻辑,我们叫做结构重复。以上的两个代码块用一致的结构操作了不同的数据。

我们去掉这种重复,提炼重复后,产生一个自定义的断言方式。

public class TestTemplate {
  @Test
  public void emptyTemplate() throws Exception {
      assertTemplateRendersAsItself("");
  }

  @Test
  public void plainTextTemplate() throws Exception {
      assertTemplateRendersAsItself("plaintext");
  }

  private void assertTemplateRendersAsItself(String template) {
      assertEquals(template, new Template(template).evaluate());
  }
}

  • 条件逻辑

在测试中,一旦存在条件逻辑的时候,一般都不是一件好事儿。这里的条件逻辑,是一种坏味道。假设我们正在重构代码,并运行之前的单元测试来保证代码一切正常。可是此时发现某个测试失败了。看上去很出乎意料,没想到这点小的变更却会影响测试,但是它的确发生了。我们查看代码,却突然发现自己无法知道,测试失败的时候,代码当时在干什么。

代码示例:测试创建了DictionaryDemo(字典)对象,用数据填充它,并验证请求到的Iterator(迭代器)的内容是正确的。

public class DictionaryTest {
    @Test
    public void returnsAnIteratorForContents(){
        DictionaryDemo dictionary = new DictionaryDemo() ;
        dictionary.add("key1", new Long(3));
        dictionary.add("key2", "45678");
        for (Iterable e = dictionary.iterator(); e.hasNext();){
            Map.Entry entry = (Map.Entry) e.next();
            if( "key1".equals(entry.getKey())){
                Assert.assertEquals(3L, entry.getValue());
            }
            if( "key2".equals(entry.getKey())){
                Assert.assertEquals("45678", entry.getValue());
            }
        }
    }
}

我们可以看到,这个测试针对的只是 DictionaryDemo的内部行为,但是仍然非常难理解和解释。通过遍历条目,我们得到返回的Iterator,并根据键值对的关系,通过 Key,找到该条目的 Value。但是实际上,如果这两个 Key 没有被添加进去的时候,这个测试不会报错。这里存在了坏的味道。通过使用自定义断言,得到修改。

代码如下:

public class DictionaryTest {

    @Test
    public void returnsAnIteratorForContents2(){
        DictionaryDemo dictionary = new DictionaryDemo() ;
        dictionary.add("key1", new Long(3));
        dictionary.add("key2", "45678");
        assertContains(dictionary.iterator(), "key1", 3L);
        assertContains(dictionary.iterator(), "key2", "45678");

    }

    private void assertContains(Iterator i, Object key, Object value){
        while (i.hasNext()){
            Map.Entry entry =  (Map.Entry) i.next();
            if( key.equals(entry.getKey())){
                Assert.assertEquals(value, entry.getValue());
                return;
            }
        }
        Assert.fail();
    }
}

最后强调一下,Assert.fail()很容易被遗漏掉。接下来我们就要再一次修改这样的坏味道了。

  • 永不失败的测试

永不失败的测试,如果是真的能够做到百战百胜,那么是再好不过了。但是往往事与愿违,永不失败的测试往往比没有测试还糟糕。因为它给了虚假的安全感,这样的测试没有价值,出了事情它绝不警告你。

检查代码是否抛出期望的异常,或许这是一个最常见的在永不失败的测试的场景。

示例代码:

public class HelloWorldTests {
    @Test
    public void includeForMissingResourceFails(){
        try {
            new Environment().include("somethingthatdoesnotexist");
        }catch (IOException e){
            Assert.assertThat(e.getMessage(),
                    contains("somethingthatdoesnotexist"));
        }
    }
}

这个代码清单中测试的结果是这样的:

  1. 如果代码如期工作并抛出异常,那么这个异常就被catch代码块捕获,于是测试通过。
  2. 如果代码没有如期工作,也就是没有抛出异常,那么方法返回,测试通过,我们并未意识到代码有任何问题。

但是,这是一个抛异常的测试,在没有抛出异常的时候,测试其实是失败的,需要调用fail()来表示失败。

public class HelloWorldTests {
    @Test
    public void includeForMissingResourceFails(){
        try {
            new Environment().include("somethingthatdoesnotexist");
              Assert.fail();
        }catch (IOException e){
            Assert.assertThat(e.getMessage(),
                    contains("somethingthatdoesnotexist"));
        }
    }
}

简单的增加对 JUnit 中 fail() 方法的调用,是得测试起作用。现在除非抛出期望的异常,否则测试失败。

另外 JUnit 4 引入的一个新特性是 @Test 注解的 expected 属性。

public class HelloWorldTests {
    @Test(expected = IOException.class)
    public void includeForMissingResourceFails(){
        new Environment().include("somethingthatdoesnotexist");
    }
}

这样的特性,更短、更容易解析、更不易出错和遗漏。当然这种方法的缺点也很明显:我们不能访问所抛出的实际异常对象,无法进一步对异常进行断言。总之,要防止偶然的写一个用不失败的的测试,最好的方法是养成运行测试的习惯,或许是临时修改被测试的代码来故意触发一次失败,从而看到所犯的错误以及坏味道。

2.4 [探讨]在项目中进行单元测试

  • 分析:项目中单元测试策略

在一个项目中,单元测试的策略的制定与执行需要考虑哪些因素?

  • 分析:如何组织单元测试的数据

在一个项目中,单元测试的数据是否应该以硬编码的形式写入代码中?如果不是的话,需要如何组织这些测试用的数据呢?

  • 分析:谁该为项目的质量负责

请思考一个问题,一个典型的项目组(包含项目经理、测试、开发和需求分析师)中谁应该为项目的质量负责?

​本文转自知乎号:汇智动力技术团队,有更多IT技术问题,欢迎加Q2834267066进行交流。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_43802541/article/details/88843032

智能推荐

java程序员面试32问 _evening424的博客-程序员信息网

第一,谈谈final, finally, finalize的区别。   final 修饰符(关键字)如果一个类被声明为final,意味着它不能再派生出新的子类,不能作为父类被继承。因此一个类不能既被声明为 abstract的,又被声明为final的。将变量或方法声明为final,可以保证它们在使用中不被改变。被声明为final的变量必须在声明时给定初值,而在以后的引用中只能读取,不可修改。被声

uc-/os-ii的中断和时钟_ryan_jianjian的博客-程序员信息网

1.中断管理和中断服务程序的结构  3.1.1中断响应过程UC/OS-II系统响应中断的过程是:系统接收到中断请求时,如果这时CPU处于中断允许状态,即中断时开放的,系统就会终止正在运行的当前任务,而按照中断向量的指向转而去运行服务子程序。需要特别注意的是对于可剥夺形的UC/OS-II内核来说,中断子程序运行结束之后,系统将会根据情况进行一次任务调度去运行优先级别最高的就绪任务,并不一定接

Linux:Unable to locate package错误解决办法_鲲志说的博客-程序员信息网

问题最近给Linux换了一个Ubuntu系统,安装了jdk和tomcat,这次准备安装MySQL,结果在输入命令 sudo apt-get install后,结果出现了下面的错误:E: Unable to locate package mysql-server解决办法从网上查阅资料后发现,问题出在执行sudo apt-get install之前,我更换了软件源,但是却忘了update,...

msf数据库配置问题解决_zsd747289639的博客-程序员信息网

错误信息: Failed to connect to the database: could not connect to server: Connection refused Is the server running on host "127.0.0.1" and accepting TCP/IP connections on port 5432?

三面腾讯,已拿offer!系列篇_普通网友的博客-程序员信息网

前言Java作为最全面的语言,国内开发者也是最多的,Java综合起来各方面都不错,在大部分场景下是一种稳健的技术选择。加上近年来安卓的推动,目前也是最流行的一种语言。现在Java的就业市场看起来还是挺大的,而且工资也是比较可观的,**但我为什么说是看起来呢?**其实,最主要的原因还是市场决定的,每年涌入市场的Java开发人员应该是最多的,但是市场上Java初级开发人员早已经饱和了,也就造成了就业市场较大的假象。如果初级程序员一直不提升技术,那么注定要被淘汰。那么要如何才能系统的学习Java语言,从一名

matlab 序列时间尺度变换程序,使用Matlab合成月尺度、年尺度数据_911ACE的博客-程序员信息网

处理PML v2陆地蒸散发与总初级生产力数据集,由8天尺度合成月尺度、年尺度数据处理代码% By Yang,2020/5/9% 所有数据全部解压放在一个文件夹下%% 合成年尺度数据for j = 1:18 % 2002-2019,18年a = num2str(2001+j...

随便推点

less学习(五)—关于Mixin_MoLvSHan的博客-程序员信息网

今天进入到Mixin(混合)的学习,经过前面几天的学习已经初步掌握了关于变量和关于Extend的一些语法和规则,如果有朋友觉得我写的实在渣的话,可以移步less官方文档自行学习。先上一个基本用法的例子.box1 { color: red;}.box3 { .box1();}编译为:/* line 1, http://localhost/about-less/styles.le

关于“COUT<<endl”等价问题测试_count<<endl等价于_wjj5351的博客-程序员信息网

关于“COUT&lt;&lt;endl”等价问题测试#include using namespace std;void main(){int k=1;cout&lt;&lt;“k=”&lt;&lt;k&lt;&lt;endl;cout&lt;&lt;“k=”&lt;&lt;k&lt;&lt;’\n’;cout&lt;&lt;“k=”&lt;&lt;k&lt;&lt;’\12’;cout&lt;&lt;“k=”&lt;&lt;k&lt;&lt;’\xA’;cout&lt;&lt;“k=”&

Mysql递归查询实践_后皇嘉树,橘徠服兮的博客-程序员信息网

表格数据结构ding_deptfunctionDROP FUNCTION IF EXISTS `getChildDepts`$$CREATE DEFINER=`root`@`localhost` FUNCTION `getChildDepts`(rootId INT) RETURNS VARCHAR(1000) CHARSET utf8BEGIN DECLARE sTemp VARCHAR...

获取request输入流_request获取输入流_CodeWorkerZHL的博客-程序员信息网

前言我们在进行请求进行拦截的时候经常会碰上这样一个问题,我们想要在拦截器filter中获取request的请求,如果使用请求中默认的getInputStream()方法或者getReader()方法获取数据,但是在后面的Controller中使用@ResquestBody注解,我们读取不到request的body中的值,这是因为request的body中的数据只能通过getInputStrea...

应用8255A控制LED小灯开闭(附代码注释)_8255控制8个led灯依次点亮_zer1123的博客-程序员信息网

设8255A的A口和B口都工作在方式0,A口作为输出口,接有8个开关;B口为输出口,接有8个发光二极管.系统硬件电路中不断扫描开关Ki,当K0闭合时,点亮LED0,LED2,LED4,LED6,其他LED灯暗,当K1闭合时,点亮LED1,:LED3,LED5,LED7,其他LED暗;当开关K0和K1同时闭合时全灭.设8255A的端口地址为200H~206H.试编程实现上述控制.参考程序: 

Linux 内核网络优化_wmem_max 的值在设置后会被改变_ysdaniel的博客-程序员信息网

核心的网络功能,所以相关的设定数据都是放置在 /proc/sys/net/ipv4/ 这个目录当中。 至于该目录下各个档案的详细资料,建议大家可以参考核心的说明文件:/usr/src/linux-{version}/networking/ip-sysctl.txt RHEL 6.0:/usr/src/kernels/2.6.32-71.el6.i686/net/ipv4/Kco

推荐文章

热门文章

相关标签