在Python中进行自动化单元测试的教程

发表于 5年以前  | 总阅读数:1062 次

一、软件测试

大型软件系统的开发是一个很复杂的过程,其中因为人的因素而所产生的错误非常多,因此软件在开发过程必须要有相应的质量保证活动,而软件测试则是保证质量的关键措施。正像软件熵(software entropy)所描述的那样:一个程序从设计很好的状态开始,随着新的功能不断地加入,程序逐渐地失去了原有的结构,最终变成了一团乱麻(其实最初的"很好的状态"得加个问号)。测试的目的说起来其实很简单也极具吸引力,那就是写出高质量的软件并解决软件熵这一问题。

可惜的是,软件开发人员很少能在编码的过程中就进行软件测试,大部分软件项目都只在最终验收时才进行测试,有些项目甚至根本没有测试计划!随着软件质量意识的增强,许多软件开发组织开始转向UML、CMM、RUP、XP等软件工程方法,以期提高软件质量,并使软件开发过程更加可控,好在这些方法对测试都提出了很严格的要求,从而使得测试在软件开发过程的作用开始真正体现出来。

软件测试作为一种系统工程,涉及到整个软件开发过程的各个方面,需要管理人员、设计人员、开发人员和测试人员的共同努力。作为软件开发过程中的主要力量,现今的程序员除了要编写实现代码外,还承担着单元测试这一艰巨任务,因此必须采用新的工作模式:

  • 编写和维护一套详尽的单元测试用例;
  • 先构造单元测试和验收测试用例,然后再编写代码;
  • 根据构造的测试用例来编写代码。

单元测试负责对最小的软件设计单元(模块)进行验证,它使用软件设计文档中对模块的描述作为指南,对重要的程序分支进行测试以发现模块中的错误。由于软件模块并不是一个单独的程序,为了进行单元测试还必须编写大量额外的代码,从而无形中增加了开发人员的工作量,目前解决这一问题比较好的方法是使用测试框架。测试框架是在用XP方法进行单元测试时的关键,尤其是在需要构造大量测试用例时更是如此,因为如果完全依靠手工的方式来构造和执行这些测试,肯定会变成一个花费大量时间并且单调无味的工作,而测试框架则可以很好地解决这些问题。

使用Python语言的开发人员可以使用Steve Purcell编写的PyUnit作为单元测试框架,通过将单元测试融合到PyUnit这一测试框架里,Python程序员可以更容易地增加、管理、执行测试用例,并对测试结果进行分析。此外,使用PyUnit还可以实现自动单元测试(回归测试)。

二、规范Python单元测试

测试是一个贯穿于整个开发过程的连续过程,从某个意义上说,软件开发的过程实际上就是测试过程。正如Martin Fowler所说的"在你不知道如何测试代码之前,就不该编写程序。而一旦你完成了程序,测试代码也应该完成。除非测试成功,你不能认为你编写出了可以工作的程序。"

测试最基本的原理就是比较预期结果是否与实际执行结果相同,如果相同则测试成功,否则测试失败。为了更好地理解PyUnit这一自动测试框架的作用,先来看一个简单的例子,假设我们要对例1中的Widget类进行测试:

    例1. widget.py  

    # 将要被测试的类
    class Widget:
      def __init__(self, size = (40, 40)):
        self._size = size
      def getSize(self):
        return self._size
      def resize(self, width, height):
        if width 0 or height < 0:
          raise ValueError, "illegal size"
        self._size = (width, height)
      def dispose(self):
        pass

采用手工方式进行单元测试的Python程序员很可能会写出类似例2的测试代码来,

例2. manual.py  

    from widget import Widget
    # 执行测试的类
    class TestWidget:
      def testSize(self):
        expectedSize = (40, 40);
        widget = Widget()
        if widget.getSize() == expectedSize:
          print "test [Widget]: getSize works perfected!"
        else:
          print "test [Widget]: getSize doesn't work!"
    # 测试
    if __name__ == '__main__':
      myTest = TestWidget()
      myTest.testSize()

稍一留心你不难发现这种手工测试方法存在许多问题。首先,测试程序的写法没有一定的规范可以遵循,十个程序员完全可能写出十种不同的测试程序来,如果每个Python程序员都有自己不同的设计测试类的方法,光维护被测试的类就够麻烦了,谁还顾得上维护测试类。其次,需要编写大量的辅助代码才能进行单元测试,例1中用于测试的代码甚至比被测试的代码还要多,而这毫无疑问将增大Python程序员的工作量。

为了让单元测试代码能够被测试和维护人员更容易地理解,最好的解决办法是让开发人员遵循一定的规范来编写用于测试的代码,具体到Python程序员来讲,则是要采用PyUnit这一自动测试框架来构造单元测试用例。目前PyUnit已经得到了大多数Python开发人员的认可,成了事实上的单元测试标准。如果采用PyUnit来进行同样的测试,则测试代码将如例3所示:

例3. auto.py  

    from widget import Widget
    import unittest
    # 执行测试的类
    class WidgetTestCase(unittest.TestCase):
      def setUp(self):
        self.widget = Widget()
      def tearDown(self):
        self.widget = None
      def testSize(self):
        self.assertEqual(self.widget.getSize(), (40, 40))
    # 构造测试集
    def suite():
      suite = unittest.TestSuite()
      suite.addTest(WidgetTestCase("testSize"))
      return suite
    # 测试
    if __name__ == "__main__":
      unittest.main(defaultTest = 'suite')

在采用PyUnit这一单元测试框架后,用于测试的代码做了相应的改动:

  • 用import语句引入unittest模块。
  • 让所有执行测试的类都继承于TestCase类,可以将TestCase看成是对特定类进行测试的方法的集合。
  • 在setUp()方法中进行测试前的初始化工作,并在tearDown()方法中执行测试后的清除工作,setUp()和tearDown()都是TestCase类中定义的方法。
  • 在testSize()中调用assertEqual()方法,对Widget类中getSize()方法的返回值和预期值进行比较,确保两者是相等的,assertEqual()也是TestCase类中定义的方法。
  • 提供名为suite()的全局方法,PyUnit在执行测试的过程调用suit()方法来确定有多少个测试用例需要被执行,可以将TestSuite看成是包含所有测试用例的一个容器。

虽然看起来有点复杂,但PyUnit使得所有的Python程序员都可以使用同样的单元测试方法,测试过程不再是杂乱无章的了,而是在同一规范指导下进行的有序行为,这就是使用PyUnit这一自动单元测试框架所带来的最大好处。

三、自动测试框架PyUnit

在对软件测试理论和PyUnit有了一个大致了解之后,下面辅以具体的实例介绍Python程序员如何借助PyUnit来进行单元测试。所有的代码均在Python 2.2.2下调试通过,操作系统使用的是Red Hat Linux 9。

3.1 安装

在Python中进行单元测试时需要用到PyUnit模块,Python 2.1及其以后的版本都将PyUnit作为一个标准模块,但如果你使用的是较老版本的Python,那就要自已动手安装了。在PyUnit的网站(http://sourceforge.net/projects/pyunit)上可以下载到PyUnit最新的源码包,此处使用的是pyunit-1.4.1.tar.gz。

在下载好PyUnit软件包后,执行下面的命令对其进行解压缩:


    [root@gary source]# tar xzvf pyunit-1.4.1.tar.gz

要在Python程序中使用PyUnit模块,最简单的办法是确保PyUni软件包中的文件unittest.py和unittestgui.py都包含在Python的搜索路径中,这既可以通过直接设置PYTHONPATH环境变量来实现,也可以执行以下的命令来将它们复制到Python的当前搜索路径中:


    [root@gary source]# cd pyunit-1.4.1
    [root@gary pyunit-1.4.1]# python setup.py install

3.2 测试用例TestCase

软件测试中最基本的组成单元是测试用例(test case),PyUnit使用TestCase类来表示测试用例,并要求所有用于执行测试的类都必须从该类继承。TestCase子类实现的测试代码应该是自包含(self contained)的,也就是说测试用例既可以单独运行,也可以和其它测试用例构成集合共同运行。

TestCase在PyUnit测试框架中被视为测试单元的运行实体,Python程序员可以通过它派生自定义的测试过程与方法(测试单元),利用Command和Composite设计模式,多个TestCase还可以组合成测试用例集合。PyUnit测试框架在运行一个测试用例时,TestCase子类定义的setUp()、runTest()和tearDown()方法被依次执行,最简单的测试用例只需覆盖runTest()方法来执行特定的测试代码就可以了,如例4所示:

    例4. static_single.py  

    import unittest
    # 执行测试的类
    class WidgetTestCase(unittest.TestCase):
      def runTest(self):
        widget = Widget()
        self.assertEqual(widget.getSize(), (40, 40))

而要在PyUnit测试框架中构造上述WidgetTestCase类的一个实例,应该不带任何参数调用其构造函数:

testCase = WidgetTestCase()

一个测试用例通常只对软件模块中的一个方法进行测试,采用覆盖runTest()方法来构造测试用例在PyUnit中称为静态方法,如果要对同一个软件模块中的多个方法进行测试,通常需要构造多个执行测试的类,如例5所示:

    例5. static_multi.py  

    import unittest
    # 测试getSize()方法的测试用例
    class WidgetSizeTestCase(unittest.TestCase):
      def runTest(self):
        widget = Widget()
        self.assertEqual(widget.getSize(), (40, 40))
    # 测试resize()方法的测试用例
    class WidgetResizeTestCase(unittest.TestCase):
      def runTest(self):
        widget = Widget()
        widget.resize(100, 100)
        self.assertEqual(widget.getSize(), (100, 100))

采用静态方法,Python程序员不得不为每个要测试的方法编写一个测试类(该类通过覆盖runTest()方法来执行测试),并在每一个测试类中生成一个待测试的对象。在为同一个软件模块编写测试用例时,很多时候待测对象有着相同的初始状态,因此采用上述方法的Python程序员不得不在每个测试类中为待测对象进行同样的初始化工作,而这往往是一项费时且枯燥的工作。

一种更好的解决办法是采用PyUnit提供的动态方法,只编写一个测试类来完成对整个软件模块的测试,这样对象的初始化工作可以在setUp()方法中完成,而资源的释放则可以在tearDown()方法中完成,如例6所示:

    例6. dynamic.py  

    import unittest
    # 执行测试的类
    class WidgetTestCase(unittest.TestCase):
      def setUp(self):
        self.widget = Widget()
      def tearDown(self):
        self.widget.dispose()
        self.widget = None
      def testSize(self):
        self.assertEqual(self.widget.getSize(), (40, 40))
      def testResize(self):
        self.widget.resize(100, 100)
        self.assertEqual(self.widget.getSize(), (100, 100))

采用动态方法最大的好处是测试类的结构非常好,用于测试一个软件模块的所有代码都可以在同一个类中实现。动态方法不再覆盖runTest()方法,而是为测试类编写多个测试方法(按习惯这些方法通常以test开头),在创建TestCase子类的实例时必须给出测试方法的名称,来为PyUnit测试框架指明运行该测试用例时究竟应该调用测试类中的哪个方法:


    sizeTestCase = WidgetTestCase("testSize")
    resizeTestCase = WidgetTestCase("testResize")

3.3 测试用例集TestSuite

完整的单元测试很少只执行一个测试用例,开发人员通常都需要编写多个测试用例才能对某一软件功能进行比较完整的测试,这些相关的测试用例称为一个测试用例集,在PyUnit中是用TestSuite类来表示的。

在创建了一些TestCase子类的实例作为测试用例之后,下一步要做的工作就是用TestSuit类来组织它们。PyUnit测试框架允许Python程序员在单元测试代码中定义一个名为suite()的全局函数,并将其作为整个单元测试的入口,PyUnit通过调用它来完成整个测试过程。


    def suite():
      suite = unittest.TestSuite()
      suite.addTest(WidgetTestCase("testSize"))
      suite.addTest(WidgetTestCase("testResize"))
      return suite

也可以直接定义一个TestSuite的子类,并在其初始化方法(init)中完成所有测试用例的添加:


                            class WidgetTestSuite(unittest.TestSuite):
      def __init__(self):
        unittest.TestSuite.__init__(self, map(WidgetTestCase,
                           ("testSize",
                            "testResize")))

这样只需要在suite()方法中返回该类的一个实例就可以了:


      def suite():
      return WidgetTestSuite()

如果用于测试的类中所有的测试方法都以test开,Python程序员甚至可以用PyUnit模块提供的makeSuite()方法来构造一个TestSuite:


      def suite():
      return unittest.makeSuite(WidgetTestCase, "test")

在PyUnit测试框架中,TestSuite类可以看成是TestCase类的一个容器,用来对多个测试用例进行组织,这样多个测试用例可以自动在一次测试中全部完成。事实上,TestSuite除了可以包含TestCase外,也可以包含TestSuite,从而可以构成一个更加庞大的测试用例集:


    suite1 = mysuite1.TheTestSuite()
    suite2 = mysuite2.TheTestSuite()
    alltests = unittest.TestSuite((suite1, suite2))

3.4 实施测试

编写测试用例(TestCase)并将它们组织成测试用例集(TestSuite)的最终目的只有一个:实施测试并获得最终结果。PyUnit使用TestRunner类作为测试用例的基本执行环境,来驱动整个单元测试过程。Python开发人员在进行单元测试时一般不直接使用TestRunner类,而是使用其子类TextTestRunner来完成测试,并将测试结果以文本方式显示出来:


    runner = unittest.TextTestRunner()
    runner.run(suite)

使用TestRunner来实施测试的例子如例7所示,

例7. text_runner.py  

    from widget import Widget
    import unittest
    # 执行测试的类
    class WidgetTestCase(unittest.TestCase):
      def setUp(self):
        self.widget = Widget()
      def tearDown(self):
        self.widget.dispose()
        self.widget = None
      def testSize(self):
        self.assertEqual(self.widget.getSize(), (40, 40))
      def testResize(self):
        self.widget.resize(100, 100)    
        self.assertEqual(self.widget.getSize(), (100, 100))    
    # 测试
    if __name__ == "__main__":
      # 构造测试集
      suite = unittest.TestSuite()
      suite.addTest(WidgetTestCase("testSize"))
      suite.addTest(WidgetTestCase("testResize"))

      # 执行测试
      runner = unittest.TextTestRunner()
      runner.run(suite)

要执行该单元测试,可以使用如下命令:


    [xiaowp@gary code]$ python text_runner.py

运行结果应该如下所示,表明执行了2个测试用例,并且两者都通过了测试:


    ..
    ----------------------------------------------------------------------
    Ran 2 tests in 0.000s
    OK

如果对数据进行修改,模拟出错的情形,将会得到如下结果:


    .F
    ==========================================
    FAIL: testResize (__main__.WidgetTestCase)
    ----------------------------------------------------------------------
    Traceback (most recent call last):
     File "text_runner.py", line 15, in testResize
      self.assertEqual(self.widget.getSize(), (200, 100))
     File "/usr/lib/python2.2/unittest.py", line 286, in failUnlessEqual
      raise self.failureException, \
    AssertionError: (100, 100) != (200, 100)
    ----------------------------------------------------------------------
    Ran 2 tests in 0.001s
    FAILED (failures=1)

默认情况下,TextTestRunner将结果输出到sys.stderr上,但如果在创建TextTestRunner类实例时将一个文件对象传递给了构造函数,则输出结果将被重定向到该文件中。在Python的交互环境中驱动单元测试时,使用TextTestRunner类是一个不错的选择。

PyUnit模块中定义了一个名为main的全局方法,使用它可以很方便地将一个单元测试模块变成可以直接运行的测试脚本,main()方法使用TestLoader类来搜索所有包含在该模块中的测试方法,并自动执行它们。如果Python程序员能够按照约定(以test开头)来命名所有的测试方法,那就只需要在测试模块的最后加入如下几行代码即可:


      if __name__ == "__main__":
      unittest.main()

使用main()方法来实施测试的例子如例8所示,

例8. main_runner.py  

    from widget import Widget
    import unittest
    # 执行测试的类
    class WidgetTestCase(unittest.TestCase):
      def setUp(self):
        self.widget = Widget()
      def tearDown(self):
        self.widget.dispose()
        self.widget = None
      def testSize(self):
        self.assertEqual(self.widget.getSize(), (40, 40))
      def testResize(self):
        self.widget.resize(100, 100)
        self.assertEqual(self.widget.getSize(), (100, 100))  
    # 测试
    if __name__ == "__main__":
      unittest.main()

要执行该单元测试,可以使用如下命令:


    [xiaowp@gary code]$ python main_runner.py

测试类WidgetTestCase中的所有测试方法都将被自动执行,但如果只想执行testSize()方法,可以使用如下命令:


    [xiaowp@gary code]$ python main_runner.py WidgetTestCase.testSize

如果在单元测试脚本中定义了TestSuite,还可以指定要运行的测试集。使用-h参数可以查看运行该脚本所有可能用到的参数:


    [xiaowp@gary code]$ python main_runner.py -h

为了使单元测试更具亲合力,PyUnit软件包中还提供了一个图形界面测试脚本unittestgui.py,将其复制到当前目录后,可以执行下面的命令来启动该测试工具,对main_runner.py脚本中的所有测试用例进行测试:


    [xiaowp@gary code]$ python unittestgui.py main_runner

该测试工具动行时的界面如图1所示:

201541593504100.png \(375×342\)

图1. 图形测试工具

单击Start按钮可以开始执行所有测试用例,测试结果将如图2所示:

201541593730394.png \(375×342\)

图2 测试结果

使用图形界面可以更好地进行单元测试,查询测试结果也更加方便。PyUnit对于没有通过的测试会进行区分,指明它是失败(failure)还是错误(error),失败是被assert类方法(如assertEqual)检查到的预期结果,而错误则是由意外情况所引起的。

四、小结

测试是保证软件质量的关键,新的软件开发方法要求程序员在编写代码前先编写测试用例,并在软件开发过程中不断地进行单元测试,从而最大限度地减少缺陷(Bug)的产生。软件单元测试是XP方法的基石,测试框架为程序员进行单元测试提供了统一的规范,Python程序员可以使用PyUnit作为软件开发过程中的自动单元测试框架。

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:1年以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:1年以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:1年以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:1年以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:1年以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:1年以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:1年以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:1年以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:1年以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:1年以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:1年以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:1年以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:1年以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:1年以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:1年以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:1年以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:1年以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:1年以前  |  398次阅读  |  详细内容 »
 相关文章
Android插件化方案 5年以前  |  237229次阅读
vscode超好用的代码书签插件Bookmarks 2年以前  |  8063次阅读
 目录